mirror of
https://github.com/YorhaX2P/NieR2Blender2NieR-PS4.git
synced 2026-08-26 19:43:33 +00:00
Merge branch 'master' into pr/cabalex/1
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
from ...wta_wtp.exporter import export_wta_wtp
|
||||
|
||||
import bpy
|
||||
from bpy.props import StringProperty
|
||||
@@ -16,12 +17,8 @@ class ExportAllSteps(bpy.types.PropertyGroup):
|
||||
name = "Export WMB",
|
||||
default = True
|
||||
)
|
||||
useWtpStep: bpy.props.BoolProperty(
|
||||
name = "Export WTP",
|
||||
default = True
|
||||
)
|
||||
useWtaStep: bpy.props.BoolProperty(
|
||||
name = "Export WTA",
|
||||
useWtaWtpStep: bpy.props.BoolProperty(
|
||||
name = "Export WTA + WTP",
|
||||
default = True
|
||||
)
|
||||
useColStep: bpy.props.BoolProperty(
|
||||
@@ -63,6 +60,16 @@ class ExportAllSteps(bpy.types.PropertyGroup):
|
||||
default = True
|
||||
)
|
||||
|
||||
exportingForGame: bpy.props.EnumProperty(
|
||||
name = "Export For Game",
|
||||
default = "NIER",
|
||||
items = [
|
||||
("NIER", "NieR: Automata", "Exports for NieR (WTA: DDS only).", 1),
|
||||
("NIERSWITCH", "NieR: Automata (Switch)", "Exports for NieR on Nintendo Switch (WTA: DDS/ASTC)", 2),
|
||||
("ASTRALCHAIN", "Astral Chain", "Exports for Astral Chain (WTA: DDS/ASTC)", 3)
|
||||
]
|
||||
)
|
||||
|
||||
class DAT_DTT_PT_Export(bpy.types.Panel):
|
||||
bl_label = "NieR:Automata Export"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
@@ -148,8 +155,7 @@ class DAT_DTT_PT_Export(bpy.types.Panel):
|
||||
row = box.row(align=True)
|
||||
row.scale_y = 1.125
|
||||
row.prop(context.scene.ExportAllSteps, "useWmbStep", text="WMB", icon="PANEL_CLOSE" if context.scene.ExportAllSteps.useWmbStep else "ADD")
|
||||
row.prop(context.scene.ExportAllSteps, "useWtpStep", text="WTP", icon="PANEL_CLOSE" if context.scene.ExportAllSteps.useWtpStep else "ADD")
|
||||
row.prop(context.scene.ExportAllSteps, "useWtaStep", text="WTA", icon="PANEL_CLOSE" if context.scene.ExportAllSteps.useWtaStep else "ADD")
|
||||
row.prop(context.scene.ExportAllSteps, "useWtaWtpStep", text="WTA + WTP", icon="PANEL_CLOSE" if context.scene.ExportAllSteps.useWtaWtpStep else "ADD")
|
||||
row = box.row(align=True)
|
||||
row.scale_y = 1.125
|
||||
secondRowItemCount = 0
|
||||
@@ -175,6 +181,10 @@ class DAT_DTT_PT_Export(bpy.types.Panel):
|
||||
row.prop(context.scene.ExportAllSteps, "centerOrigins", text="Center Origins", icon="OBJECT_ORIGIN")
|
||||
row.prop(context.scene.ExportAllSteps, "deleteLoose", text="Delete Loose", icon="SNAP_VERTEX")
|
||||
|
||||
row = box.row(align=True)
|
||||
box = row.box()
|
||||
box.prop(context.scene.ExportAllSteps, "exportingForGame", text="Export for")
|
||||
|
||||
layout.separator()
|
||||
|
||||
row = layout.row()
|
||||
@@ -243,7 +253,9 @@ class ExportAll(bpy.types.Operator):
|
||||
sarColl = bpy.data.objects["Field-Root"].users_collection[0]
|
||||
|
||||
for item in context.scene.DatContents:
|
||||
if item.filepath.endswith('.wta'):
|
||||
if item.filepath.endswith('.wmb'):
|
||||
wmbFilePath = item.filepath
|
||||
elif item.filepath.endswith('.wta'):
|
||||
wtaFilePath = item.filepath
|
||||
elif item.filepath.endswith('.col'):
|
||||
colFilePath = item.filepath
|
||||
@@ -276,15 +288,11 @@ class ExportAll(bpy.types.Operator):
|
||||
bpy.ops.b2n.deleteloosegeometryall()
|
||||
wmb_exporter.main(wmbFilePath)
|
||||
exportedFilesCount += 1
|
||||
from ...wta_wtp.exporter import export_wta, export_wtp
|
||||
if exportSteps.useWtaStep:
|
||||
print("Exporting WTA")
|
||||
export_wta.main(context, wtaFilePath)
|
||||
exportedFilesCount += 1
|
||||
if exportSteps.useWtpStep:
|
||||
print("Exporting WTP")
|
||||
export_wtp.main(context, wtpFilePath)
|
||||
exportedFilesCount += 1
|
||||
from ...wta_wtp.exporter import export_wta_wtp
|
||||
if exportSteps.useWtaWtpStep:
|
||||
print("Exporting WTA/WTP")
|
||||
export_wta_wtp.main(context, wtaFilePath, wtpFilePath, exportSteps.exportingForGame)
|
||||
exportedFilesCount += 2
|
||||
from ...col.exporter import col_exporter
|
||||
if exportSteps.useColStep:
|
||||
print("Exporting COL")
|
||||
@@ -326,7 +334,7 @@ class ExportAll(bpy.types.Operator):
|
||||
fileNames = [os.path.basename(file_path) for file_path in file_list]
|
||||
saveDatInfo(datInfoFilePath, fileNames, datFileName)
|
||||
# export dtt
|
||||
export_dat.main(datFilePath, file_list)
|
||||
export_dat.main(datFilePath, file_list, exportSteps.exportingForGame)
|
||||
exportedFilesCount += 1
|
||||
if exportSteps.useDttStep:
|
||||
if len(context.scene.DttContents) == 0:
|
||||
@@ -342,7 +350,7 @@ class ExportAll(bpy.types.Operator):
|
||||
fileNames = [os.path.basename(file_path) for file_path in file_list]
|
||||
saveDatInfo(datInfoFilePath, fileNames, dttFileName)
|
||||
# export dtt
|
||||
export_dat.main(dttFilePath, file_list)
|
||||
export_dat.main(dttFilePath, file_list, exportSteps.exportingForGame)
|
||||
exportedFilesCount += 1
|
||||
|
||||
tDiff = int(time.time() - t1)
|
||||
|
||||
@@ -7,7 +7,7 @@ from ...utils.util import *
|
||||
def to_string(bs, encoding = 'utf8'):
|
||||
return bs.split(b'\x00')[0].decode(encoding)
|
||||
|
||||
def main(export_filepath, file_list):
|
||||
def main(export_filepath, file_list, exportingForGame):
|
||||
files = file_list
|
||||
fileNumber = len(files)
|
||||
from .datHashGenerator import generateHashData
|
||||
@@ -46,11 +46,23 @@ def main(export_filepath, file_list):
|
||||
#fileOffsets
|
||||
fileOffsets = []
|
||||
currentOffset = hashMapOffset + hashMapSize
|
||||
# ASTRAL CHAIN's DTT files start at 0x8000 bytes.
|
||||
if exportingForGame == "ASTRALCHAIN" and ".dtt" in export_filepath:
|
||||
currentOffset = (math.ceil(currentOffset / 0x8000)) * 0x8000
|
||||
|
||||
# NieR Switch's DTT files start at intervals of 0x200.
|
||||
if exportingForGame == "NIERSWITCH":
|
||||
currentOffset = (math.ceil(currentOffset / 0x200)) * 0x200
|
||||
|
||||
for fp in files:
|
||||
currentOffset = (math.ceil(currentOffset / 16)) * 16
|
||||
fileOffsets.append(currentOffset)
|
||||
currentOffset += os.path.getsize(fp)
|
||||
|
||||
# ASTRAL CHAIN's BNK files are padded out to 2048 bytes.
|
||||
if exportingForGame == "ASTRALCHAIN" and ".bnk" in os.path.basename(fp):
|
||||
currentOffset = (math.ceil(currentOffset / 2048)) * 2048
|
||||
|
||||
# fileSizes
|
||||
fileSizes = []
|
||||
for fp in files:
|
||||
|
||||
@@ -345,3 +345,23 @@ def saveDatInfo(filepath: str, files: List[str], filename: str):
|
||||
"ext": ext[1:]
|
||||
}
|
||||
json.dump(jsonFiles, f, indent=4)
|
||||
|
||||
def resolveTextureDir(wmb_dir, wmbname):
|
||||
texture_dir_dat = wmb_dir.replace(wmbname, "textures").replace(".dtt", ".dat")
|
||||
texture_dir_dtt = wmb_dir.replace(wmbname, "textures").replace(".dat", ".dtt")
|
||||
if os.path.exists(texture_dir_dat):
|
||||
return texture_dir_dat
|
||||
elif os.path.exists(texture_dir_dtt):
|
||||
return texture_dir_dtt
|
||||
else:
|
||||
return ""
|
||||
|
||||
def resolveTexturePaths(texture_dir, textureID):
|
||||
dds_path = "%s/%s.dds" % (texture_dir, textureID)
|
||||
png_path = "%s/%s.png" % (texture_dir, textureID)
|
||||
if os.path.exists(dds_path):
|
||||
return dds_path
|
||||
elif os.path.exists(png_path):
|
||||
return png_path
|
||||
else:
|
||||
return ""
|
||||
|
||||
@@ -5,7 +5,7 @@ import math
|
||||
from typing import List, Tuple
|
||||
from mathutils import Vector
|
||||
|
||||
from ...utils.util import ShowMessageBox, getPreferences, printTimings
|
||||
from ...utils.util import ShowMessageBox, getPreferences, printTimings, resolveTexturePaths, resolveTextureDir
|
||||
from .wmb import *
|
||||
from ...wta_wtp.exporter.wta_wtp_ui_manager import isTextureTypeSupported, makeWtaMaterial
|
||||
|
||||
@@ -180,7 +180,7 @@ def addWtaExportMaterial(texture_dir, material):
|
||||
material_name = material[0]
|
||||
textures = material[1]
|
||||
wtaTextures: List[Tuple[str, str, str]] = [
|
||||
(mapType, id, os.path.join(texture_dir, f"{id}.dds"))
|
||||
(mapType, id, resolveTexturePaths(texture_dir, id))
|
||||
for mapType, id in textures.items()
|
||||
if isTextureTypeSupported(mapType)
|
||||
]
|
||||
@@ -245,7 +245,7 @@ def construct_materials(texture_dir, material):
|
||||
for texturesType in textures.keys():
|
||||
textures_type = texturesType.lower()
|
||||
material[texturesType] = textures.get(texturesType)
|
||||
texture_file = "%s/%s.dds" % (texture_dir, textures[texturesType])
|
||||
texture_file = resolveTexturePaths(texture_dir, textures[texturesType])
|
||||
if os.path.exists(texture_file):
|
||||
if textures_type.find('albedo') > -1:
|
||||
albedo_maps[textures_type] = textures.get(texturesType)
|
||||
@@ -258,9 +258,10 @@ def construct_materials(texture_dir, material):
|
||||
|
||||
# Albedo Nodes
|
||||
albedo_nodes = []
|
||||
albedo_uv_nodes = []
|
||||
albedo_mixRGB_nodes = []
|
||||
for i, textureID in enumerate(albedo_maps.values()):
|
||||
texture_file = "%s/%s.dds" % (texture_dir, textureID)
|
||||
texture_file = resolveTexturePaths(texture_dir, textureID)
|
||||
if os.path.exists(texture_file):
|
||||
albedo_image = nodes.new(type='ShaderNodeTexImage')
|
||||
albedo_nodes.append(albedo_image)
|
||||
@@ -277,6 +278,12 @@ def construct_materials(texture_dir, material):
|
||||
albedo_mixRGB_nodes.append(mixRGB_shader)
|
||||
mixRGB_shader.location = 300,(i-1)*-60
|
||||
mixRGB_shader.hide = True
|
||||
# UV map node for other textures
|
||||
uv_shader = nodes.new(type='ShaderNodeUVMap')
|
||||
albedo_uv_nodes.append(uv_shader)
|
||||
uv_shader.uv_map = f"UVMap{i+1}"
|
||||
uv_shader.location = -300,i*-60
|
||||
uv_shader.hide = True
|
||||
# Albedo Links
|
||||
if len(albedo_nodes) == 1:
|
||||
albedo_principled = links.new(albedo_nodes[0].outputs['Color'], principled.inputs['Base Color'])
|
||||
@@ -287,8 +294,12 @@ def construct_materials(texture_dir, material):
|
||||
for i in range(len(albedo_mixRGB_nodes)):
|
||||
albedo_link = links.new(albedo_nodes[i+1].outputs['Color'], albedo_mixRGB_nodes[i].inputs['Color1'])
|
||||
alpha_link = links.new(albedo_nodes[i].outputs['Alpha'], albedo_mixRGB_nodes[i].inputs['Fac'])
|
||||
# UV is [usually] different for other AlbedoMaps
|
||||
uv_link = links.new(albedo_uv_nodes[i].outputs['UV'], albedo_nodes[i+1].inputs['Vector'])
|
||||
if i > 0:
|
||||
mixRGB_link = links.new(albedo_mixRGB_nodes[i-1].outputs['Color'], albedo_mixRGB_nodes[i].inputs['Color2'])
|
||||
# Preserve Alpha for last texture (ASTRAL CHAIN Harmony Square signage?)
|
||||
links.new(albedo_nodes[-1].outputs['Alpha'], principled.inputs['Alpha'])
|
||||
mixRGB_link = links.new(albedo_mixRGB_nodes[-1].outputs['Color'], principled.inputs['Base Color'])
|
||||
|
||||
# Mask Nodes
|
||||
@@ -297,7 +308,7 @@ def construct_materials(texture_dir, material):
|
||||
mask_sepRGB_nodes = []
|
||||
mask_invert_nodes = []
|
||||
for i, textureID in enumerate(mask_maps.values()):
|
||||
texture_file = "%s/%s.dds" % (texture_dir, textureID)
|
||||
texture_file = resolveTexturePaths(texture_dir, textureID)
|
||||
if os.path.exists(texture_file):
|
||||
mask_image = nodes.new(type='ShaderNodeTexImage')
|
||||
mask_nodes.append(mask_image)
|
||||
@@ -334,7 +345,7 @@ def construct_materials(texture_dir, material):
|
||||
normal_nodes = []
|
||||
normal_mixRGB_nodes = []
|
||||
for i, textureID in enumerate(normal_maps.values()):
|
||||
texture_file = "%s/%s.dds" % (texture_dir, textureID)
|
||||
texture_file = resolveTexturePaths(texture_dir, textureID)
|
||||
if os.path.exists(texture_file):
|
||||
normal_image = nodes.new(type='ShaderNodeTexImage')
|
||||
normal_nodes.append(normal_image)
|
||||
@@ -374,7 +385,7 @@ def construct_materials(texture_dir, material):
|
||||
curvature_sepRGB_nodes = []
|
||||
curvature_mul_nodes = []
|
||||
for i, textureID in enumerate(curvature_maps.values()):
|
||||
texture_file = "%s/%s.dds" % (texture_dir, textureID)
|
||||
texture_file = resolveTexturePaths(texture_dir, textureID)
|
||||
if os.path.exists(texture_file):
|
||||
curvature_image = nodes.new(type='ShaderNodeTexImage')
|
||||
curvature_nodes.append(curvature_image)
|
||||
@@ -574,14 +585,14 @@ def get_wmb_material(wmb, texture_dir):
|
||||
try:
|
||||
texture_stream = wmb.wta.getTextureByIdentifier(identifier,wmb.wtp_fp)
|
||||
if texture_stream:
|
||||
if not os.path.exists(os.path.join(texture_dir, identifier + '.dds')):
|
||||
if not os.path.exists(resolveTexturePaths(texture_dir, identifier)):
|
||||
create_dir(texture_dir)
|
||||
texture_fp = open(os.path.join(texture_dir, identifier + '.dds'), "wb")
|
||||
print('[+] could not find DDS texture, trying to find it in WTA; %s.dds'% identifier)
|
||||
texture_fp.write(texture_stream)
|
||||
texture_fp.close()
|
||||
else:
|
||||
print('[+] Found %s.dds'% identifier)
|
||||
print('[+] Found ' + resolveTexturePaths(texture_dir, identifier).split("/")[-1])
|
||||
except:
|
||||
continue
|
||||
materials.append([material_name,textures,uniforms,shader_name,technique_name,parameterGroups])
|
||||
@@ -592,7 +603,7 @@ def get_wmb_material(wmb, texture_dir):
|
||||
identifier = wmb.wta.wtaTextureIdentifier[textureIndex]
|
||||
texture_stream = wmb.wta.getTextureByIdentifier(identifier,wmb.wtp_fp)
|
||||
if texture_stream:
|
||||
if not os.path.exists(os.path.join(texture_dir, identifier + '.dds')):
|
||||
if not os.path.exists(resolveTexturePaths(texture_dir, identifier)):
|
||||
create_dir(texture_dir)
|
||||
texture_fp = open(os.path.join(texture_dir, identifier + '.dds'), "wb")
|
||||
print('[+] dumping %s.dds'% identifier)
|
||||
@@ -682,7 +693,7 @@ def main(only_extract = False, wmb_file = os.path.join(os.path.split(os.path.rea
|
||||
wmbCollection.children.link(col)
|
||||
#bpy.context.view_layer.active_layer_collection = bpy.context.view_layer.layer_collection.children[-1]
|
||||
|
||||
texture_dir = wmb_file.replace(wmbname, 'textures')
|
||||
texture_dir = resolveTextureDir(wmb_file, wmbname)
|
||||
if wmb.hasBone:
|
||||
boneArray = [[bone.boneIndex, "bone%d"%bone.boneIndex, bone.parentIndex,"bone%d"%bone.parentIndex, bone.world_position, bone.world_rotation, bone.boneNumber, bone.local_position, bone.local_rotation, bone.world_rotation, bone.world_position_tpose] for bone in wmb.boneArray]
|
||||
armature_no_wmb = wmbname.replace('.wmb','')
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,540 @@
|
||||
import subprocess
|
||||
import asyncio
|
||||
import math
|
||||
import json
|
||||
from ...utils.ioUtils import read_int32, write_Int32, write_uInt16, write_float16
|
||||
from . import generate_wta_wtp_data
|
||||
from .wta_wtp_utils import *
|
||||
from ..tegrax1swizzle import compressImageData, getFormatByIndex
|
||||
|
||||
# Asynchronously encodes a PNG file to ASTC.
|
||||
async def encode_astc(filePath, format: str):
|
||||
script_file = os.path.realpath(__file__)
|
||||
directory = os.path.dirname(script_file).replace("exporter", "")
|
||||
appPath = os.path.join(directory, "astcenc-avx2.exe")
|
||||
result = subprocess.run([appPath, "-cs", filePath, filePath.replace('.png', '.astc'), format.split("_")[1], "-medium"], cwd=directory)
|
||||
|
||||
def main(context, export_filepath_wta, export_filepath_wtp, exportingForGame):
|
||||
wta_fp = open(export_filepath_wta,'wb')
|
||||
wtp_fp = open(export_filepath_wtp,'wb')
|
||||
|
||||
# Assign data and check if valid
|
||||
identifiers_array, texture_paths_array, albedo_indexes, metadata_path = generate_wta_wtp_data.generate(context, exportingForGame)
|
||||
|
||||
if None in [identifiers_array, texture_paths_array, albedo_indexes]:
|
||||
print("WTP Export Failed! :{")
|
||||
return
|
||||
|
||||
# Assign some shit
|
||||
unknown04 = 3
|
||||
textureCount = len(texture_paths_array)
|
||||
paddingAmount = ((textureCount + 7) // 8) * 8 #rounds up to the nearest 8th integer
|
||||
textureOffsetArrayOffset = 32
|
||||
textureSizeArrayOffset = textureOffsetArrayOffset + (paddingAmount * 4)
|
||||
unknownArrayOffset1 = textureSizeArrayOffset + (paddingAmount * 4)
|
||||
textureIdentifierArrayOffset = unknownArrayOffset1 + (paddingAmount * 4)
|
||||
textureInfoArrayOffset = textureIdentifierArrayOffset + (paddingAmount * 4)
|
||||
wtaTextureOffset = [0] * textureCount
|
||||
wtaTextureSize = [0] * textureCount
|
||||
wtaTextureIdentifier = [0] * textureCount
|
||||
unknownArray1 = [0] * textureCount
|
||||
textureInfoArray = []
|
||||
paddingAmountArray = []
|
||||
|
||||
# Pad the DDS files
|
||||
#pad_dds_files(texture_paths_array)
|
||||
|
||||
current_wtaTextureOffset = 0
|
||||
|
||||
if exportingForGame == "NIER":
|
||||
# Open every DDS texture
|
||||
for i in range(textureCount):
|
||||
dds_fp = open(texture_paths_array[i], 'rb')
|
||||
dds_paddedSize = os.stat(texture_paths_array[i]).st_size
|
||||
|
||||
#checks dds dxt and cube map info
|
||||
dds_fp.seek(84)
|
||||
dxt = dds_fp.read(4)
|
||||
dds_fp.seek(112)
|
||||
cube = dds_fp.read(4)
|
||||
|
||||
#finds how much padding bytes are added to a dds
|
||||
dds_padding = 0
|
||||
if i != len(texture_paths_array)-1:
|
||||
dds_fp.seek(dds_paddedSize-4)
|
||||
dds_padding = read_int32(dds_fp)
|
||||
paddingAmountArray.append(dds_padding)
|
||||
|
||||
#wtaTextureOffset
|
||||
if i+1 in range(len(wtaTextureSize)):
|
||||
"""
|
||||
if dds_paddedSize < 12289:
|
||||
wtaTextureOffset[i+1] = wtaTextureOffset[i] + 12288
|
||||
elif dds_paddedSize < 176129:
|
||||
wtaTextureOffset[i+1] = wtaTextureOffset[i] + 176128
|
||||
elif dds_paddedSize < 352257:
|
||||
wtaTextureOffset[i+1] = wtaTextureOffset[i] + 352256
|
||||
elif dds_paddedSize < 528385:
|
||||
wtaTextureOffset[i+1] = wtaTextureOffset[i] + 528384
|
||||
elif dds_paddedSize < 700417:
|
||||
wtaTextureOffset[i+1] = wtaTextureOffset[i] + 700416
|
||||
elif dds_paddedSize < 2797569:
|
||||
wtaTextureOffset[i+1] = wtaTextureOffset[i] + 2797568
|
||||
else:
|
||||
wtaTextureOffset[i+1] = dds_paddedSize
|
||||
"""
|
||||
wtaTextureOffset[i+1] = current_wtaTextureOffset + dds_paddedSize
|
||||
current_wtaTextureOffset += dds_paddedSize
|
||||
#wtaTextureSize
|
||||
wtaTextureSize[i] = dds_paddedSize# - dds_padding
|
||||
#wtaTextureIdentifier
|
||||
wtaTextureIdentifier[i] = identifiers_array[i]
|
||||
#unknownArray1
|
||||
if i in albedo_indexes:
|
||||
unknownArray1[i] = 637534240
|
||||
else:
|
||||
unknownArray1[i] = 570425376
|
||||
#unknownArray2
|
||||
if dxt not in [b'DXT1', b'DXT3', b'DXT5']:
|
||||
print("Unknown DXT format! Make sure you use DXT1, DXT3 or DXT5!")
|
||||
dds_fp.close()
|
||||
wta_fp.close()
|
||||
return
|
||||
|
||||
if dxt == b'DXT1':
|
||||
textureInfoArray.append(71)
|
||||
textureInfoArray.append(3)
|
||||
if cube == b'\x00\xfe\x00\x00':
|
||||
textureInfoArray.append(4)
|
||||
else:
|
||||
textureInfoArray.append(0)
|
||||
textureInfoArray.append(1)
|
||||
textureInfoArray.append(0)
|
||||
if dxt == b'DXT3':
|
||||
textureInfoArray.append(74)
|
||||
textureInfoArray.append(3)
|
||||
if cube == b'\x00\xfe\x00\x00':
|
||||
textureInfoArray.append(4)
|
||||
else:
|
||||
textureInfoArray.append(0)
|
||||
textureInfoArray.append(1)
|
||||
textureInfoArray.append(0)
|
||||
if dxt == b'DXT5':
|
||||
textureInfoArray.append(77)
|
||||
textureInfoArray.append(3)
|
||||
if cube == b'\x00\xfe\x00\x00':
|
||||
textureInfoArray.append(4)
|
||||
else:
|
||||
textureInfoArray.append(0)
|
||||
textureInfoArray.append(1)
|
||||
textureInfoArray.append(0)
|
||||
|
||||
# Write WTP
|
||||
dds_fp.seek(0)
|
||||
content = dds_fp.read()
|
||||
#print("-Writing dds: " + texture_paths_array[i] + " to file: " + export_filepath + " at position: " + str(i))
|
||||
wtp_fp.write(content)
|
||||
dds_fp.close()
|
||||
|
||||
|
||||
dds_fp.close()
|
||||
|
||||
# Write everything
|
||||
padding = b''
|
||||
for i in range(paddingAmount - textureCount):
|
||||
padding += b'\x00\x00\x00\x00'
|
||||
|
||||
wta_fp.write(b'WTB\x00')
|
||||
wta_fp.write(to_bytes(unknown04))
|
||||
wta_fp.write(to_bytes(textureCount))
|
||||
wta_fp.write(to_bytes(textureOffsetArrayOffset))
|
||||
wta_fp.write(to_bytes(textureSizeArrayOffset))
|
||||
wta_fp.write(to_bytes(unknownArrayOffset1))
|
||||
wta_fp.write(to_bytes(textureIdentifierArrayOffset))
|
||||
wta_fp.write(to_bytes(textureInfoArrayOffset))
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(wtaTextureOffset[i]))
|
||||
wta_fp.write(padding)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(wtaTextureSize[i]))
|
||||
wta_fp.write(padding)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(unknownArray1[i]))
|
||||
wta_fp.write(padding)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(wtaTextureIdentifier[i]))
|
||||
wta_fp.write(padding)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(textureInfoArray[(i*5)]))
|
||||
wta_fp.write(to_bytes(textureInfoArray[(i*5)+1]))
|
||||
wta_fp.write(to_bytes(textureInfoArray[(i*5)+2]))
|
||||
wta_fp.write(to_bytes(textureInfoArray[(i*5)+3]))
|
||||
wta_fp.write(to_bytes(textureInfoArray[(i*5)+4]))
|
||||
wta_fp.write(padding)
|
||||
|
||||
elif exportingForGame == "NIERSWITCH":
|
||||
# NIER SWITCH
|
||||
|
||||
# Change offsets
|
||||
textureSizeArrayOffset = math.ceil((textureOffsetArrayOffset + (textureCount * 4)) / 0x20) * 0x20
|
||||
unknownArrayOffset1 = math.ceil((textureSizeArrayOffset + (textureCount * 4)) / 0x20) * 0x20
|
||||
textureIdentifierArrayOffset = math.ceil((unknownArrayOffset1 + (textureCount * 4)) / 0x20) * 0x20
|
||||
textureInfoArrayOffset = math.ceil((textureIdentifierArrayOffset + (textureCount * 4)) / 0x20) * 0x20
|
||||
|
||||
# Encode all PNG files to ASTC (asynchronously for speed)
|
||||
tasks = []
|
||||
for i in range(textureCount):
|
||||
if ".png" in texture_paths_array[i].lower():
|
||||
textureFormat = "ASTC_6x6_UNORM"
|
||||
if identifiers_array[i].upper() in metadata.keys():
|
||||
textureFormat = getFormatByIndex(metadata[identifiers_array[i].upper()]["format"])
|
||||
tasks.append(asyncio.ensure_future(encode_astc(texture_paths_array[i], textureFormat)))
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(asyncio.gather(*tasks))
|
||||
|
||||
# Open every DDS texture
|
||||
for i in range(textureCount):
|
||||
path = texture_paths_array[i].replace(".png", ".astc")
|
||||
|
||||
#wtaTextureIdentifier
|
||||
wtaTextureIdentifier[i] = identifiers_array[i]
|
||||
wtaTextureOffset[i] = wtp_fp.tell()
|
||||
|
||||
# Default infos
|
||||
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],
|
||||
"arrayCount": 1
|
||||
}
|
||||
|
||||
if ".dds" in path.lower():
|
||||
dds_fp = open(path, 'rb')
|
||||
dds_paddedSize = os.stat(texture_paths_array[i]).st_size
|
||||
|
||||
dds_fp.seek(12)
|
||||
info["width"] = int.from_bytes(dds_fp.read(4), "little")
|
||||
info["height"] = int.from_bytes(dds_fp.read(4), "little")
|
||||
|
||||
#checks dds dxt and cube map info
|
||||
dds_fp.seek(84)
|
||||
dxt = dds_fp.read(4)
|
||||
dds_fp.seek(112)
|
||||
cube = dds_fp.read(4)
|
||||
|
||||
|
||||
# DXT checking
|
||||
if info["format"] == 0x7D: # If not prefilled
|
||||
if dxt == b'DXT1':
|
||||
info["format"] = 0x46 # BC1_UNORM_SRGB
|
||||
elif dxt == b'DXT3':
|
||||
info["format"] = 0x47 # BC2_UNORM_SRGB
|
||||
elif dxt == b'DXT5':
|
||||
info["format"] = 0x48 # BC3_UNORM_SRGB
|
||||
else:
|
||||
info["format"] = 0x50 # BC6H_UF16
|
||||
|
||||
#unknownArray1
|
||||
unknownArray1[i] = 1677721632 # DDS textures are always SRGB
|
||||
wtaTextureOffset[i] = wtp_fp.tell()
|
||||
|
||||
dds_fp.seek(0x80)
|
||||
blockHeightLog2 = info["textureLayout"][0] & 7
|
||||
wtp_fp.write(compressImageData(
|
||||
getFormatByIndex(info['format']),
|
||||
info['width'],
|
||||
info['height'],
|
||||
info['depth'],
|
||||
info['arrayCount'],
|
||||
info['mipCount'],
|
||||
dds_fp.read(),
|
||||
blockHeightLog2
|
||||
))
|
||||
wtaTextureSize[i] = wtp_fp.tell() - wtaTextureOffset[i]
|
||||
if wtaTextureSize[i] < 90112:
|
||||
wtaTextureSize[i] = 90112
|
||||
while wtp_fp.tell() < (wtaTextureSize[i] + wtaTextureOffset[i]):
|
||||
wtp_fp.write(b'\x00')
|
||||
info['imageSize'] = wtaTextureSize[i]
|
||||
|
||||
dds_fp.close()
|
||||
else:
|
||||
# ASTC
|
||||
astc_fp = open(path, 'rb')
|
||||
astc_fp.seek(7)
|
||||
info["width"] = int.from_bytes(astc_fp.read(3), "little")
|
||||
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'],
|
||||
info['height'],
|
||||
info['depth'],
|
||||
info['arrayCount'],
|
||||
info['mipCount'],
|
||||
astc_fp.read(),
|
||||
blockHeightLog2
|
||||
))
|
||||
wtaTextureSize[i] = wtp_fp.tell() - wtaTextureOffset[i]
|
||||
if wtaTextureSize[i] < 90112:
|
||||
wtaTextureSize[i] = 90112
|
||||
while wtp_fp.tell() < (wtaTextureSize[i] + wtaTextureOffset[i]):
|
||||
wtp_fp.write(b'\x00')
|
||||
info['imageSize'] = wtaTextureSize[i]
|
||||
|
||||
if "SRGB" in getFormatByIndex(info["format"]):
|
||||
unknownArray1[i] = (1677721632)
|
||||
else:
|
||||
unknownArray1[i] = (1610612768)
|
||||
|
||||
astc_fp.close()
|
||||
# Remove the temporary ASTC file
|
||||
os.remove(path)
|
||||
|
||||
# MipCount needs to be set to 1 -- TODO: find what's causing this
|
||||
#info["mipCount"] = int(math.log2(max(info["width"], info["height"]))) + 1
|
||||
textureInfoArray.append(info)
|
||||
wtp_fp.seek(math.ceil(wtp_fp.tell() / 16) * 16)
|
||||
|
||||
# Write everything
|
||||
wta_fp.write(b'WTB\x00')
|
||||
wta_fp.write(to_bytes(unknown04))
|
||||
wta_fp.write(to_bytes(textureCount))
|
||||
wta_fp.write(to_bytes(textureOffsetArrayOffset))
|
||||
wta_fp.write(to_bytes(textureSizeArrayOffset))
|
||||
wta_fp.write(to_bytes(unknownArrayOffset1))
|
||||
wta_fp.write(to_bytes(textureIdentifierArrayOffset))
|
||||
wta_fp.write(to_bytes(textureInfoArrayOffset))
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(wtaTextureOffset[i]))
|
||||
wta_fp.seek(textureSizeArrayOffset)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(wtaTextureSize[i]))
|
||||
wta_fp.seek(unknownArrayOffset1)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(unknownArray1[i]))
|
||||
wta_fp.seek(textureIdentifierArrayOffset)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(wtaTextureIdentifier[i]))
|
||||
|
||||
for i in range(textureCount):
|
||||
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"])
|
||||
while wta_fp.tell() % 16 != 0:
|
||||
wta_fp.write(b'\x00')
|
||||
|
||||
elif exportingForGame == "ASTRALCHAIN":
|
||||
# ASTRAL CHAIN
|
||||
|
||||
# Change offsets
|
||||
textureSizeArrayOffset = math.ceil((textureOffsetArrayOffset + (textureCount * 4)) / 0x20) * 0x20
|
||||
unknownArrayOffset1 = math.ceil((textureSizeArrayOffset + (textureCount * 4)) / 0x20) * 0x20
|
||||
textureIdentifierArrayOffset = math.ceil((unknownArrayOffset1 + (textureCount * 4)) / 0x20) * 0x20
|
||||
textureInfoArrayOffset = math.ceil((textureIdentifierArrayOffset + (textureCount * 4)) / 0x20) * 0x20
|
||||
|
||||
# Open metadata
|
||||
if metadata_path:
|
||||
with open(metadata_path, "r") as metadata_fp:
|
||||
metadata = json.load(metadata_fp)
|
||||
else:
|
||||
metadata = {}
|
||||
|
||||
# Encode all PNG files to ASTC (asynchronously for speed)
|
||||
tasks = []
|
||||
for i in range(textureCount):
|
||||
if ".png" in texture_paths_array[i].lower():
|
||||
textureFormat = "ASTC_4x4_UNORM"
|
||||
if identifiers_array[i].upper() in metadata.keys():
|
||||
textureFormat = getFormatByIndex(metadata[identifiers_array[i].upper()]["format"])
|
||||
tasks.append(asyncio.ensure_future(encode_astc(texture_paths_array[i], textureFormat)))
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(asyncio.gather(*tasks))
|
||||
|
||||
# Open every DDS texture
|
||||
for i in range(textureCount):
|
||||
path = texture_paths_array[i].replace(".png", ".astc")
|
||||
|
||||
#wtaTextureIdentifier
|
||||
wtaTextureIdentifier[i] = identifiers_array[i]
|
||||
wtaTextureOffset[i] = wtp_fp.tell()
|
||||
|
||||
# Default infos
|
||||
info = {
|
||||
"magic": b"XT1\x00",
|
||||
"unk1": 16777473, # 0x 01 01 00 01
|
||||
"imageSize": 0, # Uint64 identical to header (which is weird because the header is uint32, lol)
|
||||
"headerSize": 56, # Always 56
|
||||
"mipCount": 1,
|
||||
"type": 1,
|
||||
"format": 0x79,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"depth": 1,
|
||||
"unk4": 32, # Always 32 (?)
|
||||
"textureLayout": [1027, 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"]
|
||||
|
||||
if ".dds" in path.lower():
|
||||
dds_fp = open(path, 'rb')
|
||||
dds_paddedSize = os.stat(texture_paths_array[i]).st_size
|
||||
|
||||
dds_fp.seek(12)
|
||||
info["width"] = int.from_bytes(dds_fp.read(4), "little")
|
||||
info["height"] = int.from_bytes(dds_fp.read(4), "little")
|
||||
|
||||
#checks dds dxt and cube map info
|
||||
dds_fp.seek(84)
|
||||
dxt = dds_fp.read(4)
|
||||
dds_fp.seek(112)
|
||||
cube = dds_fp.read(4)
|
||||
|
||||
|
||||
# DXT checking
|
||||
if info["format"] == 0x79: # If not prefilled
|
||||
if dxt == b'DXT1':
|
||||
info["format"] = 0x46 # BC1_UNORM_SRGB
|
||||
elif dxt == b'DXT3':
|
||||
info["format"] = 0x47 # BC2_UNORM_SRGB
|
||||
elif dxt == b'DXT5':
|
||||
info["format"] = 0x48 # BC3_UNORM_SRGB
|
||||
else:
|
||||
info["format"] = 0x50 # BC6H_UF16
|
||||
|
||||
# Astral Chain uses no padding (i think); probably unnecessary
|
||||
|
||||
#unknownArray1
|
||||
unknownArray1[i] = 1677721632 # DDS textures are always SRGB
|
||||
wtaTextureOffset[i] = wtp_fp.tell()
|
||||
|
||||
dds_fp.seek(80)
|
||||
blockHeightLog2 = info["textureLayout"][0] & 7
|
||||
wtp_fp.write(compressImageData(
|
||||
getFormatByIndex(info['format']),
|
||||
info['width'],
|
||||
info['height'],
|
||||
info['depth'],
|
||||
info['arrayCount'],
|
||||
info['mipCount'],
|
||||
dds_fp.read(),
|
||||
blockHeightLog2
|
||||
))
|
||||
wtaTextureSize[i] = wtp_fp.tell() - wtaTextureOffset[i]
|
||||
if wtaTextureSize[i] < 90112:
|
||||
wtaTextureSize[i] = 90112
|
||||
while wtp_fp.tell() < (wtaTextureSize[i] + wtaTextureOffset[i]):
|
||||
wtp_fp.write(b'\x00')
|
||||
info['imageSize'] = wtaTextureSize[i]
|
||||
|
||||
dds_fp.close()
|
||||
else:
|
||||
# ASTC
|
||||
astc_fp = open(path, 'rb')
|
||||
astc_fp.seek(7)
|
||||
info["width"] = int.from_bytes(astc_fp.read(3), "little")
|
||||
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'],
|
||||
info['height'],
|
||||
info['depth'],
|
||||
info['arrayCount'],
|
||||
info['mipCount'],
|
||||
astc_fp.read(),
|
||||
blockHeightLog2
|
||||
))
|
||||
wtaTextureSize[i] = wtp_fp.tell() - wtaTextureOffset[i]
|
||||
if wtaTextureSize[i] < 90112:
|
||||
wtaTextureSize[i] = 90112
|
||||
while wtp_fp.tell() < (wtaTextureSize[i] + wtaTextureOffset[i]):
|
||||
wtp_fp.write(b'\x00')
|
||||
info['imageSize'] = wtaTextureSize[i]
|
||||
|
||||
if "SRGB" in getFormatByIndex(info["format"]):
|
||||
unknownArray1[i] = (1677721632)
|
||||
else:
|
||||
unknownArray1[i] = (1610612768)
|
||||
|
||||
astc_fp.close()
|
||||
# Remove the temporary ASTC file
|
||||
os.remove(path)
|
||||
|
||||
# MipCount needs to be set to 1 -- TODO: find what's causing this
|
||||
#info["mipCount"] = int(math.log2(max(info["width"], info["height"]))) + 1
|
||||
textureInfoArray.append(info)
|
||||
wtp_fp.seek(math.ceil(wtp_fp.tell() / 16) * 16)
|
||||
|
||||
# Write everything
|
||||
wta_fp.write(b'WTB\x00')
|
||||
wta_fp.write(to_bytes(unknown04))
|
||||
wta_fp.write(to_bytes(textureCount))
|
||||
wta_fp.write(to_bytes(textureOffsetArrayOffset))
|
||||
wta_fp.write(to_bytes(textureSizeArrayOffset))
|
||||
wta_fp.write(to_bytes(unknownArrayOffset1))
|
||||
wta_fp.write(to_bytes(textureIdentifierArrayOffset))
|
||||
wta_fp.write(to_bytes(textureInfoArrayOffset))
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(wtaTextureOffset[i]))
|
||||
wta_fp.seek(textureSizeArrayOffset)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(wtaTextureSize[i]))
|
||||
wta_fp.seek(unknownArrayOffset1)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(unknownArray1[i]))
|
||||
wta_fp.seek(textureIdentifierArrayOffset)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(to_bytes(wtaTextureIdentifier[i]))
|
||||
wta_fp.seek(textureInfoArrayOffset)
|
||||
for i in range(textureCount):
|
||||
wta_fp.write(textureInfoArray[i]["magic"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["unk1"])
|
||||
write_Int32(wta_fp, 0)
|
||||
write_Int32(wta_fp, textureInfoArray[i]["imageSize"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["headerSize"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["mipCount"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["type"])
|
||||
write_Int32(wta_fp, textureInfoArray[i]["format"])
|
||||
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]["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')
|
||||
|
||||
|
||||
wta_fp.close()
|
||||
wtp_fp.close()
|
||||
print('WTA + WTP Export Complete. :]}')
|
||||
@@ -1,14 +1,16 @@
|
||||
import string
|
||||
import os
|
||||
|
||||
from ...utils.util import ShowMessageBox
|
||||
|
||||
|
||||
def generate(context):
|
||||
def generate(context, exportingForGame):
|
||||
wta_data = context.scene.WTAMaterials
|
||||
|
||||
identifiers_array = []
|
||||
texture_paths_array = []
|
||||
albedo_indexes = []
|
||||
metadata_path = None
|
||||
|
||||
true_index = 0
|
||||
for index, texture in enumerate(wta_data):
|
||||
@@ -18,28 +20,33 @@ def generate(context):
|
||||
|
||||
# Check if identifier is 8 chars long
|
||||
if len( texture.texture_identifier) != 8:
|
||||
print('[!] WTA/WTP Export Error: A texture identifier is not 8 characters long.')
|
||||
ShowMessageBox('A texture identifier is not 8 characters long.', 'WTA/WTP Export Error', 'ERROR')
|
||||
return None, None, None
|
||||
print('[!] WTA/WTP Export Error: A texture identifier is not characters long.')
|
||||
ShowMessageBox('A texture identifier is not characters long.', 'WTA/WTP Export Error', 'ERROR')
|
||||
return None, None, None, None
|
||||
|
||||
# Check if identifier is valid hex
|
||||
if not all(c in string.hexdigits for c in texture.texture_identifier):
|
||||
print('[!] WTA/WTP Export Error: A texture identifier contains a non-hex character.')
|
||||
ShowMessageBox('A texture identifier contains a non-hex character.', 'WTA/WTP Export Error', 'ERROR')
|
||||
return None, None, None
|
||||
return None, None, None, None
|
||||
|
||||
# Assign Identifier.
|
||||
identifiers_array.append(texture.texture_identifier)
|
||||
|
||||
# Check if game metadata exists.
|
||||
xt1MetadataPath = os.path.join(*texture.texture_path.split('/')[:-1], "xt1_info.json")
|
||||
if exportingForGame == "ASTRALCHAIN" and not metadata_path and os.path.exists(xt1MetadataPath):
|
||||
metadata_path = xt1MetadataPath
|
||||
|
||||
# Check if path is valid and assign.
|
||||
if not texture.texture_path.lower().endswith('.dds'):
|
||||
if not texture.texture_path.lower().endswith('.dds') and not texture.texture_path.lower().endswith('.png'):
|
||||
if texture.parent_mat == "":
|
||||
print('[!] WTA/WTP Export Error: A manual ' + texture.texture_map_type + ' texture does not have a valid texture assigned.')
|
||||
ShowMessageBox('A manual ' + texture.texture_map_type + ' texture does not have a valid texture assigned.', 'WTA/WTP Export Error', 'ERROR')
|
||||
else:
|
||||
print('[!] WTA/WTP Export Error: A texture in material', texture.parent_mat, 'does not have a valid path assigned.')
|
||||
ShowMessageBox(texture.parent_mat + ' does not have a valid texture assigned to ' + texture.texture_map_type, 'WTA/WTP Export Error', 'ERROR')
|
||||
return None, None, None
|
||||
return None, None, None, None
|
||||
|
||||
texture_paths_array.append(texture.texture_path)
|
||||
|
||||
@@ -49,4 +56,4 @@ def generate(context):
|
||||
|
||||
true_index += 1
|
||||
|
||||
return identifiers_array, texture_paths_array, albedo_indexes
|
||||
return identifiers_array, texture_paths_array, albedo_indexes, metadata_path
|
||||
@@ -118,7 +118,7 @@ def handleAutoSetTextureWarnings(operatorSelf, warnings: List[str]):
|
||||
print("\n".join(warnings))
|
||||
|
||||
def isTextureTypeSupported(textureType: str) -> bool:
|
||||
for supportedTex in ['g_AlbedoMap', 'g_MaskMap', 'g_NormalMap', 'g_EnvMap', 'g_DetailNormalMap', 'g_IrradianceMap', 'g_CurvatureMap', 'g_SpreadPatternMap', 'g_LUT', 'g_LightMap', 'g_GradationMap', 'g_ParallaxMap']:
|
||||
for supportedTex in ['g_AlbedoMap', 'g_ColorMask', 'g_MaskMap', 'g_NormalMap', 'g_EnvMap', 'g_DetailNormalMap', 'g_IrradianceMap', 'g_CurvatureMap', 'g_SpreadPatternMap', 'g_LUT', 'g_LightMap', 'g_GradationMap', 'g_ParallaxMap']:
|
||||
if supportedTex in textureType:
|
||||
return True
|
||||
return False
|
||||
@@ -286,8 +286,8 @@ class ExportWTAOperator(bpy.types.Operator, ExportHelper):
|
||||
filter_glob: StringProperty(default="*.wta", options={'HIDDEN'})
|
||||
|
||||
def execute(self, context):
|
||||
from . import export_wta
|
||||
export_wta.main(context, self.filepath)
|
||||
from . import export_wta_wtp
|
||||
export_wta_wtp.main(context, self.filepath)
|
||||
return{'FINISHED'}
|
||||
|
||||
class FilepathSelector(bpy.types.Operator, ImportHelper):
|
||||
@@ -297,7 +297,7 @@ class FilepathSelector(bpy.types.Operator, ImportHelper):
|
||||
bl_options = {"UNDO"}
|
||||
|
||||
filename_ext = ".dds"
|
||||
filter_glob: StringProperty(default="*.dds", options={'HIDDEN'})
|
||||
filter_glob: StringProperty(default="*.dds,*.png", options={'HIDDEN'})
|
||||
|
||||
id : bpy.props.IntProperty(options={'HIDDEN'})
|
||||
|
||||
@@ -602,7 +602,7 @@ class WTA_WTP_PT_Hints(bpy.types.Panel):
|
||||
row = box.row()
|
||||
row.label(text='- Texture identifier has to be 8 HEX characters long.')
|
||||
row = box.row()
|
||||
row.label(text='- Textures have to be in DDS format (DXT1, DXT3, DXT5).')
|
||||
row.label(text='- Textures have to be in DDS format (DXT1, DXT3, DXT5) for NieR, or PNG (converting to ASTC) for Switch games.')
|
||||
row = box.row()
|
||||
row.label(text='- It is recommended to "Sync Identifiers in Materials" before WMB export.')
|
||||
|
||||
|
||||
@@ -2,7 +2,13 @@ import os
|
||||
import bpy
|
||||
from bpy.props import StringProperty
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from struct import pack
|
||||
import subprocess
|
||||
import asyncio
|
||||
import json
|
||||
from ...utils import ioUtils as io
|
||||
from ..tegrax1swizzle import getFormatByIndex, getFormatTable, loadImageData
|
||||
|
||||
|
||||
class WTAData:
|
||||
def __init__(self, f, wtpFile) -> None:
|
||||
@@ -41,14 +47,54 @@ class WTAData:
|
||||
self.idx.append(io.read_uint32(f))
|
||||
|
||||
# Texture Info
|
||||
"""
|
||||
f.seek(self.offsetTextureInfo)
|
||||
self.infos = []
|
||||
for i in range(self.num_files):
|
||||
info = []
|
||||
info.append(io.read_uint32(f))
|
||||
info.append([io.read_uint32(f) for x in range(4)])
|
||||
self.infos.append(info)
|
||||
"""
|
||||
|
||||
infoFormat = f.read(4)
|
||||
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):
|
||||
info = {
|
||||
"magic": f.read(4),
|
||||
"unk1": io.read_uint32(f),
|
||||
"imageSize": io.read_uint64(f),
|
||||
"headerSize": io.read_uint32(f),
|
||||
"mipCount": io.read_uint32(f),
|
||||
"type": io.read_uint32(f),
|
||||
"format": io.read_uint32(f),
|
||||
"width": io.read_uint32(f),
|
||||
"height": io.read_uint32(f),
|
||||
"depth": io.read_uint32(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
|
||||
self.type = "TEX"
|
||||
for i in range(self.num_files):
|
||||
f.seek(self.offsetTextureInfo + i * 0x100)
|
||||
info = {
|
||||
"magic": f.read(4),
|
||||
"format": io.read_uint32(f),
|
||||
"unk1": io.read_uint32(f),
|
||||
"width": io.read_uint32(f),
|
||||
"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),
|
||||
"type": 1,
|
||||
"textureLayout": [4, 0],
|
||||
"arrayCount": 1
|
||||
}
|
||||
self.infos.append(info)
|
||||
else:
|
||||
self.type = "PC"
|
||||
|
||||
self.data = wtpFile.read()
|
||||
|
||||
@@ -58,13 +104,132 @@ class WTAData:
|
||||
count = 0
|
||||
fileName = os.path.basename(self.wtaPath)
|
||||
dir = os.path.dirname(self.wtaPath)
|
||||
for i in range(self.num_files):
|
||||
os.makedirs(extractionDir, exist_ok=True)
|
||||
with open(os.path.join(extractionDir, f"{self.idx[i]:0>8X}.dds"), "wb") as f:
|
||||
f.write(self.data[self.offsets[i]:self.offsets[i]+self.sizes[i]])
|
||||
count += 1
|
||||
|
||||
if self.type == "PC":
|
||||
for i in range(self.num_files):
|
||||
os.makedirs(extractionDir, exist_ok=True)
|
||||
with open(os.path.join(extractionDir, f"{self.idx[i]:0>8X}.dds"), "wb") as f:
|
||||
f.write(self.data[self.offsets[i]:self.offsets[i]+self.sizes[i]])
|
||||
count += 1
|
||||
else:
|
||||
# Switch games must construct the DDS/ASTC headers manually.
|
||||
tasks = []
|
||||
for i in range(self.num_files):
|
||||
os.makedirs(extractionDir, exist_ok=True)
|
||||
# Unswizzle
|
||||
textureFormat = getFormatByIndex(self.infos[i]["format"])
|
||||
blockHeightLog2 = self.infos[i]["textureLayout"][0] & 7
|
||||
texture = loadImageData(
|
||||
textureFormat,
|
||||
self.infos[i]['width'],
|
||||
self.infos[i]['height'],
|
||||
self.infos[i]['depth'],
|
||||
self.infos[i]['arrayCount'],
|
||||
self.infos[i]['mipCount'],
|
||||
self.data[self.offsets[i]:self.offsets[i]+self.sizes[i]],
|
||||
blockHeightLog2
|
||||
)
|
||||
|
||||
# Construct headers
|
||||
if "ASTC" in textureFormat:
|
||||
# ASTC
|
||||
formatInfo = getFormatTable(textureFormat)
|
||||
with open(os.path.join(extractionDir, f"{self.idx[i]:0>8X}.astc"), "wb") as f:
|
||||
f.write(b''.join([
|
||||
b'\x13\xAB\xA1\x5C', formatInfo[1].to_bytes(1, "little"),
|
||||
formatInfo[2].to_bytes(1, "little"), b'\1',
|
||||
self.infos[i]['width'].to_bytes(3, "little"),
|
||||
self.infos[i]['height'].to_bytes(3, "little"), b'\1\0\0',
|
||||
texture,
|
||||
]))
|
||||
|
||||
tasks.append(asyncio.ensure_future(decode_astc(os.path.join(extractionDir, f"{self.idx[i]:0>8X}.astc"))))
|
||||
else:
|
||||
# DDS
|
||||
headerDataObject = DDSHeader(textureFormat, self.infos[i]['width'], self.infos[i]['height'], self.infos[i]['depth'])
|
||||
with open(os.path.join(extractionDir, f"{self.idx[i]:0>8X}.dds"), "wb") as f:
|
||||
f.write(headerDataObject.save())
|
||||
f.write(texture)
|
||||
|
||||
count += 1
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(asyncio.gather(*tasks))
|
||||
|
||||
# Write metadata file (need to store format and textureLayout 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"]
|
||||
}
|
||||
with open(os.path.join(extractionDir, "xt1_info.json"), "w") as f:
|
||||
f.write(json.dumps(metadata, indent=4))
|
||||
|
||||
return count
|
||||
|
||||
# Asynchronously decodes an ASTC file to PNG.
|
||||
async def decode_astc(filePath):
|
||||
script_file = os.path.realpath(__file__)
|
||||
directory = os.path.dirname(script_file).replace("importer", "")
|
||||
appPath = os.path.join(directory, "astcenc-avx2.exe")
|
||||
result = subprocess.run([appPath, "-ds", filePath, filePath.replace('.astc', '.png')], cwd=directory)
|
||||
os.remove(filePath)
|
||||
|
||||
# WAY too lazy to reimplement this; remind me later i guess?
|
||||
class DDSHeader(object):
|
||||
# https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
|
||||
class DDSPixelFormat(object):
|
||||
def __init__(self, textureFormat):
|
||||
self.size = 32
|
||||
self.flags = 4 # contains fourcc
|
||||
if textureFormat == "BC6H_UF16":
|
||||
self.fourCC = b'DX10'
|
||||
elif textureFormat.startswith("BC1"):
|
||||
self.fourCC = b'DXT1'
|
||||
elif textureFormat.startswith("BC2"):
|
||||
self.fourCC = b'DXT3'
|
||||
else:
|
||||
self.fourCC = b'DXT5'
|
||||
# BC1 = DXT1, BC2 = DXT3, above is DXT5 i think; BC6H is the only DX10 format
|
||||
self.RGBBitCount = 0
|
||||
self.RBitMask = 0x00000000
|
||||
self.GBitMask = 0x00000000
|
||||
self.BBitMask = 0x00000000
|
||||
self.ABitMask = 0x00000000
|
||||
|
||||
def __init__(self, textureFormat, width, height, depth):
|
||||
self.magic = b'DDS\x20'
|
||||
self.size = 124
|
||||
self.flags = 0x1 + 0x2 + 0x4 + 0x1000 + 0x20000 + 0x80000 # Defaults (caps, height, width, pixelformat) + mipmapcount and linearsize
|
||||
self.height = height
|
||||
self.width = width
|
||||
self.format = textureFormat
|
||||
if self.format == "R8G8B8A8_UNORM":
|
||||
self.pitchOrLinearSize = ((self.width + 1) >> 1) * 4
|
||||
else:
|
||||
self.pitchOrLinearSize = int(max(1, ((self.width+3)/4) ) * getFormatTable(self.format)[0]) # https://docs.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide
|
||||
self.depth = depth
|
||||
self.mipmapCount = 1#texture.mipCount # Setting this to the normal value breaks everything, don't do that
|
||||
self.reserved1 = [0x00000000] * 11
|
||||
self.ddspf = self.DDSPixelFormat(textureFormat)
|
||||
self.caps = 4198408 # Defaults (DDSCAPS_TEXTURE) + mipmap and complex
|
||||
self.caps2 = 0
|
||||
self.caps3 = 0
|
||||
self.caps4 = 0
|
||||
self.reserved2 = 0
|
||||
|
||||
def save(self):
|
||||
output = self.magic + pack("20I4s10I", self.size, self.flags, self.height, self.width, self.pitchOrLinearSize, self.depth,
|
||||
self.mipmapCount, self.reserved1[0], self.reserved1[1], self.reserved1[2], self.reserved1[3], self.reserved1[4],
|
||||
self.reserved1[5], self.reserved1[6], self.reserved1[7], self.reserved1[8], self.reserved1[9], self.reserved1[10],
|
||||
self.ddspf.size, self.ddspf.flags, self.ddspf.fourCC, self.ddspf.RGBBitCount, self.ddspf.RBitMask, self.ddspf.GBitMask,
|
||||
self.ddspf.BBitMask, self.ddspf.ABitMask, self.caps, self.caps2, self.caps3, self.caps4, self.reserved2)
|
||||
if self.format == "BC6H_UF16":
|
||||
output += bytearray(b"\x5F\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00")
|
||||
return output
|
||||
|
||||
class ExtractNierWtaWtp(bpy.types.Operator, ImportHelper):
|
||||
'''Extract textures from WTA/WTP files'''
|
||||
bl_idname = "import_scene.nier_wta_wtp"
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
# TegraX1Swizzle.py - cabalex [Updated Dec 2022]
|
||||
# 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
|
||||
|
||||
formatTable = {
|
||||
"R8G8B8A8_UNORM": [4, 1, 1, 1],
|
||||
"BC1_UNORM": [8, 4, 4, 1],
|
||||
"BC2_UNORM": [16, 4, 4, 1],
|
||||
"BC3_UNORM": [16, 4, 4, 1],
|
||||
"BC4_UNORM": [8, 4, 4, 1],
|
||||
"BC1_UNORM_SRGB": [8, 4, 4, 1],
|
||||
"BC2_UNORM_SRGB": [16, 4, 4, 1],
|
||||
"BC3_UNORM_SRGB": [16, 4, 4, 1],
|
||||
"BC4_SNORM": [8, 4, 4, 1],
|
||||
"BC6H_UF16": [16, 4, 4, 1],
|
||||
"ASTC_4x4_UNORM": [16, 4, 4, 1],
|
||||
"ASTC_6x6_UNORM": [16, 6, 6, 1],
|
||||
"ASTC_8x8_UNORM": [16, 8, 8, 1],
|
||||
"ASTC_4x4_SRGB": [16, 4, 4, 1],
|
||||
"ASTC_6x6_SRGB": [16, 6, 6, 1],
|
||||
"ASTC_8x8_SRGB": [16, 8, 8, 1]
|
||||
}
|
||||
# each one: bytesPerPixel, blockWidth, blockHeight, blockDepth, targetBuffer (but i removed targetBuffer)
|
||||
|
||||
formats = {
|
||||
# DDS
|
||||
0x25: "R8G8B8A8_UNORM",
|
||||
0x42: "BC1_UNORM",
|
||||
0x43: "BC2_UNORM",
|
||||
0x44: "BC3_UNORM",
|
||||
0x45: "BC4_UNORM",
|
||||
0x46: "BC1_UNORM_SRGB",
|
||||
0x47: "BC2_UNORM_SRGB",
|
||||
0x48: "BC3_UNORM_SRGB",
|
||||
0x49: "BC4_SNORM",
|
||||
0x50: "BC6H_UF16",
|
||||
# ASTC (weird texture formats ??)
|
||||
0x2D: "ASTC_4x4_UNORM",
|
||||
0x38: "ASTC_8x8_UNORM",
|
||||
0x3A: "ASTC_12x12_UNORM",
|
||||
# ASTC
|
||||
0x79: "ASTC_4x4_UNORM",
|
||||
0x80: "ASTC_8x8_UNORM",
|
||||
0x87: "ASTC_4x4_SRGB",
|
||||
0x8E: "ASTC_8x8_SRGB",
|
||||
|
||||
# Unknown NieR switch formats
|
||||
0x7D: "ASTC_6x6_UNORM",
|
||||
0x8B: "ASTC_6x6_SRGB",
|
||||
}
|
||||
|
||||
def getFormatTable(_format):
|
||||
return formatTable[_format]
|
||||
|
||||
def getFormatByIndex(_format):
|
||||
return formats[_format]
|
||||
|
||||
def pow2_round_up(x):
|
||||
x -= 1
|
||||
x |= x >> 1
|
||||
x |= x >> 2
|
||||
x |= x >> 4
|
||||
x |= x >> 8
|
||||
x |= x >> 16
|
||||
return x + 1
|
||||
|
||||
def DIV_ROUND_UP(n, d):
|
||||
return (n + d - 1) // d
|
||||
|
||||
def subArray(data, offset, length):
|
||||
return data[offset:offset+length]
|
||||
|
||||
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):
|
||||
block_height = 1 << blockHeightLog2
|
||||
|
||||
width = DIV_ROUND_UP(width, blkWidth)
|
||||
height = DIV_ROUND_UP(height, blkHeight)
|
||||
depth = DIV_ROUND_UP(depth, blkDepth)
|
||||
|
||||
if tileMode == 1:
|
||||
if roundPitch == 1:
|
||||
pitch = round_up(width * bpp, 32)
|
||||
else:
|
||||
pitch = width * bpp
|
||||
surfSize = round_up(pitch * height, 32)
|
||||
|
||||
else:
|
||||
pitch = round_up(width * bpp, 64)
|
||||
surfSize = pitch * round_up(height, block_height * 8)
|
||||
|
||||
result = bytearray(surfSize)
|
||||
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
if tileMode == 1:
|
||||
pos = y * pitch + x * bpp
|
||||
|
||||
else:
|
||||
pos = getAddrBlockLinear(x, y, width, bpp, 0, block_height)
|
||||
|
||||
pos_ = (y * width + x) * bpp
|
||||
|
||||
if pos + bpp <= surfSize:
|
||||
if toSwizzle == 1:
|
||||
result[pos:pos + bpp] = data[pos_:pos_ + bpp]
|
||||
|
||||
else:
|
||||
result[pos_:pos_ + bpp] = data[pos:pos + bpp]
|
||||
size = width * height * bpp
|
||||
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)
|
||||
#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 getAddrBlockLinear(x, y, image_width, bytes_per_pixel, base_address, block_height):
|
||||
"""
|
||||
From the Tegra X1 TRM
|
||||
"""
|
||||
image_width_in_gobs = DIV_ROUND_UP(image_width * bytes_per_pixel, 64)
|
||||
|
||||
GOB_address = (base_address
|
||||
+ (y // (8 * block_height)) * 512 * block_height * image_width_in_gobs
|
||||
+ (x * bytes_per_pixel // 64) * 512 * block_height
|
||||
+ (y % (8 * block_height) // 8) * 512)
|
||||
|
||||
x *= bytes_per_pixel
|
||||
|
||||
Address = (GOB_address + ((x % 64) // 32) * 256 + ((y % 8) // 2) * 64
|
||||
+ ((x % 32) // 16) * 32 + (y % 2) * 16 + (x % 16))
|
||||
|
||||
return Address
|
||||
|
||||
def loadImageData(format: str, width: int, height: int, depth: int, arrayCount: int, mipCount: int, imageData, blockHeightLog2, target=1, linearTileMode=False):
|
||||
[bpp, blkWidth, blkHeight, blkDepth] = getFormatTable(format)
|
||||
blockHeight = DIV_ROUND_UP(height, blkHeight)
|
||||
pitch = 0
|
||||
dataAlignment = 512
|
||||
if linearTileMode:
|
||||
tileMode = 1
|
||||
else:
|
||||
tileMode = 0
|
||||
if depth > 1:
|
||||
numDepth = depth
|
||||
else:
|
||||
numDepth = 1
|
||||
linesPerBlockHeight = (1 << int(blockHeightLog2)) * 8
|
||||
arrayOffset = 0
|
||||
for depthLevel in range(numDepth):
|
||||
for arrayLevel in range(arrayCount):
|
||||
surfaceSize = 0
|
||||
blockHeightShift = 0
|
||||
mipOffsets = []
|
||||
for mipLevel in range(mipCount):
|
||||
width = max(1, width >> mipLevel)
|
||||
height = max(1, height >> mipLevel)
|
||||
depth = max(1, depth >> mipLevel)
|
||||
size = DIV_ROUND_UP(width, blkWidth) * DIV_ROUND_UP(height, blkHeight) * bpp
|
||||
if pow2_round_up(DIV_ROUND_UP(height, blkWidth)) < linesPerBlockHeight:
|
||||
blockHeightShift += 1
|
||||
width__ = DIV_ROUND_UP(width, blkWidth)
|
||||
height__ = DIV_ROUND_UP(height, blkHeight)
|
||||
|
||||
# calculate the mip size instead
|
||||
alignedData = bytearray(round_up(surfaceSize, dataAlignment) - surfaceSize)
|
||||
surfaceSize += len(alignedData)
|
||||
mipOffsets.append(surfaceSize)
|
||||
|
||||
# get the first mip offset and current one and the total image size
|
||||
msize = int((mipOffsets[0] + len(imageData) - mipOffsets[mipLevel]) / arrayCount)
|
||||
|
||||
data_ = subArray(imageData, arrayOffset + mipOffsets[mipLevel], msize)
|
||||
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_)
|
||||
|
||||
# the program creates a copy and uses that to remove unneeded data
|
||||
# yeah, i'm not doing that
|
||||
return result
|
||||
except Exception as e:
|
||||
raise e
|
||||
print(f"Failed to swizzle texture! {e}")
|
||||
return False
|
||||
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):
|
||||
bpp = formatTable[format][0]
|
||||
blkWidth = formatTable[format][1]
|
||||
blkHeight = formatTable[format][2]
|
||||
blkDepth = formatTable[format][3]
|
||||
blockHeight = DIV_ROUND_UP(height, blkHeight)
|
||||
pitch = 0
|
||||
dataAlignment = 512
|
||||
if linearTileMode:
|
||||
tileMode = 1
|
||||
else:
|
||||
tileMode = 0
|
||||
if depth > 1:
|
||||
numDepth = depth
|
||||
else:
|
||||
numDepth = 1
|
||||
linesPerBlockHeight = (1 << int(blockHeightLog2)) * 8
|
||||
arrayOffset = 0
|
||||
for depthLevel in range(numDepth):
|
||||
for arrayLevel in range(arrayCount):
|
||||
surfaceSize = 0
|
||||
blockHeightShift = 0
|
||||
mipOffsets = []
|
||||
for mipLevel in range(mipCount):
|
||||
width = max(1, width >> mipLevel)
|
||||
height = max(1, height >> mipLevel)
|
||||
depth = max(1, depth >> mipLevel)
|
||||
size = DIV_ROUND_UP(width, blkWidth) * DIV_ROUND_UP(height, blkHeight) * bpp
|
||||
if pow2_round_up(DIV_ROUND_UP(height, blkWidth)) < linesPerBlockHeight:
|
||||
blockHeightShift += 1
|
||||
width__ = DIV_ROUND_UP(width, blkWidth)
|
||||
height__ = DIV_ROUND_UP(height, blkHeight)
|
||||
|
||||
# calculate the mip size instead
|
||||
alignedData = bytearray(round_up(surfaceSize, dataAlignment) - surfaceSize)
|
||||
surfaceSize += len(alignedData)
|
||||
mipOffsets.append(surfaceSize)
|
||||
|
||||
# get the first mip offset and current one and the total image size
|
||||
msize = int((mipOffsets[0] + len(imageData) - mipOffsets[mipLevel]) / arrayCount)
|
||||
|
||||
data_ = subArray(imageData, arrayOffset + mipOffsets[mipLevel], msize)
|
||||
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_)
|
||||
|
||||
# the program creates a copy and uses that to remove unneeded data
|
||||
# yeah, i'm not doing that
|
||||
return result
|
||||
except Exception as e:
|
||||
raise e
|
||||
print(f"Failed to swizzle texture! {e}")
|
||||
return False
|
||||
arrayOffset += len(imageData) / arrayCount
|
||||
return False
|
||||
Reference in New Issue
Block a user