mirror of
https://github.com/YorhaX2P/NieR2Blender2NieR-PS4.git
synced 2026-08-26 19:43:33 +00:00
Fix NieR Switch texture issues
- Texture issues were due to misunderstanding of .tex headers; should be fixed now? - Added support for special padding (Astral Chain, Bayo 3)
This commit is contained in:
@@ -2,7 +2,7 @@ import subprocess
|
||||
import asyncio
|
||||
import math
|
||||
import json
|
||||
from ...utils.ioUtils import read_int32, write_Int32, write_uInt16, write_float16
|
||||
from ...utils.ioUtils import read_int32, write_Int32, write_uInt32, write_byte
|
||||
from . import generate_wta_wtp_data
|
||||
from .wta_wtp_utils import *
|
||||
from ..tegrax1swizzle import compressImageData, getFormatByIndex
|
||||
@@ -205,17 +205,13 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
info = {
|
||||
"magic": b".tex",
|
||||
"format": 0x7D,
|
||||
"unk1": 1,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"depth": 1,
|
||||
"mipCount": 1,
|
||||
"unk2": 256,
|
||||
"unk3": 0.25,
|
||||
"unk4": 0,
|
||||
# guesses used for swizzling (wtpImportOperator.py)
|
||||
"type": 1,
|
||||
"textureLayout": [4, 0],
|
||||
"blockHeightLog2": 4,
|
||||
"arrayCount": 1
|
||||
}
|
||||
|
||||
@@ -249,8 +245,13 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
unknownArray1[i] = 1677721632 # DDS textures are always SRGB
|
||||
wtaTextureOffset[i] = wtp_fp.tell()
|
||||
|
||||
# Change infos
|
||||
if info["height"] < 256:
|
||||
info["blockHeightLog2"] = 8 & 7
|
||||
if info["height"] < 128:
|
||||
info["blockHeightLog2"] = 16 & 7
|
||||
|
||||
dds_fp.seek(0x80)
|
||||
blockHeightLog2 = info["textureLayout"][0] & 7
|
||||
wtp_fp.write(compressImageData(
|
||||
getFormatByIndex(info['format']),
|
||||
info['width'],
|
||||
@@ -259,7 +260,7 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
info['arrayCount'],
|
||||
info['mipCount'],
|
||||
dds_fp.read(),
|
||||
blockHeightLog2
|
||||
info['blockHeightLog2']
|
||||
))
|
||||
wtaTextureSize[i] = wtp_fp.tell() - wtaTextureOffset[i]
|
||||
if wtaTextureSize[i] < 90112:
|
||||
@@ -277,7 +278,6 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
info["height"] = int.from_bytes(astc_fp.read(3), "little")
|
||||
|
||||
astc_fp.seek(16)
|
||||
blockHeightLog2 = info["textureLayout"][0] & 7
|
||||
wtp_fp.write(compressImageData(
|
||||
getFormatByIndex(info['format']),
|
||||
info['width'],
|
||||
@@ -286,7 +286,7 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
info['arrayCount'],
|
||||
info['mipCount'],
|
||||
astc_fp.read(),
|
||||
blockHeightLog2
|
||||
info['blockHeightLog2']
|
||||
))
|
||||
wtaTextureSize[i] = wtp_fp.tell() - wtaTextureOffset[i]
|
||||
if wtaTextureSize[i] < 90112:
|
||||
@@ -331,17 +331,17 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
wta_fp.write(to_bytes(wtaTextureIdentifier[i]))
|
||||
|
||||
for i in range(textureCount):
|
||||
print("Texture", getFormatByIndex(textureInfoArray[i]["format"]), f"{textureInfoArray[i]['width']}x{textureInfoArray[i]['height']}", textureInfoArray[i]["blockHeightLog2"])
|
||||
wta_fp.seek(textureInfoArrayOffset + i * 0x100)
|
||||
wta_fp.write(textureInfoArray[i]["magic"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["format"])
|
||||
write_Int32(wta_fp, 1)
|
||||
write_Int32(wta_fp, textureInfoArray[i]["width"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["height"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["depth"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["mipCount"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["unk2"])
|
||||
write_float16(wta_fp, textureInfoArray[i]["unk3"])
|
||||
write_uInt16(wta_fp, textureInfoArray[i]["unk4"])
|
||||
write_uInt32(wta_fp, textureInfoArray[i]["format"])
|
||||
write_uInt32(wta_fp, 1) # Texture type (1)
|
||||
write_uInt32(wta_fp, textureInfoArray[i]["width"])
|
||||
write_uInt32(wta_fp, textureInfoArray[i]["height"])
|
||||
write_uInt32(wta_fp, textureInfoArray[i]["depth"])
|
||||
write_uInt32(wta_fp, textureInfoArray[i]["mipCount"])
|
||||
write_uInt32(wta_fp, 256) # header size? (0x100 = 256)
|
||||
write_uInt32(wta_fp, wtaTextureSize[i]) # Texture size? (0x3400 = 13312)
|
||||
while wta_fp.tell() % 16 != 0:
|
||||
wta_fp.write(b'\x00')
|
||||
|
||||
@@ -393,15 +393,24 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"depth": 1,
|
||||
"unk4": 32, # Always 32 (?)
|
||||
"textureLayout": [1027, 65543],
|
||||
"specialPad": 32, # Always 32 (?) - special padding
|
||||
"blockHeightLog2": 3,
|
||||
"flags": 4,
|
||||
"unk2": 0,
|
||||
"unk3": 0,
|
||||
"unk4": 65543,
|
||||
"arrayCount": 1
|
||||
}
|
||||
|
||||
if identifiers_array[i].upper() in metadata.keys():
|
||||
info["type"] = metadata[identifiers_array[i].upper()]["type"]
|
||||
info["format"] = metadata[identifiers_array[i].upper()]["format"]
|
||||
info["textureLayout"] = metadata[identifiers_array[i].upper()]["textureLayout"]
|
||||
info["specialPad"] = metadata[identifiers_array[i].upper()]["specialPad"]
|
||||
info["blockHeightLog2"] = metadata[identifiers_array[i].upper()]["blockHeightLog2"]
|
||||
info["flags"] = metadata[identifiers_array[i].upper()]["flags"]
|
||||
info["unk2"] = metadata[identifiers_array[i].upper()]["unk2"]
|
||||
info["unk3"] = metadata[identifiers_array[i].upper()]["unk3"]
|
||||
info["unk4"] = metadata[identifiers_array[i].upper()]["unk4"]
|
||||
|
||||
if ".dds" in path.lower():
|
||||
dds_fp = open(path, 'rb')
|
||||
@@ -436,7 +445,9 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
wtaTextureOffset[i] = wtp_fp.tell()
|
||||
|
||||
dds_fp.seek(80)
|
||||
blockHeightLog2 = info["textureLayout"][0] & 7
|
||||
specialPad = 0
|
||||
if info["flags"] & 0x4:
|
||||
specialPad = 32
|
||||
wtp_fp.write(compressImageData(
|
||||
getFormatByIndex(info['format']),
|
||||
info['width'],
|
||||
@@ -445,7 +456,8 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
info['arrayCount'],
|
||||
info['mipCount'],
|
||||
dds_fp.read(),
|
||||
blockHeightLog2
|
||||
info['blockHeightLog2'],
|
||||
specialPad=specialPad
|
||||
))
|
||||
wtaTextureSize[i] = wtp_fp.tell() - wtaTextureOffset[i]
|
||||
if wtaTextureSize[i] < 90112:
|
||||
@@ -463,7 +475,9 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
info["height"] = int.from_bytes(astc_fp.read(3), "little")
|
||||
|
||||
astc_fp.seek(16)
|
||||
blockHeightLog2 = info["textureLayout"][0] & 7
|
||||
specialPad = 0
|
||||
if info["flags"] & 0x4:
|
||||
specialPad = 32
|
||||
wtp_fp.write(compressImageData(
|
||||
getFormatByIndex(info['format']),
|
||||
info['width'],
|
||||
@@ -472,7 +486,8 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
info['arrayCount'],
|
||||
info['mipCount'],
|
||||
astc_fp.read(),
|
||||
blockHeightLog2
|
||||
info['blockHeightLog2'],
|
||||
specialPad=specialPad
|
||||
))
|
||||
wtaTextureSize[i] = wtp_fp.tell() - wtaTextureOffset[i]
|
||||
if wtaTextureSize[i] < 90112:
|
||||
@@ -528,9 +543,12 @@ def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
write_Int32(wta_fp, textureInfoArray[i]["width"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["height"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["depth"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["specialPad"])
|
||||
write_byte(wta_fp, textureInfoArray[i]["blockHeightLog2"])
|
||||
write_byte(wta_fp, textureInfoArray[i]["flags"])
|
||||
write_byte(wta_fp, textureInfoArray[i]["unk2"])
|
||||
write_byte(wta_fp, textureInfoArray[i]["unk3"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["unk4"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["textureLayout"][0])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["textureLayout"][1])
|
||||
while wta_fp.tell() % 16 != 0:
|
||||
wta_fp.write(b'\x00')
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ class WTAData:
|
||||
self.infos = []
|
||||
|
||||
infoFormat = f.read(4)
|
||||
if infoFormat == b'XT1\x00': # Astral Chain, Bayonetta 3 WTA format
|
||||
if infoFormat == b'XT1\x00':
|
||||
# Astral Chain, Bayonetta 3 WTA format
|
||||
self.type = "XT1"
|
||||
f.seek(f.tell() - 4)
|
||||
for i in range(self.num_files):
|
||||
@@ -66,14 +67,20 @@ class WTAData:
|
||||
"width": io.read_uint32(f),
|
||||
"height": io.read_uint32(f),
|
||||
"depth": io.read_uint32(f),
|
||||
"specialPad": io.read_uint32(f),
|
||||
"blockHeightLog2": io.read_uint8(f),
|
||||
"flags": io.read_uint8(f), # 0x4: use specialPad
|
||||
"unk2": io.read_uint8(f),
|
||||
"unk3": io.read_uint8(f),
|
||||
"unk4": io.read_uint32(f),
|
||||
"textureLayout": [io.read_uint32(f), io.read_uint32(f)],
|
||||
"arrayCount": 1
|
||||
}
|
||||
if info["type"] == 3 or info["type"] == 8: # T_Cube or T_Cube_Array
|
||||
info["arrayCount"] = 6
|
||||
self.infos.append(info)
|
||||
elif infoFormat == b'.tex': # NieR Switch WTA format
|
||||
|
||||
elif infoFormat == b'.tex':
|
||||
# NieR Switch WTA format
|
||||
self.type = "TEX"
|
||||
for i in range(self.num_files):
|
||||
f.seek(self.offsetTextureInfo + i * 0x100)
|
||||
@@ -85,13 +92,18 @@ class WTAData:
|
||||
"height": io.read_uint32(f),
|
||||
"depth": io.read_uint32(f),
|
||||
"mipCount": io.read_uint32(f),
|
||||
"unk2": io.read_uint32(f),
|
||||
"unk3": io.read_float16(f),
|
||||
"unk4": io.read_uint16(f),
|
||||
"headerSize": io.read_uint32(f),
|
||||
"textureSize": io.read_uint64(f), # identical to the one in the headers
|
||||
"type": 1,
|
||||
"textureLayout": [4, 0],
|
||||
"blockHeightLog2": 4,
|
||||
"arrayCount": 1
|
||||
}
|
||||
|
||||
if info["width"] < 256:
|
||||
info["blockHeightLog2"] = 8 & 7
|
||||
if info["width"] < 128:
|
||||
info["blockHeightLog2"] = 16 & 7
|
||||
|
||||
self.infos.append(info)
|
||||
else:
|
||||
self.type = "PC"
|
||||
@@ -118,7 +130,11 @@ class WTAData:
|
||||
os.makedirs(extractionDir, exist_ok=True)
|
||||
# Unswizzle
|
||||
textureFormat = getFormatByIndex(self.infos[i]["format"])
|
||||
blockHeightLog2 = self.infos[i]["textureLayout"][0] & 7
|
||||
|
||||
specialPad = 1
|
||||
if "specialPad" in self.infos[i].keys() and "flags" in self.infos[i].keys() and self.infos[i]["flags"] & 0x4:
|
||||
specialPad = self.infos[i]["specialPad"]
|
||||
|
||||
texture = loadImageData(
|
||||
textureFormat,
|
||||
self.infos[i]['width'],
|
||||
@@ -127,7 +143,8 @@ class WTAData:
|
||||
self.infos[i]['arrayCount'],
|
||||
self.infos[i]['mipCount'],
|
||||
self.data[self.offsets[i]:self.offsets[i]+self.sizes[i]],
|
||||
blockHeightLog2
|
||||
self.infos[i]["blockHeightLog2"],
|
||||
specialPad=specialPad
|
||||
)
|
||||
|
||||
# Construct headers
|
||||
@@ -156,14 +173,24 @@ class WTAData:
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(asyncio.gather(*tasks))
|
||||
|
||||
# Write metadata file (need to store format and textureLayout for later)
|
||||
# Write metadata file (need to store format and other data for later)
|
||||
metadata = {}
|
||||
for i in range(self.num_files):
|
||||
metadata[f"{self.idx[i]:0>8X}"] = {
|
||||
"type": self.infos[i]["type"],
|
||||
"format": self.infos[i]["format"],
|
||||
"textureLayout": self.infos[i]["textureLayout"]
|
||||
"blockHeightLog2": self.infos[i]["blockHeightLog2"],
|
||||
}
|
||||
|
||||
if "specialPad" in self.infos[i].keys():
|
||||
# add AC/B3 specialPad
|
||||
metadata[f"{self.idx[i]:0>8X}"]["specialPad"] = self.infos[i]["specialPad"]
|
||||
metadata[f"{self.idx[i]:0>8X}"]["blockHeightLog2"] = self.infos[i]["blockHeightLog2"]
|
||||
metadata[f"{self.idx[i]:0>8X}"]["flags"] = self.infos[i]["flags"]
|
||||
metadata[f"{self.idx[i]:0>8X}"]["unk2"] = self.infos[i]["unk2"]
|
||||
metadata[f"{self.idx[i]:0>8X}"]["unk3"] = self.infos[i]["unk3"]
|
||||
metadata[f"{self.idx[i]:0>8X}"]["unk4"] = self.infos[i]["unk4"]
|
||||
|
||||
with open(os.path.join(extractionDir, "xt1_info.json"), "w") as f:
|
||||
f.write(json.dumps(metadata, indent=4))
|
||||
|
||||
|
||||
+15
-13
@@ -1,9 +1,11 @@
|
||||
# TegraX1Swizzle.py - cabalex [Updated Dec 2022]
|
||||
# TegraX1Swizzle.py - cabalex [Updated Aug 2024]
|
||||
# Based on:
|
||||
# KillzXGaming's Switch Toolbox texture decoding - https://github.com/KillzXGaming/Switch-Toolbox/blob/604f7b3d369bc97d9d05632da3211ed11b990ba7/Switch_Toolbox_Library/Texture%20Decoding/Switch/TegraX1Swizzle.cs
|
||||
# aboood40091's BNTX-Extractor - https://github.com/aboood40091/BNTX-Extractor/blob/master/swizzle.py
|
||||
# [Format table] Ryujinx's image table - https://github.com/Ryujinx/Ryujinx/blob/c86aacde76b5f8e503e2b412385c8491ecc86b3b/Ryujinx.Graphics/Graphics3d/Texture/ImageUtils.cs
|
||||
|
||||
# Aug 2024: Added special padding to GOB offsets.
|
||||
|
||||
formatTable = {
|
||||
"R8G8B8A8_UNORM": [4, 1, 1, 1],
|
||||
"BC1_UNORM": [8, 4, 4, 1],
|
||||
@@ -76,7 +78,7 @@ def round_up(x, y):
|
||||
return ((x - 1) | (y - 1)) + 1
|
||||
|
||||
|
||||
def _swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bpp, tileMode, blockHeightLog2, data, toSwizzle):
|
||||
def _swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bpp, tileMode, blockHeightLog2, specialPad, data, toSwizzle):
|
||||
block_height = 1 << blockHeightLog2
|
||||
|
||||
width = DIV_ROUND_UP(width, blkWidth)
|
||||
@@ -102,7 +104,7 @@ def _swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bp
|
||||
pos = y * pitch + x * bpp
|
||||
|
||||
else:
|
||||
pos = getAddrBlockLinear(x, y, width, bpp, 0, block_height)
|
||||
pos = getAddrBlockLinear(x, y, width, bpp, 0, block_height, specialPad)
|
||||
|
||||
pos_ = (y * width + x) * bpp
|
||||
|
||||
@@ -116,20 +118,20 @@ def _swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bp
|
||||
return result[:size]
|
||||
|
||||
#def deswizzle(width, height, blkWidth, blkHeight, bpp, tileMode, alignment, size_range, data):
|
||||
def deswizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bpp, tileMode, size_range, data):
|
||||
return _swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bpp, tileMode, size_range, bytes(data), 0)
|
||||
def deswizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bpp, tileMode, size_range, specialPad, data):
|
||||
return _swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bpp, tileMode, size_range, specialPad, bytes(data), 0)
|
||||
#return _swizzle(width, height, blkWidth, blkHeight, bpp, tileMode, alignment, size_range, bytes(data), 0)
|
||||
|
||||
|
||||
def swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bpp, tileMode, size_range, data):
|
||||
return _swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bpp, tileMode, size_range, bytes(data), 1)
|
||||
def swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bpp, tileMode, size_range, specialPad, data):
|
||||
return _swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, roundPitch, bpp, tileMode, size_range, specialPad, bytes(data), 1)
|
||||
|
||||
|
||||
def getAddrBlockLinear(x, y, image_width, bytes_per_pixel, base_address, block_height):
|
||||
def getAddrBlockLinear(x, y, image_width, bytes_per_pixel, base_address, block_height, specialPad):
|
||||
"""
|
||||
From the Tegra X1 TRM
|
||||
"""
|
||||
image_width_in_gobs = DIV_ROUND_UP(image_width * bytes_per_pixel, 64)
|
||||
image_width_in_gobs = DIV_ROUND_UP(round_up(image_width, specialPad) * bytes_per_pixel, 64)
|
||||
|
||||
GOB_address = (base_address
|
||||
+ (y // (8 * block_height)) * 512 * block_height * image_width_in_gobs
|
||||
@@ -143,7 +145,7 @@ def getAddrBlockLinear(x, y, image_width, bytes_per_pixel, base_address, block_h
|
||||
|
||||
return Address
|
||||
|
||||
def loadImageData(format: str, width: int, height: int, depth: int, arrayCount: int, mipCount: int, imageData, blockHeightLog2, target=1, linearTileMode=False):
|
||||
def loadImageData(format: str, width: int, height: int, depth: int, arrayCount: int, mipCount: int, imageData, blockHeightLog2, target=1, linearTileMode=False, specialPad=1):
|
||||
[bpp, blkWidth, blkHeight, blkDepth] = getFormatTable(format)
|
||||
blockHeight = DIV_ROUND_UP(height, blkHeight)
|
||||
pitch = 0
|
||||
@@ -185,7 +187,7 @@ def loadImageData(format: str, width: int, height: int, depth: int, arrayCount:
|
||||
try:
|
||||
pitch = round_up(width__ * bpp, 64)
|
||||
surfaceSize += pitch * round_up(height__, max(1, blockHeight >> blockHeightShift) * 8)
|
||||
result = deswizzle(width, height, depth, blkWidth, blkHeight, blkDepth, target, bpp, tileMode, max(0, blockHeightLog2 - blockHeightShift), data_)
|
||||
result = deswizzle(width, height, depth, blkWidth, blkHeight, blkDepth, target, bpp, tileMode, max(0, blockHeightLog2 - blockHeightShift), specialPad, data_)
|
||||
|
||||
# the program creates a copy and uses that to remove unneeded data
|
||||
# yeah, i'm not doing that
|
||||
@@ -197,7 +199,7 @@ def loadImageData(format: str, width: int, height: int, depth: int, arrayCount:
|
||||
arrayOffset += len(imageData) / arrayCount
|
||||
return False
|
||||
|
||||
def compressImageData(format: str, width: int, height: int, depth: int, arrayCount: int, mipCount: int, imageData, blockHeightLog2, target=1, linearTileMode=False):
|
||||
def compressImageData(format: str, width: int, height: int, depth: int, arrayCount: int, mipCount: int, imageData, blockHeightLog2, target=1, linearTileMode=False, specialPad=1):
|
||||
bpp = formatTable[format][0]
|
||||
blkWidth = formatTable[format][1]
|
||||
blkHeight = formatTable[format][2]
|
||||
@@ -242,7 +244,7 @@ def compressImageData(format: str, width: int, height: int, depth: int, arrayCou
|
||||
try:
|
||||
pitch = round_up(width__ * bpp, 64)
|
||||
surfaceSize += pitch * round_up(height__, max(1, blockHeight >> blockHeightShift) * 8)
|
||||
result = swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, target, bpp, tileMode, max(0, blockHeightLog2 - blockHeightShift), data_)
|
||||
result = swizzle(width, height, depth, blkWidth, blkHeight, blkDepth, target, bpp, tileMode, max(0, blockHeightLog2 - blockHeightShift), specialPad, data_)
|
||||
|
||||
# the program creates a copy and uses that to remove unneeded data
|
||||
# yeah, i'm not doing that
|
||||
|
||||
Reference in New Issue
Block a user