@@ -147,12 +147,11 @@ def rename_texture(self, old_name: str, new_name: str):
147147 # -- DDS import ----------------------------------------------------------
148148
149149 def import_dds (self , name : str , dds_bytes : bytes ):
150- """Replace a texture's image data from a DDS file, in place .
150+ """Replace a texture's image data from a DDS file.
151151
152- The DDS must match the target texture's compression format family and
153- dimensions. The image data is re-swizzled and written over the existing
154- block-linear data, leaving the BNTX structure (string pool, relocation
155- table, pointers) untouched so the file stays valid.
152+ The DDS must match the target texture's compression format family.
153+ Dimensions and mip count may differ from the original; larger imports
154+ relocate image data to the end of the file.
156155 """
157156 import dds_io
158157 import texture_swizzle as tsw
@@ -163,9 +162,6 @@ def import_dds(self, name: str, dds_bytes: bytes):
163162 d = ptr + 0x10
164163
165164 cur_format = self ._read_fmt ("I" , d + self ._OFF_FORMAT )
166- cur_width = self ._read_fmt ("i" , d + self ._OFF_WIDTH )
167- cur_height = self ._read_fmt ("i" , d + self ._OFF_HEIGHT )
168- cur_mip_count = self ._read_fmt ("H" , d + self ._OFF_MIP_COUNT )
169165 cur_image_size = self ._read_fmt ("I" , d + self ._OFF_IMAGE_SIZE )
170166 cur_alignment = self ._read_fmt ("I" , d + self ._OFF_ALIGNMENT ) or 512
171167 tile_mode = self ._read_fmt ("H" , d + self ._OFF_TILE_MODE )
@@ -198,42 +194,50 @@ def import_dds(self, name: str, dds_bytes: bytes):
198194 f"Format mismatch: texture is { cur_key .upper ()} but DDS is { dds .key .upper ()} . "
199195 f"Re-export the DDS as { cur_key .upper ()} ."
200196 )
201- if dds .width != cur_width or dds .height != cur_height :
202- raise BntxImportError (
203- f"Size mismatch: texture is { cur_width } x{ cur_height } but DDS is "
204- f"{ dds .width } x{ dds .height } . Resize the DDS to match."
205- )
206197
207198 bpb , blk_w , blk_h = dds .block_info
208199
209- # Use as many mips as fit in the existing data region (no structural growth).
210- max_mips = min (dds .mip_count , cur_mip_count )
211- combined , _offsets , bh_log2 , image_size = tsw .swizzle_surface_mipmaps (
200+ max_mips = dds .mip_count
201+ combined , offsets , bh_log2 , image_size = tsw .swizzle_surface_mipmaps (
212202 dds .width , dds .height , blk_w , blk_h , bpb , max_mips , dds .mips [:max_mips ], cur_alignment
213203 )
214204
215205 data_base = self ._data_base (d )
216206 if data_base <= 0 or data_base + cur_image_size > len (self ._data ):
217207 raise BntxImportError ("Texture image data is outside the file bounds." )
218- if len (combined ) > cur_image_size :
219- raise BntxImportError (
220- "Imported data is larger than the original texture region; "
221- "replacing with more mip levels than the original is not supported."
222- )
223-
224- # Overwrite in place and zero any trailing slack so stale bytes do not leak.
225- self ._data [data_base : data_base + len (combined )] = combined
226- if len (combined ) < cur_image_size :
227- self ._data [data_base + len (combined ) : data_base + cur_image_size ] = b"\x00 " * (
228- cur_image_size - len (combined )
229- )
230208
231- # Refresh the (already-relocated) mip pointer values for the mips we wrote.
232209 ptr_array = self ._mip_ptr_array_addr (d )
210+
211+ if len (combined ) <= cur_image_size :
212+ # Overwrite in place and zero any trailing slack so stale bytes do not leak.
213+ self ._data [data_base : data_base + len (combined )] = combined
214+ if len (combined ) < cur_image_size :
215+ self ._data [data_base + len (combined ) : data_base + cur_image_size ] = b"\x00 " * (
216+ cur_image_size - len (combined )
217+ )
218+ write_base = data_base
219+ stored_image_size = image_size
220+ else :
221+ # Larger mip chain: relocate image data to the end of the file.
222+ align = cur_alignment or 512
223+ write_base = len (self ._data )
224+ pad = (align - (write_base % align )) % align
225+ if pad :
226+ self ._data .extend (b"\x00 " * pad )
227+ write_base = len (self ._data )
228+ self ._data .extend (combined )
229+ if image_size > len (combined ):
230+ self ._data .extend (b"\x00 " * (image_size - len (combined )))
231+ stored_image_size = image_size
232+ self .file_size = len (self ._data )
233+
233234 for i in range (max_mips ):
234- self ._write_fmt ("q" , ptr_array + i * 8 , data_base + _offsets [i ])
235+ self ._write_fmt ("q" , ptr_array + i * 8 , write_base + offsets [i ])
235236
237+ self ._write_fmt ("i" , d + self ._OFF_WIDTH , dds .width )
238+ self ._write_fmt ("i" , d + self ._OFF_HEIGHT , dds .height )
236239 self ._write_fmt ("H" , d + self ._OFF_MIP_COUNT , max_mips )
240+ self ._write_fmt ("I" , d + self ._OFF_IMAGE_SIZE , stored_image_size )
237241
238242 # Preserve the existing block-height layout bits, refresh the low 3 bits.
239243 layout = self ._read_fmt ("I" , d + self ._OFF_LAYOUT )
0 commit comments