From 3caa0a90aac64745e8227fb5c4f69b8c6866fcb9 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Fri, 6 Jun 2025 17:50:56 +0800 Subject: [PATCH 1/6] chore(Texture2DConverter): fix typo, optimize docstring and types --- UnityPy/export/Texture2DConverter.py | 29 +++++++++++----------------- 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/UnityPy/export/Texture2DConverter.py b/UnityPy/export/Texture2DConverter.py index 464044d4..8eb8682d 100644 --- a/UnityPy/export/Texture2DConverter.py +++ b/UnityPy/export/Texture2DConverter.py @@ -4,6 +4,7 @@ from io import BytesIO from threading import Lock from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union import astc_encoder import texture2ddecoder @@ -117,11 +118,12 @@ def compress_astc(data: bytes, width: int, height: int, target_texture_format: T def image_to_texture2d( img: Image.Image, - target_texture_format: Union[TF, int], - platform: int = 0, + target_texture_format: Union[TextureFormat, int], + platform: Union[BuildTarget, int] = 0, platform_blob: Optional[List[int]] = None, flip: bool = True, ) -> Tuple[bytes, TextureFormat]: + """Converts a PIL Image to Texture2D bytes.""" if not isinstance(target_texture_format, TextureFormat): target_texture_format = TextureFormat(target_texture_format) @@ -178,7 +180,7 @@ def image_to_texture2d( elif target_texture_format == TF.Alpha8: tex_format = TF.Alpha8 pil_mode = "A" - # R - should probably be moerged into #A, as pure R is used as Alpha + # R - should probably be merged into #A, as pure R is used as Alpha # but need test data for this first elif target_texture_format in [ TF.R8, @@ -244,15 +246,7 @@ def get_image_from_texture2d( texture_2d: Texture2D, flip: bool = True, ) -> Image.Image: - """converts the given texture into PIL.Image - - :param texture_2d: texture to be converterd - :type texture_2d: Texture2D - :param flip: flips the image back to the original (all Unity textures are flipped by default) - :type flip: bool - :return: PIL.Image object - :rtype: Image - """ + """Converts the given Texture2D object to PIL Image.""" return parse_image_data( texture_2d.get_image_data(), texture_2d.m_Width, @@ -269,12 +263,13 @@ def parse_image_data( image_data: Union[bytes, bytearray, memoryview], width: int, height: int, - texture_format: Union[int, TextureFormat], + texture_format: Union[TextureFormat, int], version: Tuple[int, int, int, int], - platform: int, - platform_blob: Optional[bytes] = None, + platform: Union[BuildTarget, int], + platform_blob: Optional[List[int]] = None, flip: bool = True, ) -> Image.Image: + """Converts the given image data bytes to PIL Image.""" if not width or not height: return Image.new("RGBA", (0, 0)) @@ -333,9 +328,7 @@ def parse_image_data( def swap_bytes_for_xbox(image_data: Union[bytes, bytearray, memoryview]) -> bytearray: - """swaps the texture bytes - This is required for textures deployed on XBOX360. - """ + """Swaps the texture bytes for textures deployed on XBOX360.""" image_data = bytearray(image_data) for i in range(0, len(image_data), 2): image_data[i : i + 2] = image_data[i : i + 2][::-1] From e9bbc3a9b3c48eb435ec1ab833f0f4952cc5c08b Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Fri, 6 Jun 2025 20:38:36 +0800 Subject: [PATCH 2/6] chore(Texture2DConverter): remove redundant ifs --- UnityPy/export/Texture2DConverter.py | 44 ++++++++++++---------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/UnityPy/export/Texture2DConverter.py b/UnityPy/export/Texture2DConverter.py index 8eb8682d..dabf498c 100644 --- a/UnityPy/export/Texture2DConverter.py +++ b/UnityPy/export/Texture2DConverter.py @@ -135,21 +135,15 @@ def image_to_texture2d( tex_format = TF.RGBA32 pil_mode = "RGBA" - # DXT + # DXT / BC if target_texture_format in [TF.DXT1, TF.DXT1Crunched]: tex_format = TF.DXT1 compress_func = compress_etcpak elif target_texture_format in [TF.DXT5, TF.DXT5Crunched]: tex_format = TF.DXT5 compress_func = compress_etcpak - elif target_texture_format in [TF.BC4]: - tex_format = TF.BC4 - compress_func = compress_etcpak - elif target_texture_format in [TF.BC5]: - tex_format = TF.BC5 - compress_func = compress_etcpak - elif target_texture_format in [TF.BC7]: - tex_format = TF.BC7 + elif target_texture_format in [TF.BC4, TF.BC5, TF.BC7]: + tex_format = target_texture_format compress_func = compress_etcpak # ASTC elif target_texture_format.name.startswith("ASTC"): @@ -210,10 +204,6 @@ def image_to_texture2d( switch_info = None if TextureSwizzler.is_switch_swizzled(platform, platform_blob): - if TYPE_CHECKING: - # due to earlier check platform_blob can't be None - assert platform_blob is not None - gobsPerBlock = TextureSwizzler.get_switch_gobs_per_block(platform_blob) s_tex_format = tex_format if tex_format == TF.RGB24: s_tex_format = TF.RGBA32 @@ -221,23 +211,26 @@ def image_to_texture2d( elif tex_format == TF.BGR24: s_tex_format = TF.BGRA32 pil_mode = "BGRA" + + assert platform_blob is not None + gobs_per_block = TextureSwizzler.get_switch_gobs_per_block(platform_blob) block_size = TextureSwizzler.TEXTUREFORMAT_BLOCK_SIZE_MAP[s_tex_format] - width, height = TextureSwizzler.get_padded_texture_size(img.width, img.height, *block_size, gobsPerBlock) - switch_info = (block_size, gobsPerBlock) + width, height = TextureSwizzler.get_padded_texture_size(img.width, img.height, *block_size, gobs_per_block) + switch_info = (block_size, gobs_per_block) if compress_func: width, height = get_compressed_image_size(width, height, tex_format) img = pad_image(img, width, height) - enc_img = compress_func(img.tobytes("raw", "RGBA"), img.width, img.height, tex_format) + enc_img = compress_func(img.tobytes("raw", "RGBA"), width, height, tex_format) else: - if switch_info is not None: + if switch_info: img = pad_image(img, width, height) enc_img = img.tobytes("raw", pil_mode) - if switch_info is not None: - block_size, gobsPerBlock = switch_info - enc_img = bytes(TextureSwizzler.swizzle(enc_img, width, height, *block_size, gobsPerBlock)) + if switch_info: + block_size, gobs_per_block = switch_info + enc_img = bytes(TextureSwizzler.swizzle(enc_img, width, height, *block_size, gobs_per_block)) return enc_img, tex_format @@ -285,17 +278,16 @@ def parse_image_data( original_width, original_height = (width, height) if TextureSwizzler.is_switch_swizzled(platform, platform_blob): - if TYPE_CHECKING: - # due to earlier check platform_blob can't be None - assert platform_blob is not None - gobsPerBlock = TextureSwizzler.get_switch_gobs_per_block(platform_blob) if texture_format == TF.RGB24: texture_format = TF.RGBA32 elif texture_format == TF.BGR24: texture_format = TF.BGRA32 + + assert platform_blob is not None + gobs_per_block = TextureSwizzler.get_switch_gobs_per_block(platform_blob) block_size = TextureSwizzler.TEXTUREFORMAT_BLOCK_SIZE_MAP[texture_format] - width, height = TextureSwizzler.get_padded_texture_size(width, height, *block_size, gobsPerBlock) - image_data = TextureSwizzler.deswizzle(image_data, width, height, *block_size, gobsPerBlock) + width, height = TextureSwizzler.get_padded_texture_size(width, height, *block_size, gobs_per_block) + image_data = TextureSwizzler.deswizzle(image_data, width, height, *block_size, gobs_per_block) else: width, height = get_compressed_image_size(width, height, texture_format) From a6ef38cd615610497c395c182416254a21e8f045 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Fri, 6 Jun 2025 20:39:36 +0800 Subject: [PATCH 3/6] chore!(TextureSwizzler): rename `TEXTURE_FORMAT_BLOCK_SIZE_MAP` --- UnityPy/export/Texture2DConverter.py | 4 ++-- UnityPy/helpers/TextureSwizzler.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/UnityPy/export/Texture2DConverter.py b/UnityPy/export/Texture2DConverter.py index dabf498c..4620096c 100644 --- a/UnityPy/export/Texture2DConverter.py +++ b/UnityPy/export/Texture2DConverter.py @@ -214,7 +214,7 @@ def image_to_texture2d( assert platform_blob is not None gobs_per_block = TextureSwizzler.get_switch_gobs_per_block(platform_blob) - block_size = TextureSwizzler.TEXTUREFORMAT_BLOCK_SIZE_MAP[s_tex_format] + block_size = TextureSwizzler.TEXTURE_FORMAT_BLOCK_SIZE_MAP[s_tex_format] width, height = TextureSwizzler.get_padded_texture_size(img.width, img.height, *block_size, gobs_per_block) switch_info = (block_size, gobs_per_block) @@ -285,7 +285,7 @@ def parse_image_data( assert platform_blob is not None gobs_per_block = TextureSwizzler.get_switch_gobs_per_block(platform_blob) - block_size = TextureSwizzler.TEXTUREFORMAT_BLOCK_SIZE_MAP[texture_format] + block_size = TextureSwizzler.TEXTURE_FORMAT_BLOCK_SIZE_MAP[texture_format] width, height = TextureSwizzler.get_padded_texture_size(width, height, *block_size, gobs_per_block) image_data = TextureSwizzler.deswizzle(image_data, width, height, *block_size, gobs_per_block) else: diff --git a/UnityPy/helpers/TextureSwizzler.py b/UnityPy/helpers/TextureSwizzler.py index cb225fdb..fb3edd2b 100644 --- a/UnityPy/helpers/TextureSwizzler.py +++ b/UnityPy/helpers/TextureSwizzler.py @@ -70,7 +70,7 @@ def swizzle( # this should be the amount of pixels that can fit 16 bytes -TEXTUREFORMAT_BLOCK_SIZE_MAP: Dict[TextureFormat, Tuple[int, int]] = { +TEXTURE_FORMAT_BLOCK_SIZE_MAP: Dict[TextureFormat, Tuple[int, int]] = { TextureFormat.Alpha8: (16, 1), # 1 byte per pixel TextureFormat.ARGB4444: (8, 1), # 2 bytes per pixel TextureFormat.RGBA32: (4, 1), # 4 bytes per pixel From 7ae6d8d3eec5f04c8ab8a2958ef45b5523a21f91 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Fri, 6 Jun 2025 20:40:30 +0800 Subject: [PATCH 4/6] chore(pyproject.toml): fix typo --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 125db3b7..98a83e82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ keywords = [ "unity", "unity-asset", "python3", - "data-minig", + "data-mining", "unitypack", "assetstudio", "unity-asset-extractor", @@ -43,9 +43,9 @@ dependencies = [ "brotli", # WebFile compression # Texture & Sprite handling "Pillow", - "texture2ddecoder >= 1.0.5", # texture decompression - "etcpak", # ETC & DXT compression - "astc-encoder-py >= 0.1.8", # ASTC compression + "texture2ddecoder >= 1.0.5", # texture decompression + "etcpak", # ETC & DXT compression + "astc-encoder-py >= 0.1.8", # ASTC compression # audio extraction "pyfmodex >= 0.7.1", # filesystem handling From 647f7d8a7586c3eaa76850d268fc0d3fc057263e Mon Sep 17 00:00:00 2001 From: Rudolf Kolbe Date: Sat, 14 Jun 2025 21:45:15 +0200 Subject: [PATCH 5/6] chore(ruff): fix accidently dropped format related commit --- UnityPy/export/Texture2DConverter.py | 16 ++++++++++++---- UnityPy/helpers/TextureSwizzler.py | 5 ++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/UnityPy/export/Texture2DConverter.py b/UnityPy/export/Texture2DConverter.py index 4620096c..5726428a 100644 --- a/UnityPy/export/Texture2DConverter.py +++ b/UnityPy/export/Texture2DConverter.py @@ -215,7 +215,9 @@ def image_to_texture2d( assert platform_blob is not None gobs_per_block = TextureSwizzler.get_switch_gobs_per_block(platform_blob) block_size = TextureSwizzler.TEXTURE_FORMAT_BLOCK_SIZE_MAP[s_tex_format] - width, height = TextureSwizzler.get_padded_texture_size(img.width, img.height, *block_size, gobs_per_block) + width, height = TextureSwizzler.get_padded_texture_size( + img.width, img.height, *block_size, gobs_per_block + ) switch_info = (block_size, gobs_per_block) if compress_func: @@ -230,7 +232,9 @@ def image_to_texture2d( if switch_info: block_size, gobs_per_block = switch_info - enc_img = bytes(TextureSwizzler.swizzle(enc_img, width, height, *block_size, gobs_per_block)) + enc_img = bytes( + TextureSwizzler.swizzle(enc_img, width, height, *block_size, gobs_per_block) + ) return enc_img, tex_format @@ -286,8 +290,12 @@ def parse_image_data( assert platform_blob is not None gobs_per_block = TextureSwizzler.get_switch_gobs_per_block(platform_blob) block_size = TextureSwizzler.TEXTURE_FORMAT_BLOCK_SIZE_MAP[texture_format] - width, height = TextureSwizzler.get_padded_texture_size(width, height, *block_size, gobs_per_block) - image_data = TextureSwizzler.deswizzle(image_data, width, height, *block_size, gobs_per_block) + width, height = TextureSwizzler.get_padded_texture_size( + width, height, *block_size, gobs_per_block + ) + image_data = TextureSwizzler.deswizzle( + image_data, width, height, *block_size, gobs_per_block + ) else: width, height = get_compressed_image_size(width, height, texture_format) diff --git a/UnityPy/helpers/TextureSwizzler.py b/UnityPy/helpers/TextureSwizzler.py index fb3edd2b..226f7e69 100644 --- a/UnityPy/helpers/TextureSwizzler.py +++ b/UnityPy/helpers/TextureSwizzler.py @@ -1,5 +1,6 @@ # based on https://github.com/nesrak1/AssetsTools.NET/blob/dev/AssetsTools.NET.Texture/Swizzle/SwitchSwizzle.cs from typing import Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union from ..enums import BuildTarget, TextureFormat @@ -118,7 +119,9 @@ def get_switch_gobs_per_block(platform_blob: List[int]) -> int: return 1 << int.from_bytes(platform_blob[8:12], "little") -def is_switch_swizzled(platform: Union[BuildTarget, int], platform_blob: Optional[List[int]]) -> bool: +def is_switch_swizzled( + platform: Union[BuildTarget, int], platform_blob: Optional[List[int]] +) -> bool: if platform != BuildTarget.Switch: return False if not platform_blob or len(platform_blob) < 12: From f74797f27e47c42a5308f10b7874cfbfb1f0b732 Mon Sep 17 00:00:00 2001 From: Rudolf Kolbe Date: Sat, 14 Jun 2025 21:46:25 +0200 Subject: [PATCH 6/6] chore(Texture2DConverter): only bytes cast image_data if not already bytes --- UnityPy/export/Texture2DConverter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/UnityPy/export/Texture2DConverter.py b/UnityPy/export/Texture2DConverter.py index 5726428a..4ed96e7a 100644 --- a/UnityPy/export/Texture2DConverter.py +++ b/UnityPy/export/Texture2DConverter.py @@ -299,7 +299,9 @@ def parse_image_data( else: width, height = get_compressed_image_size(width, height, texture_format) - image_data = bytes(image_data) + if not isinstance(image_data, bytes): + # bytes(bytes item) would cause an unnecessary copy + image_data = bytes(image_data) if "Crunched" in texture_format.name: if (