Skip to content

Commit 4e3ebc8

Browse files
committed
refactor: Texture2DSwizzler - wrap methods
1 parent 80bb79d commit 4e3ebc8

2 files changed

Lines changed: 116 additions & 70 deletions

File tree

UnityPy/export/Texture2DConverter.py

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import struct
44
from io import BytesIO
55
from threading import Lock
6-
from typing import TYPE_CHECKING, Callable, Dict, Optional, Tuple, Union
6+
from typing import TYPE_CHECKING, Callable, Dict, Optional, Sequence, Tuple, Union
77

88
import astc_encoder
99
import texture2ddecoder
@@ -17,6 +17,9 @@
1717
from ..classes import Texture2D
1818

1919

20+
PlatformBlobType = Union[bytes, Sequence[int]]
21+
22+
2023
TEXTURE_FORMAT_BLOCK_SIZE_TABLE: Dict[TF, Optional[Tuple[int, int]]] = {}
2124
for tf in TF:
2225
if tf.name.startswith("ASTC"):
@@ -134,7 +137,7 @@ def image_to_texture2d(
134137
img: Image.Image,
135138
target_texture_format: Union[TF, int],
136139
platform: int = 0,
137-
platform_blob: Optional[bytes] = None,
140+
platform_blob: Optional[PlatformBlobType] = None,
138141
flip: bool = True,
139142
) -> Tuple[bytes, TF]:
140143
if not isinstance(target_texture_format, TF):
@@ -216,27 +219,18 @@ def image_to_texture2d(
216219
pil_mode = "RGB"
217220
# everything else defaulted to RGBA
218221

219-
switch_swizzle = None
220-
if platform == BuildTarget.Switch and platform_blob:
221-
gobs_per_block = TextureSwizzler.get_switch_gobs_per_block(platform_blob)
222-
222+
if TextureSwizzler.is_switch_swizzled(platform, platform_blob):
223+
assert platform_blob is not None
223224
if texture_format == TF.RGB24:
224225
texture_format = TF.RGBA32
225226
pil_mode = "RGBA"
226227
elif texture_format == TF.BGR24:
227228
texture_format = TF.BGRA32
228229
pil_mode = "BGRA"
229230

230-
block_size = TextureSwizzler.TEXTUREFORMAT_BLOCK_SIZE_MAP.get(texture_format)
231-
if not block_size:
232-
raise NotImplementedError(
233-
f"Not implemented swizzle format: {texture_format.name}"
234-
)
235-
236-
width, height = TextureSwizzler.get_padded_texture_size(
237-
img.width, img.height, *block_size, gobs_per_block
231+
width, height = TextureSwizzler.get_padded_image_size(
232+
img.width, img.height, texture_format, platform_blob
238233
)
239-
switch_swizzle = (block_size, gobs_per_block)
240234
else:
241235
width, height = get_compressed_image_size(img.width, img.height, texture_format)
242236

@@ -248,9 +242,11 @@ def image_to_texture2d(
248242
else:
249243
enc_img = img.tobytes("raw", pil_mode)
250244

251-
if switch_swizzle is not None:
252-
block_size, gobs_per_block = switch_swizzle
253-
enc_img = TextureSwizzler.swizzle(enc_img, width, height, *block_size, gobs_per_block)
245+
if TextureSwizzler.is_switch_swizzled(platform, platform_blob):
246+
assert platform_blob is not None
247+
enc_img = TextureSwizzler.swizzle(
248+
enc_img, width, height, texture_format, platform_blob
249+
)
254250

255251
return enc_img, texture_format
256252

@@ -285,10 +281,10 @@ def parse_image_data(
285281
image_data: bytes,
286282
width: int,
287283
height: int,
288-
texture_format: Union[int, TF],
284+
texture_format: Union[TF, int],
289285
version: Tuple[int, int, int, int],
290286
platform: int,
291-
platform_blob: Optional[bytes] = None,
287+
platform_blob: Optional[PlatformBlobType] = None,
292288
flip: bool = True,
293289
) -> Image.Image:
294290
if not width or not height:
@@ -304,30 +300,19 @@ def parse_image_data(
304300
if platform == BuildTarget.XBOX360 and texture_format in XBOX_SWAP_FORMATS:
305301
image_data = swap_bytes_for_xbox(image_data)
306302

307-
original_width, original_height = width, height
308-
switch_swizzle = None
309-
if platform == BuildTarget.Switch and platform_blob:
310-
gobs_per_block = TextureSwizzler.get_switch_gobs_per_block(platform_blob)
303+
ori_width, ori_height = width, height
311304

312-
pil_mode = "RGBA"
305+
if TextureSwizzler.is_switch_swizzled(platform, platform_blob):
306+
assert platform_blob is not None
313307
if texture_format == TF.RGB24:
314308
texture_format = TF.RGBA32
315309
elif texture_format == TF.BGR24:
316310
texture_format = TF.BGRA32
317-
pil_mode = "BGRA"
318-
elif texture_format == TF.Alpha8:
319-
pil_mode = "L"
320-
321-
block_size = TextureSwizzler.TEXTUREFORMAT_BLOCK_SIZE_MAP.get(texture_format)
322-
if not block_size:
323-
raise NotImplementedError(
324-
f"Not implemented swizzle format: {texture_format.name}"
325-
)
326311

327-
width, height = TextureSwizzler.get_padded_texture_size(
328-
width, height, *block_size, gobs_per_block
312+
width, height = TextureSwizzler.get_padded_image_size(
313+
width, height, texture_format, platform_blob
329314
)
330-
switch_swizzle = (block_size, gobs_per_block, pil_mode)
315+
image_data = TextureSwizzler.deswizzle(image_data, width, height, texture_format, platform_blob)
331316
else:
332317
width, height = get_compressed_image_size(width, height, texture_format)
333318

@@ -342,20 +327,15 @@ def parse_image_data(
342327
else:
343328
image_data = texture2ddecoder.unpack_crunch(image_data)
344329

345-
if switch_swizzle is not None:
346-
block_size, gobs_per_block, pil_mode = switch_swizzle
347-
image_data = TextureSwizzler.deswizzle(image_data, width, height, *block_size, gobs_per_block)
348-
349-
350330
conv_func = CONV_TABLE.get(texture_format)
351331
if not conv_func:
352332
raise NotImplementedError(
353333
f"Not implemented texture format: {texture_format.name}"
354334
)
355335
img = conv_func(image_data, width, height)
356336

357-
if original_width != width or original_height != height:
358-
img = img.crop((0, 0, original_width, original_height))
337+
if ori_width != width or ori_height != height:
338+
img = img.crop((0, 0, ori_width, ori_height))
359339

360340
return img.transpose(Image.FLIP_TOP_BOTTOM) if flip else img
361341

UnityPy/helpers/TextureSwizzler.py

Lines changed: 92 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# based on https://github.com/nesrak1/UABEA/blob/master/TexturePlugin/Texture2DSwitchDeswizzler.cs
2-
from typing import Dict, Tuple
2+
from typing import Dict, Optional, Sequence, Tuple, Union
3+
4+
from UnityPy.enums import BuildTarget
35

46
from ..enums import TextureFormat as TF
57

8+
9+
PlatformBlobType = Union[bytes, Sequence[int]]
10+
11+
612
GOB_X_TEXEL_COUNT = 4
713
GOB_Y_TEXEL_COUNT = 8
814
TEXEL_BYTE_SIZE = 16
@@ -13,20 +19,20 @@
1319
]
1420

1521

16-
def ceil_divide(a: int, b: int) -> int:
22+
def _ceil_divide(a: int, b: int) -> int:
1723
return (a + b - 1) // b
1824

1925

20-
def deswizzle(
26+
def _deswizzle(
2127
data: bytes,
2228
width: int,
2329
height: int,
2430
block_width: int,
2531
block_height: int,
2632
texels_per_block: int,
2733
) -> bytes:
28-
block_count_x = ceil_divide(width, block_width)
29-
block_count_y = ceil_divide(height, block_height)
34+
block_count_x = _ceil_divide(width, block_width)
35+
block_count_y = _ceil_divide(height, block_height)
3036
gob_count_x = block_count_x // GOB_X_TEXEL_COUNT
3137
gob_count_y = block_count_y // GOB_Y_TEXEL_COUNT
3238
new_data = bytearray(len(data))
@@ -50,16 +56,16 @@ def deswizzle(
5056
return bytes(new_data)
5157

5258

53-
def swizzle(
59+
def _swizzle(
5460
data: bytes,
5561
width: int,
5662
height: int,
5763
block_width: int,
5864
block_height: int,
5965
texels_per_block: int,
6066
) -> bytes:
61-
block_count_x = ceil_divide(width, block_width)
62-
block_count_y = ceil_divide(height, block_height)
67+
block_count_x = _ceil_divide(width, block_width)
68+
block_count_y = _ceil_divide(height, block_height)
6369
gob_count_x = block_count_x // GOB_X_TEXEL_COUNT
6470
gob_count_y = block_count_y // GOB_Y_TEXEL_COUNT
6571
new_data = bytearray(len(data))
@@ -83,8 +89,31 @@ def swizzle(
8389
return bytes(new_data)
8490

8591

92+
def _get_padded_texture_size(
93+
width: int, height: int, block_width: int, block_height: int, texels_per_block: int
94+
) -> Tuple[int, int]:
95+
width = (
96+
_ceil_divide(width, block_width * GOB_X_TEXEL_COUNT)
97+
* block_width
98+
* GOB_X_TEXEL_COUNT
99+
)
100+
height = (
101+
_ceil_divide(height, block_height * GOB_Y_TEXEL_COUNT * texels_per_block)
102+
* block_height
103+
* GOB_Y_TEXEL_COUNT
104+
* texels_per_block
105+
)
106+
return width, height
107+
108+
109+
def _get_texels_per_block(platform_blob: PlatformBlobType) -> int:
110+
if not platform_blob:
111+
raise ValueError("Given platform_blob is empty")
112+
return 1 << int.from_bytes(platform_blob[8:12], "little")
113+
114+
86115
# this should be the amount of pixels that can fit 16 bytes
87-
TEXTUREFORMAT_BLOCK_SIZE_MAP: Dict[TF, Tuple[int, int]] = {
116+
TEXTURE_FORMAT_BLOCK_SIZE_MAP: Dict[TF, Tuple[int, int]] = {
88117
TF.Alpha8: (16, 1), # 1 byte per pixel
89118
TF.ARGB4444: (8, 1), # 2 bytes per pixel
90119
TF.RGBA32: (4, 1), # 4 bytes per pixel
@@ -117,22 +146,59 @@ def swizzle(
117146
}
118147

119148

120-
def get_padded_texture_size(
121-
width: int, height: int, block_width: int, block_height: int, texels_per_block: int
122-
):
123-
width = (
124-
ceil_divide(width, block_width * GOB_X_TEXEL_COUNT)
125-
* block_width
126-
* GOB_X_TEXEL_COUNT
127-
)
128-
height = (
129-
ceil_divide(height, block_height * GOB_Y_TEXEL_COUNT * texels_per_block)
130-
* block_height
131-
* GOB_Y_TEXEL_COUNT
132-
* texels_per_block
133-
)
134-
return width, height
149+
def deswizzle(
150+
data: bytes,
151+
width: int,
152+
height: int,
153+
texture_format: TF,
154+
platform_blob: PlatformBlobType,
155+
) -> bytes:
156+
block_size = TEXTURE_FORMAT_BLOCK_SIZE_MAP.get(texture_format)
157+
if not block_size:
158+
raise NotImplementedError(
159+
f"Not implemented swizzle format: {texture_format.name}"
160+
)
161+
texels_per_block = _get_texels_per_block(platform_blob)
162+
return _deswizzle(data, width, height, *block_size, texels_per_block)
135163

136164

137-
def get_switch_gobs_per_block(platform_blob: bytes) -> int:
138-
return 1 << int.from_bytes(platform_blob[8:12], "little")
165+
def swizzle(
166+
data: bytes,
167+
width: int,
168+
height: int,
169+
texture_format: TF,
170+
platform_blob: PlatformBlobType,
171+
) -> bytes:
172+
block_size = TEXTURE_FORMAT_BLOCK_SIZE_MAP.get(texture_format)
173+
if not block_size:
174+
raise NotImplementedError(
175+
f"Not implemented swizzle format: {texture_format.name}"
176+
)
177+
texels_per_block = _get_texels_per_block(platform_blob)
178+
return _swizzle(data, width, height, *block_size, texels_per_block)
179+
180+
181+
def get_padded_image_size(
182+
width: int,
183+
height: int,
184+
texture_format: TF,
185+
platform_blob: PlatformBlobType,
186+
):
187+
block_size = TEXTURE_FORMAT_BLOCK_SIZE_MAP.get(texture_format)
188+
if not block_size:
189+
raise NotImplementedError(
190+
f"Not implemented swizzle format: {texture_format.name}"
191+
)
192+
texels_per_block = _get_texels_per_block(platform_blob)
193+
return _get_padded_texture_size(width, height, *block_size, texels_per_block)
194+
195+
196+
def is_switch_swizzled(
197+
platform: Union[BuildTarget, int], platform_blob: Optional[PlatformBlobType] = None
198+
) -> bool:
199+
if platform != BuildTarget.Switch:
200+
return False
201+
if not platform_blob or len(platform_blob) < 12:
202+
return False
203+
gobs_per_block = _get_texels_per_block(platform_blob)
204+
return gobs_per_block > 1

0 commit comments

Comments
 (0)