Skip to content

Commit

Permalink
1.7.18 - "correct" Texture2D format selection for replacement & DXT/E…
Browse files Browse the repository at this point in the history
…TC compression added
  • Loading branch information
K0lb3 committed Oct 19, 2021
1 parent ed883be commit 0bb34ed
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 33 deletions.
2 changes: 1 addition & 1 deletion UnityPy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "1.7.17"
__version__ = "1.7.18"

from .environment import Environment

Expand Down
33 changes: 22 additions & 11 deletions UnityPy/classes/Texture2D.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,31 @@ def image(self, img):
if img is None:
raise Exception("No image provided")

if (isinstance(img, str) or isinstance(img, BufferedIOBase) or
isinstance(img, RawIOBase) or isinstance(img, IOBase)):
if (
isinstance(img, str)
or isinstance(img, BufferedIOBase)
or isinstance(img, RawIOBase)
or isinstance(img, IOBase)
):
img = Image.open(img)

img_data, tex_format = Texture2DConverter.image_to_texture2d(img)
img_data, tex_format = Texture2DConverter.image_to_texture2d(
img, self.m_TextureFormat
)
self.image_data = img_data
# width * height * channel count
self.m_CompleteImageSize = len(self._image_data)# img.width * img.height * len(img.getbands())
self.m_CompleteImageSize = len(
self._image_data
) # img.width * img.height * len(img.getbands())
self.m_TextureFormat = tex_format

@property
def image_data(self):
return self._image_data

def reset_streamdata(self):
if not self.m_StreamData: return
if not self.m_StreamData:
return
self.m_StreamData.offset = 0
self.m_StreamData.size = 0
self.m_StreamData.path = ""
Expand All @@ -43,7 +52,7 @@ def reset_streamdata(self):
def image_data(self, data: bytes):
self._image_data = data
# prefer writing to cab if possible
if self.m_StreamData:
if self.version >= (5, 3) and self.m_StreamData.path:
cab = self.assets_file.get_writeable_cab()
if cab:
self.m_StreamData.offset = cab.Position
Expand All @@ -53,20 +62,22 @@ def image_data(self, data: bytes):
else:
self.reset_streamdata()

def set_image(
self, img, target_format: TextureFormat = None, in_cab: bool = False
):
def set_image(self, img, target_format: TextureFormat = None, in_cab: bool = False):
if img is None:
raise Exception("No image provided")
img_data, tex_format = Texture2DConverter.image_to_texture2d(img)
if not target_format:
target_format = self.m_TextureFormat
img_data, tex_format = Texture2DConverter.image_to_texture2d(img, target_format)

if in_cab:
self.image_data = img_data
else:
self._image_data = img_data
self.reset_streamdata()
# width * height * channel count
self.m_CompleteImageSize = len(self._image_data)#img.width * img.height * len(img.getbands())
self.m_CompleteImageSize = len(
self._image_data
) # img.width * img.height * len(img.getbands())
self.m_TextureFormat = tex_format

def __init__(self, reader):
Expand Down
110 changes: 89 additions & 21 deletions UnityPy/export/Texture2DConverter.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import texture2ddecoder
import etcpak
from PIL import Image
from copy import copy
from io import BytesIO
Expand All @@ -8,21 +9,70 @@
TF = TextureFormat


def image_to_texture2d(img: Image, flip: bool = True):
# tex for eventually later usage of compressions

tex_format = None
def image_to_texture2d(img: Image, target_texture_format: TF, flip: bool = True):
if flip:
img = img.transpose(Image.FLIP_TOP_BOTTOM)

