Skip to content
Open
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
2 changes: 1 addition & 1 deletion UnityPy/classes/legacy_patch/Texture2D.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def _Texture2d_set_image(
def _Texture2D_get_image_data(self: Texture2D):
if self.image_data:
return self.image_data
if self.m_StreamData:
if self.m_StreamData and self.m_StreamData.path:
from ...helpers.ResourceReader import get_resource_data

return get_resource_data(
Expand Down
29 changes: 19 additions & 10 deletions UnityPy/classes/legacy_patch/Texture2DArray.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,30 @@
from PIL import Image


def _Texture2DArray_get_image_data(self: Texture2DArray):
if self.image_data:
return self.image_data
if self.m_StreamData and self.m_StreamData.path:
from ...helpers.ResourceReader import get_resource_data

return get_resource_data(
self.m_StreamData.path,
self.object_reader.assets_file,
self.m_StreamData.offset,
self.m_StreamData.size,
)
raise ValueError("No image data found")


def _Texture2DArray_get_images(self: Texture2DArray) -> List[Image.Image]:
from ...enums import BuildTarget
from ...export import Texture2DConverter
from ...helpers.ResourceReader import get_resource_data

texture_format = GRAPHICS_TO_TEXTURE_MAP.get(GraphicsFormat(self.m_Format))
if not texture_format:
raise NotImplementedError(f"GraphicsFormat {self.m_Format} not supported yet")

image_data = self.image_data
if image_data is None:
image_data = get_resource_data(
self.m_StreamData.path,
self.object_reader.assets_file,
self.m_StreamData.offset,
self.m_StreamData.size,
)
image_data = self.get_image_data()

# calculate the number of textures in the array
texture_size = self.m_DataSize // self.m_Depth
Expand All @@ -36,13 +44,14 @@ def _Texture2DArray_get_images(self: Texture2DArray) -> List[Image.Image]:
self.m_Height,
texture_format,
self.object_reader.version,
0,
getattr(self.object_reader, "platform", BuildTarget.UnknownPlatform),
None,
)
for offset in range(0, self.m_DataSize, texture_size)
]


Texture2DArray.images = property(_Texture2DArray_get_images)
Texture2DArray.get_image_data = _Texture2DArray_get_image_data

__all__ = ("Texture2DArray",)
2 changes: 2 additions & 0 deletions UnityPy/export/Texture2DConverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ def get_image_from_texture2d(
flip: bool = True,
) -> Image.Image:
"""Converts the given Texture2D object to PIL Image."""
if not texture_2d.m_Width or not texture_2d.m_Height:
return Image.new("RGBA", (0, 0))
return parse_image_data(
texture_2d.get_image_data(),
texture_2d.m_Width,
Expand Down
104 changes: 104 additions & 0 deletions tests/test_texture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from types import SimpleNamespace

import pytest

import UnityPy
from UnityPy.classes.generated import GLTextureSettings, StreamingInfo, Texture2D, Texture2DArray
from UnityPy.enums import TextureFormat
from UnityPy.enums.GraphicsFormat import GraphicsFormat
from UnityPy.streams import EndianBinaryReader

VERSION = (2022, 3, 0, 0)


def object_reader():
environment = UnityPy.Environment()

def load_dependencies(possible_dependencies=None):
raise AssertionError("no dependency should be loaded")

assets_file = SimpleNamespace(environment=environment, load_dependencies=load_dependencies, _cache={})
return SimpleNamespace(assets_file=assets_file, version=VERSION, platform=0)


def texture_settings():
return GLTextureSettings(m_Aniso=1, m_FilterMode=1, m_MipBias=0.0, m_WrapMode=0)


def texture2d(width, height, image_data=b"", stream_path=""):
texture = Texture2D(
image_data=image_data,
m_CompleteImageSize=len(image_data),
m_ForcedFallbackFormat=0,
m_Height=height,
m_ImageCount=1,
m_IsReadable=False,
m_LightmapFormat=0,
m_MipCount=1,
m_Name="texture",
m_TextureDimension=2,
m_TextureFormat=TextureFormat.RGBA32,
m_TextureSettings=texture_settings(),
m_Width=width,
m_StreamData=StreamingInfo(offset=0, path=stream_path, size=0),
)
texture.object_reader = object_reader()
return texture


def test_texture2d_without_size():
assert texture2d(0, 0).image.size == (0, 0)


def test_texture2d_without_image_data():
with pytest.raises(ValueError):
texture2d(2, 2).get_image_data()


ARRAY_LAYERS = [bytes([255, 0, 0, 255, 0, 255, 0, 255]), bytes([0, 0, 255, 255, 255, 255, 255, 255])]
ARRAY_PIXELS = [
[(255, 0, 0, 255), (0, 255, 0, 255)],
[(0, 0, 255, 255), (255, 255, 255, 255)],
]


def texture2darray(image_data=b"", stream_path=""):
data_size = sum(len(layer) for layer in ARRAY_LAYERS)
texture = Texture2DArray(
image_data=image_data,
m_ColorSpace=1,
m_DataSize=data_size,
m_Depth=len(ARRAY_LAYERS),
m_Format=GraphicsFormat.R8G8B8A8_UNorm,
m_Height=1,
m_IsReadable=False,
m_MipCount=1,
m_Name="array",
m_TextureSettings=texture_settings(),
m_Width=2,
m_StreamData=StreamingInfo(offset=4, path=stream_path, size=data_size if stream_path else 0),
)
texture.object_reader = object_reader()
return texture


def array_pixels(texture):
return [[image.getpixel((x, 0)) for x in range(2)] for image in texture.images]


def test_texture2darray_inline():
assert array_pixels(texture2darray(image_data=b"".join(ARRAY_LAYERS))) == ARRAY_PIXELS


def test_texture2darray_streamed():
# streamed arrays serialize an empty image_data and point at a resource file
texture = texture2darray(stream_path="archive:/CAB-array/CAB-array.resS")
resource = EndianBinaryReader(b"\0" * 4 + b"".join(ARRAY_LAYERS))
texture.object_reader.assets_file.environment.register_cab("CAB-array.resS", resource)
assert array_pixels(texture) == ARRAY_PIXELS
assert texture.get_image_data() == b"".join(ARRAY_LAYERS)


def test_texture2darray_without_image_data():
with pytest.raises(ValueError):
texture2darray().get_image_data()
Loading