Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 42 additions & 47 deletions UnityPy/export/Texture2DConverter.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import annotations

import struct
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

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on windows-latest

Ruff (F811)

UnityPy\export\Texture2DConverter.py:7:79: F811 Redefinition of unused `Union` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on windows-latest

Ruff (F811)

UnityPy\export\Texture2DConverter.py:7:72: F811 Redefinition of unused `Tuple` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on windows-latest

Ruff (F811)

UnityPy\export\Texture2DConverter.py:7:62: F811 Redefinition of unused `Optional` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on windows-latest

Ruff (F811)

UnityPy\export\Texture2DConverter.py:7:56: F811 Redefinition of unused `List` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on windows-latest

Ruff (F811)

UnityPy\export\Texture2DConverter.py:7:50: F811 Redefinition of unused `Dict` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on windows-latest

Ruff (F811)

UnityPy\export\Texture2DConverter.py:7:40: F811 Redefinition of unused `Callable` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on windows-latest

Ruff (F811)

UnityPy\export\Texture2DConverter.py:7:35: F811 Redefinition of unused `Any` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on windows-latest

Ruff (F811)

UnityPy\export\Texture2DConverter.py:7:20: F811 Redefinition of unused `TYPE_CHECKING` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on ubuntu-latest

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:79: F811 Redefinition of unused `Union` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on ubuntu-latest

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:72: F811 Redefinition of unused `Tuple` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on ubuntu-latest

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:62: F811 Redefinition of unused `Optional` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on ubuntu-latest

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:56: F811 Redefinition of unused `List` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on ubuntu-latest

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:50: F811 Redefinition of unused `Dict` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on ubuntu-latest

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:40: F811 Redefinition of unused `Callable` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on ubuntu-latest

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:35: F811 Redefinition of unused `Any` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on ubuntu-latest

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:20: F811 Redefinition of unused `TYPE_CHECKING` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on macos-13

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:79: F811 Redefinition of unused `Union` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on macos-13

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:72: F811 Redefinition of unused `Tuple` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on macos-13

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:62: F811 Redefinition of unused `Optional` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on macos-13

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:56: F811 Redefinition of unused `List` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on macos-13

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:50: F811 Redefinition of unused `Dict` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on macos-13

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:40: F811 Redefinition of unused `Callable` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on macos-13

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:35: F811 Redefinition of unused `Any` from line 6

Check failure on line 7 in UnityPy/export/Texture2DConverter.py

View workflow job for this annotation

GitHub Actions / Build wheels on macos-13

Ruff (F811)

UnityPy/export/Texture2DConverter.py:7:20: F811 Redefinition of unused `TYPE_CHECKING` from line 6

import astc_encoder
import texture2ddecoder
Expand Down Expand Up @@ -117,11 +118,12 @@

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)

Expand All @@ -133,21 +135,15 @@
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"):
Expand Down Expand Up @@ -178,7 +174,7 @@
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,
Expand Down Expand Up @@ -208,34 +204,37 @@
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
pil_mode = "RGBA"
elif tex_format == TF.BGR24:
s_tex_format = TF.BGRA32
pil_mode = "BGRA"
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)

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
)
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

Expand All @@ -244,15 +243,7 @@
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,
Expand All @@ -269,12 +260,13 @@
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))

Expand All @@ -290,21 +282,26 @@
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
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)

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
)
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 (
Expand Down Expand Up @@ -333,9 +330,7 @@


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]
Expand Down
7 changes: 5 additions & 2 deletions UnityPy/helpers/TextureSwizzler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# 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

Check failure on line 5 in UnityPy/helpers/TextureSwizzler.py

View workflow job for this annotation

GitHub Actions / Build wheels on windows-latest

Ruff (I001)

UnityPy\helpers\TextureSwizzler.py:2:1: I001 Import block is un-sorted or un-formatted

Check failure on line 5 in UnityPy/helpers/TextureSwizzler.py

View workflow job for this annotation

GitHub Actions / Build wheels on ubuntu-latest

Ruff (I001)

UnityPy/helpers/TextureSwizzler.py:2:1: I001 Import block is un-sorted or un-formatted

Check failure on line 5 in UnityPy/helpers/TextureSwizzler.py

View workflow job for this annotation

GitHub Actions / Build wheels on macos-13

Ruff (I001)

UnityPy/helpers/TextureSwizzler.py:2:1: I001 Import block is un-sorted or un-formatted

GOB_X_TEXEL_COUNT = 4
GOB_Y_TEXEL_COUNT = 8
Expand Down Expand Up @@ -70,7 +71,7 @@


# 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
Expand Down Expand Up @@ -118,7 +119,9 @@
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:
Expand Down
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ keywords = [
"unity",
"unity-asset",
"python3",
"data-minig",
"data-mining",
"unitypack",
"assetstudio",
"unity-asset-extractor",
Expand Down Expand Up @@ -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
Expand Down
Loading