Jump to content

Unswizzling/Untiling Xbox 360 textures (Call of Duty 4 Modern Warfare)


0x6D6F

Recommended Posts

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.
image.thumb.png.5f8f8e98f9d11241094c687ec13843ac.png

My python script which was generated from this c++ untile example takes this image and gives me this output:
image.thumb.png.bfa939b794fc713b79f49ccfd434a52e.png

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.

 

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...