if img.mode == "RGBA":
tex_format = TextureFormat.RGBA32
elif img.mode == "RGB":
tex_format = TextureFormat.RGB24
elif img.mode == "A":
tex_format = TextureFormat.Alpha8
# DXT
if target_texture_format in [TF.DXT1, TF.DXT1Crunched]:
raw_img = img.convert("RGBA").tobytes()
enc_img = etcpak.compress_to_dxt1(raw_img, img.width, img.height)
tex_format = TF.DXT1
elif target_texture_format in [TF.DXT5, TF.DXT5Crunched]:
raw_img = img.convert("RGBA").tobytes()
enc_img = etcpak.compress_to_dxt5(raw_img, img.width, img.height)
tex_format = TF.DXT5
# ETC
elif target_texture_format in [TF.ETC_RGB4, TF.ETC_RGB4Crunched, TF.ETC_RGB4_3DS]:
r, g, b, a = img.split()
raw_img = Image.merge("RGBA", (b, g, r, a)).tobytes()
enc_img = etcpak.compress_to_etc1(raw_img, img.width, img.height)
tex_format = TF.ETC_RGB4
elif target_texture_format == TF.ETC2_RGB:
r, g, b, a = img.split()
raw_img = Image.merge("RGBA", (b, g, r, a)).tobytes()
enc_img = etcpak.compress_to_etc2_rgb(raw_img, img.width, img.height)
tex_format = TF.ETC2_RGB
elif (
target_texture_format in [TF.ETC2_RGBA8, TF.ETC2_RGBA8Crunched, TF.ETC2_RGBA1]
or "_RGB_" in target_texture_format.name
):
r, g, b, a = img.split()
raw_img = Image.merge("RGBA", (b, g, r, a)).tobytes()
enc_img = etcpak.compress_to_etc2_rgba(raw_img, img.width, img.height)
tex_format = TF.ETC2_RGBA8
# A
elif target_texture_format == TF.Alpha8:
enc_img = Image.tobytes("raw", "A")
tex_format = TF.Alpha8
# R
elif target_texture_format in [
TF.R8,
TF.R16,
TF.RHalf,
TF.RFloat,
TF.EAC_R,
TF.EAC_R_SIGNED,
]:
enc_img = Image.tobytes("raw", "R")
tex_format = TF.R8
# RGBA
elif target_texture_format in [
TF.RGB565,
TF.RGB24,
TF.RGB9e5Float,
TF.PVRTC_RGB2,
TF.PVRTC_RGB4,
TF.ATC_RGB4,
]:
enc_img = Image.tobytes("raw", "RGB")
tex_format = TF.RGB24
# everything else defaulted to RGBA
else:
enc_img = img.tobytes("raw", "RGBA")
tex_format = TF.RGBA32

return img.tobytes(), tex_format
return enc_img, tex_format


def get_image_from_texture2d(texture_2d, flip=True) -> Image:
Expand All @@ -38,9 +88,12 @@ def get_image_from_texture2d(texture_2d, flip=True) -> Image:
image_data = copy(bytes(texture_2d.image_data))
if not image_data:
return Image.new("RGB", (0, 0))

texture_format = texture_2d.m_TextureFormat if isinstance(texture_2d, TextureFormat) else TextureFormat(
texture_2d.m_TextureFormat)

texture_format = (
texture_2d.m_TextureFormat
if isinstance(texture_2d.m_TextureFormat, TF)
else TF(texture_2d.m_TextureFormat)
)
selection = CONV_TABLE[texture_format]

if len(selection) == 0:
Expand All @@ -53,9 +106,12 @@ def get_image_from_texture2d(texture_2d, flip=True) -> Image:

if "Crunched" in texture_format.name:
version = texture_2d.version
if (version[0] > 2017 or (version[0] == 2017 and version[1] >= 3) # 2017.3 and up
or texture_format == TextureFormat.ETC_RGB4Crunched
or texture_format == TextureFormat.ETC2_RGBA8Crunched):
if (
version[0] > 2017
or (version[0] == 2017 and version[1] >= 3) # 2017.3 and up
or texture_format == TF.ETC_RGB4Crunched
or texture_format == TF.ETC2_RGBA8Crunched
):
image_data = texture2ddecoder.unpack_unity_crunch(image_data)
else:
image_data = texture2ddecoder.unpack_crunch(image_data)
Expand All @@ -81,15 +137,21 @@ def swap_bytes_for_xbox(image_data: bytes, build_target: BuildTarget) -> bytes:
:rtype: bytes
"""
if (
build_target == BuildTarget.XBOX360
build_target == BuildTarget.XBOX360
): # swap bytes for Xbox confirmed,PS3 not encountered
for i in range(0, len(image_data), 2):
image_data[i: i + 2] = image_data[i: i + 2][::-1]
image_data[i : i + 2] = image_data[i : i + 2][::-1]
return image_data


def pillow(
image_data: bytes, width: int, height: int, mode: str, codec: str, args, swap: tuple = None
image_data: bytes,
width: int,
height: int,
mode: str,
codec: str,
args,
swap: tuple = None,
) -> Image:
img = (
Image.frombytes(mode, (width, height), image_data, codec, args)
Expand Down Expand Up @@ -149,7 +211,13 @@ def eac(image_data: bytes, width: int, height: int, fmt: list):


def half(
image_data: bytes, width: int, height: int, mode: str, codec: str, args, swap: tuple = None
image_data: bytes,
width: int,
height: int,
mode: str,
codec: str,
args,
swap: tuple = None,
) -> Image:
# convert half-float to int8
stream = BytesIO(image_data)
Expand Down

0 comments on commit 0bb34ed

Please sign in to comment.