November 15, 20241 yr Semi related to this post but thought I'd make a new post instead of bumping that thread. I am completely new to image formats and currently down a rabbit hole learning about all shorts of shenanigans like swizzling and tiling. I am trying to view a texture a texture dumped from memory from the Xbox 360 version of COD4. Based on other metadata I know the width and height is 1024, 512. I am unsure of the format but I am fairly sure it is DXN or DXT1 DDS image. I don't have the DDS header of the pixel data so I have prepended a DXT1 header for myself. This is what it looks like without any work except for the prepended DDS header. My python script which was generated from this c++ untile example takes this image and gives me this output: Python script from pathlib import Path def xg_address_2d_tiled_x(block_offset, width_in_blocks, texel_byte_pitch): aligned_width = (width_in_blocks + 31) & ~31 log_bpp = (texel_byte_pitch >> 2) + ( (texel_byte_pitch >> 1) >> (texel_byte_pitch >> 2) ) offset_byte = block_offset << log_bpp offset_tile = ( ((offset_byte & ~0xFFF) >> 3) + ((offset_byte & 0x700) >> 2) + (offset_byte & 0x3F) ) offset_macro = offset_tile >> (7 + log_bpp) macro_x = (offset_macro % (aligned_width >> 5)) << 2 tile = (((offset_tile >> (5 + log_bpp)) & 2) + (offset_byte >> 6)) & 3 macro = (macro_x + tile) << 3 micro = ( (((offset_tile >> 1) & ~0xF) + (offset_tile & 0xF)) & ((texel_byte_pitch << 3) - 1) ) >> log_bpp return macro + micro def xg_address_2d_tiled_y(block_offset, width_in_blocks, texel_byte_pitch): aligned_width = (width_in_blocks + 31) & ~31 log_bpp = (texel_byte_pitch >> 2) + ( (texel_byte_pitch >> 1) >> (texel_byte_pitch >> 2) ) offset_byte = block_offset << log_bpp offset_tile = ( ((offset_byte & ~0xFFF) >> 3) + ((offset_byte & 0x700) >> 2) + (offset_byte & 0x3F) ) offset_macro = offset_tile >> (7 + log_bpp) macro_y = (offset_macro // (aligned_width >> 5)) << 2 tile = ((offset_tile >> (6 + log_bpp)) & 1) + ((offset_byte & 0x800) >> 10) macro = (macro_y + tile) << 3 micro = ( ( (offset_tile & ((texel_byte_pitch << 6) - 1 & ~0x1F)) + ((offset_tile & 0xF) << 1) ) >> (3 + log_bpp) ) & ~1 return macro + micro + ((offset_tile & 0x10) >> 4) def xbox_360_convert_to_linear_texture(data, pixel_width, pixel_height): dest_data = bytearray(len(data)) block_pixel_size = 4 texel_byte_pitch = 8 width_in_blocks = pixel_width // block_pixel_size height_in_blocks = pixel_height // block_pixel_size for j in range(height_in_blocks): for i in range(width_in_blocks): block_offset = j * width_in_blocks + i x = xg_address_2d_tiled_x(block_offset, width_in_blocks, texel_byte_pitch) y = xg_address_2d_tiled_y(block_offset, width_in_blocks, texel_byte_pitch) src_byte_offset = ( j * width_in_blocks * texel_byte_pitch + i * texel_byte_pitch ) dest_byte_offset = ( y * width_in_blocks * texel_byte_pitch + x * texel_byte_pitch ) if dest_byte_offset + texel_byte_pitch > len(dest_data): continue dest_data[dest_byte_offset : dest_byte_offset + texel_byte_pitch] = data[ src_byte_offset : src_byte_offset + texel_byte_pitch ] return dest_data filename = "viewhands_marine_gloves_col" dumped_image_data = bytearray(Path(filename).read_bytes()) pixel_width, pixel_height = 1024, 512 linear_texture_data = xbox_360_convert_to_linear_texture( dumped_image_data[0x80:], pixel_width, pixel_height ) dumped_image_data[0x80:] = linear_texture_data with open("generated_" + filename, "wb") as f: f.write(dumped_image_data) The untiling code looks to have worked but my source and generated images have weird colours, I think perhaps I am missing something or may be using the wrong DDS header but frankly I have no idea what I am doing at this moment. Any help would be appreciated! Here is the source file with the prepended DDS header.
November 15, 20241 yr Author Okay so replying to myself here, doing an endian swap of the bytes prior to the unswizzle results in the colours being closer to the real thing. Byte swap with no swizzle and DX1 DDS header def flip_byte_order_32bit(data: bytes): if len(data) % 4 != 0: raise ValueError( "Data length must be a multiple of 4 bytes for 32-bit flipping." ) flipped = bytearray() for i in range(0, len(data), 4): group = data[i : i + 4] flipped.extend(group[::-1]) # Reverse the 4-byte group return flipped Now doing the unswizzle on that gives me this Much closer but still not right.
November 15, 20241 yr Author Solution Okay so I worked it out, I was doing the byte swap on every 4-byte chunks instead of every 2-byte chunks. def flip_byte_order_16bit(data: bytes): if len(data) % 2 != 0: raise ValueError( "Data length must be a multiple of 2 bytes for 16-bit flipping." ) flipped = bytearray() for i in range(0, len(data), 2): group = data[i : i + 2] flipped.extend(group[::-1]) # Reverse the 2-byte group return flipped Reversing every 2 bytes works great Hopefully might be of use to someone every coming across this!
November 16, 20241 yr Supporter I believe that noesis has bult-in function for this case. "imageUntile360Raw" and "imageUntile360DXT"
Create an account or sign in to comment