Advertisement
Vearie

UnDXTBZ2.py

Jan 28th, 2024 (edited)
3,421
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.76 KB | None | 0 0
  1. VERSION = 1.02
  2.  
  3. import os, sys, glob
  4. from ctypes import Structure, c_uint32, c_int, c_uint, c_ubyte
  5. from struct import pack, unpack
  6.  
  7. # If True, output files will be "*.dds". Otherwise will be "*.dxtbz2.dds".
  8. REPLACE_EXT = True
  9.  
  10. # If True, only the largest mip map will be used when converting .dxtbz2 to .dds
  11. SINGLE_MIP = False
  12.  
  13. # If True, automatically overwrites existing output files
  14. OVERWRITE_EXISTING = False
  15.  
  16. DWORD = c_uint32
  17. DXGI_FORMAT = c_uint32
  18. D3D10_RESOURCE_DIMENSION = c_uint32
  19.  
  20. class InvalidFormat(Exception): pass
  21.  
  22. class DXTBZ2Header(Structure):
  23.     _fields_ = [
  24.         ("m_Sig", c_int),
  25.         ("m_DXTLevel", c_int),
  26.         ("m_1x1Red", c_ubyte),
  27.         ("m_1x1Green", c_ubyte),
  28.         ("m_1x1Blue", c_ubyte),
  29.         ("m_1x1Alpha", c_ubyte),
  30.         ("m_NumMips", c_int),
  31.         ("m_BaseHeight", c_int),
  32.         ("m_BaseWidth", c_int)
  33.     ]
  34.  
  35. class DDS_PIXELFORMAT(Structure):
  36.     _fields_ = [
  37.         ("dwSize", DWORD),
  38.         ("dwFlags", DWORD),
  39.         ("dwFourCC", DWORD),
  40.         ("dwRGBBitCount", DWORD),
  41.         ("dwRBitMask", DWORD),
  42.         ("dwGBitMask", DWORD),
  43.         ("dwBBitMask", DWORD),
  44.         ("dwABitMask", DWORD)
  45.     ]
  46.  
  47. class DDS_HEADER(Structure):
  48.     _fields_ = [
  49.         ("dwSize", DWORD),
  50.         ("dwFlags", DWORD),
  51.         ("dwHeight", DWORD),
  52.         ("dwWidth", DWORD),
  53.         ("dwPitchOrLinearSize", DWORD),
  54.         ("dwDepth", DWORD),
  55.         ("dwMipMapCount", DWORD),
  56.         ("dwReserved1", DWORD*11), # dwReserved1[11]
  57.         ("ddspf", DDS_PIXELFORMAT),
  58.         ("dwCaps", DWORD),
  59.         ("dwCaps2", DWORD),
  60.         ("dwCaps3", DWORD),
  61.         ("dwCaps4", DWORD),
  62.         ("dwReserved2", DWORD)
  63.     ]
  64.  
  65. class DDS_HEADER_DXT10(Structure):
  66.     _fields_ = [
  67.         ("dxgiFormat", DXGI_FORMAT),
  68.         ("resourceDimension", D3D10_RESOURCE_DIMENSION),
  69.         ("miscFlag", c_uint),
  70.         ("arraySize", c_uint),
  71.         ("miscFlags2", c_uint)
  72.     ]
  73.  
  74. # DDS_HEADER.dwFlags
  75. DDSD_CAPS = 0x1 # Required in every .dds file.
  76. DDSD_HEIGHT = 0x2 # Required in every .dds file.
  77. DDSD_WIDTH = 0x4 # Required in every .dds file.
  78. DDSD_PITCH = 0x8 # Required when pitch is provided for an uncompressed texture.
  79. DDSD_PIXELFORMAT = 0x1000 # Required in every .dds file.
  80. DDSD_MIPMAPCOUNT = 0x20000 # Required in a mipmapped texture.
  81. DDSD_LINEARSIZE = 0x80000 # Required when pitch is provided for a compressed texture.
  82. DDSD_DEPTH = 0x800000 # Required in a depth texture.
  83.  
  84. # DDS_PIXELFORMAT.dwFlags
  85. DDPF_ALPHAPIXELS = 0x1 # Texture contains alpha data; dwRGBAlphaBitMask contains valid data.
  86. DDPF_ALPHA = 0x2 # Used in some older DDS files for alpha channel only uncompressed data (dwRGBBitCount contains the alpha channel bitcount; dwABitMask contains valid data)
  87. DDPF_FOURCC = 0x4 # Texture contains compressed RGB data; dwFourCC contains valid data.
  88. DDPF_RGB = 0x40 # Texture contains uncompressed RGB data; dwRGBBitCount and the RGB masks (dwRBitMask, dwGBitMask, dwBBitMask) contain valid data.
  89. DDPF_YUV = 0x200 # Used in some older DDS files for YUV uncompressed data (dwRGBBitCount contains the YUV bit count; dwRBitMask contains the Y mask, dwGBitMask contains the U mask, dwBBitMask contains the V mask)
  90. DDPF_LUMINANCE = 0x20000 # Used in some older DDS files for single channel color uncompressed data (dwRGBBitCount contains the luminance channel bit count; dwRBitMask contains the channel mask). Can be combined with DDPF_ALPHAPIXELS for a two channel DDS file.
  91.  
  92. # dwCaps
  93. DDSCAPS_COMPLEX = 0x8 # Optional; must be used on any file that contains more than one surface (a mipmap, a cubic environment map, or mipmapped volume texture).
  94. DDSCAPS_TEXTURE = 0x1000 # Required
  95. DDSCAPS_MIPMAP = 0x400000 # Optional; should be used for a mipmap.
  96.  
  97.  
  98. def dxtbz2_to_dds(dxtbz2_path, dds_path):
  99.     with open(dxtbz2_path, "rb") as dxtbz2:
  100.         header = DXTBZ2Header()
  101.         dds_header = DDS_HEADER()
  102.         size = DWORD()
  103.        
  104.         dxtbz2.readinto(header)
  105.         dxtbz2.readinto(size)
  106.        
  107.         if pack(">I", header.m_Sig) != b"GSH!":
  108.             raise InvalidFormat("Bad Header Magic #")
  109.        
  110.         # How to derive proper dxgiFormat from dxtbz2?
  111.         has_alpha = size.value // header.m_BaseHeight == header.m_BaseHeight
  112.        
  113.         with open(dds_path, "wb") as dds:
  114.             dds_header.dwSize = 124
  115.             dds_header.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_MIPMAPCOUNT
  116.             dds_header.dwHeight = header.m_BaseHeight
  117.             dds_header.dwWidth = header.m_BaseWidth
  118.             dds_header.dwDepth = 0
  119.             dds_header.dwMipMapCount = 1 if SINGLE_MIP else header.m_NumMips
  120.             dds_header.dwCaps = DDSCAPS_TEXTURE
  121.            
  122.             if dds_header.dwMipMapCount > 1:
  123.                 dds_header.dwCaps |= DDSCAPS_COMPLEX
  124.                 dds_header.dwCaps |= DDSCAPS_MIPMAP
  125.            
  126.             dds_header.ddspf.dwSize = 32
  127.            
  128.             dds_header.ddspf.dwFlags = DDPF_FOURCC
  129.            
  130.             if has_alpha:
  131.                 dds_header.ddspf.dwFlags |= DDPF_ALPHAPIXELS
  132.            
  133.             dds_header.ddspf.dwFourCC = unpack("I", b"DX10")[0]
  134.            
  135.             # dds_header.ddspf.dwFourCC = unpack("I", b"\0\0\0\0")[0]
  136.             # dds_header.ddspf.dwRGBBitCount = 32
  137.             # dds_header.ddspf.dwRBitMask = 0x00FF0000 # Red
  138.             # dds_header.ddspf.dwGBitMask = 0x0000FF00 # Blue
  139.             # dds_header.ddspf.dwBBitMask = 0x000000FF # Green
  140.             # dds_header.ddspf.dwABitMask = 0xFF000000 # Alpha
  141.            
  142.             dds.write(b"DDS ")
  143.             dds.write(dds_header)
  144.            
  145.             if True: # FourCC is DX10
  146.                 dds_header_ex = DDS_HEADER_DXT10()
  147.                
  148.                 # https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format
  149.                 # dds_header_ex.dxgiFormat = 87 # DXGI_FORMAT_B8G8R8A8_UNORM = 87
  150.                 if has_alpha:
  151.                     dds_header_ex.dxgiFormat = 77 # DXGI_FORMAT_BC3_UNORM = 77 # Works for transparent textures
  152.                 else:
  153.                     dds_header_ex.dxgiFormat = 71 # DXGI_FORMAT_BC1_UNORM = 71 # Works for opaque textures
  154.                
  155.                 dds_header_ex.resourceDimension = 3 # DDS_DIMENSION_TEXTURE2D = 3
  156.                 # dds_header_ex.miscFlag = 0
  157.                 dds_header_ex.arraySize = 1
  158.                 # dds_header_ex.miscFlags2 = 0
  159.                
  160.                 dds.write(dds_header_ex)
  161.            
  162.             for mipmap_index in range(dds_header.dwMipMapCount):
  163.                 chunk_data = dxtbz2.read(size.value)
  164.                 # print("Writing Chunk %d/%d (%d bytes)" % (mipmap_index+1, dds_header.dwMipMapCount, len(chunk_data)))
  165.                 dds.write(chunk_data)
  166.                
  167.                 if not chunk_data or not dxtbz2:
  168.                     print("Unexpected end of data in %r" % dxtbz2_path)
  169.                     break
  170.                
  171.                 dxtbz2.readinto(size)
  172.  
  173. if __name__ == "__main__":
  174.     files = sys.argv[1::] # Convert files provided in command line if specified...
  175.    
  176.     # Otherwise convert all .dxtbz2 files in CWD
  177.     if not files:
  178.         files = glob.glob("*.dxtbz2")
  179.    
  180.     for file in files:
  181.         if REPLACE_EXT:
  182.             output_file = os.path.join(os.path.dirname(file), os.path.splitext(os.path.basename(file))[0] + ".dds")
  183.         else:
  184.             output_file = file + ".dds"
  185.        
  186.         # User safety - Do not overwrite any existing files.
  187.         if OVERWRITE_EXISTING or not os.path.exists(output_file):
  188.             print(">", file, "->", output_file)
  189.             try:
  190.                 dxtbz2_to_dds(file, output_file)
  191.             except InvalidFormat as e:
  192.                 print("Error Processing %r: %r" % (file, e))
  193.         else:
  194.             print("File %r already exists." % output_file)
  195.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement