swizzleinator 0.2.1

Texture swizzling/deswizzling library
Documentation
// Adapted from https://github.com/bartlomiejduda/ReverseBox/blob/main/reversebox/image/swizzling/swizzle_x360.py

use super::{Deswizzler, Format, SwizzleError, Swizzler};

pub struct Xbox360;

impl Swizzler for Xbox360 {
    fn swizzle<T: Format>(
        source: &mut [u8],
        dest: &mut [u8],
        dimentions: (usize, usize, usize),
        format: T,
        align_resolution: bool,
    ) -> Result<(), SwizzleError> {
        x360::do_swizzle(source, dest, dimentions, format, false, align_resolution)
    }
}

impl Deswizzler for Xbox360 {
    fn deswizzle<T: Format>(
        source: &mut [u8],
        dest: &mut [u8],
        dimentions: (usize, usize, usize),
        format: T,
        align_resolution: bool,
    ) -> Result<(), SwizzleError> {
        x360::do_swizzle(source, dest, dimentions, format, true, align_resolution)
    }
}

mod x360 {
    use crate::swizzle::{Format, SwizzleError, TextureSlice};

    pub fn do_swizzle<T: Format>(
        source: &mut [u8],
        dest: &mut [u8],
        dimensions: (usize, usize, usize),
        format: T,
        unswizzle: bool,
        align_resolution: bool,
    ) -> Result<(), SwizzleError> {
        if format.x360_swap() {
            swap_byte_order_x360(source);
        }

        untile_x360_image_data(
            &source,
            dest,
            dimensions,
            format.pixel_block_size(),
            format.block_size(),
            unswizzle,
        )?;

        if format.x360_swap() {
            for chunk in dest.chunks_exact_mut(4) {
                let (x, y, z, w) = (chunk[1], chunk[2], chunk[3], chunk[0]);
                chunk[0] = x;
                chunk[1] = y;
                chunk[2] = z;
                chunk[3] = w;
            }
        }
        Ok(())
    }

    pub fn swap_byte_order_x360(image_data: &mut [u8]) {
        for chunk in image_data.chunks_mut(2) {
            chunk.swap(0, 1);
        }
    }

    fn xg_address_2d_tiled_x(
        block_offset: usize,
        width_in_blocks: usize,
        texel_byte_pitch: usize,
    ) -> usize {
        let aligned_width = (width_in_blocks + 31) & !31;
        let log_bpp =
            (texel_byte_pitch >> 2) + ((texel_byte_pitch >> 1) >> (texel_byte_pitch >> 2));
        let offset_byte = block_offset << log_bpp;
        let offset_tile =
            ((offset_byte & !0xFFF) >> 3) + ((offset_byte & 0x700) >> 2) + (offset_byte & 0x3F);
        let offset_macro = offset_tile >> (7 + log_bpp);

        let macro_x = (offset_macro % (aligned_width >> 5)) << 2;
        let tile = (((offset_tile >> (5 + log_bpp)) & 2) + (offset_byte >> 6)) & 3;
        let macro_ = (macro_x + tile) << 3;
        let micro = ((((offset_tile >> 1) & !0xF) + (offset_tile & 0xF))
            & ((texel_byte_pitch << 3) - 1))
            >> log_bpp;

        macro_ + micro
    }

    fn xg_address_2d_tiled_y(
        block_offset: usize,
        width_in_blocks: usize,
        texel_byte_pitch: usize,
    ) -> usize {
        let aligned_width = (width_in_blocks + 31) & !31;
        let log_bpp =
            (texel_byte_pitch >> 2) + ((texel_byte_pitch >> 1) >> (texel_byte_pitch >> 2));
        let offset_byte = block_offset << log_bpp;
        let offset_tile =
            ((offset_byte & !0xFFF) >> 3) + ((offset_byte & 0x700) >> 2) + (offset_byte & 0x3F);
        let offset_macro = offset_tile >> (7 + log_bpp);

        let macro_y = (offset_macro / (aligned_width >> 5)) << 2;
        let tile = ((offset_tile >> (6 + log_bpp)) & 1) + ((offset_byte & 0x800) >> 10);
        let macro_ = (macro_y + tile) << 3;
        let micro = (((offset_tile & (((texel_byte_pitch << 6) - 1) & !0x1F))
            + ((offset_tile & 0xF) << 1))
            >> (3 + log_bpp))
            & !1;

        macro_ + micro + ((offset_tile & 0x10) >> 4)
    }

    fn untile_x360_image_data(
        image_data: &[u8],
        dest: &mut [u8],
        dimensions: (usize, usize, usize),
        block_pixel_size: usize,
        texel_byte_pitch: usize,
        deswizzle: bool,
    ) -> Result<(), SwizzleError> {
        let (image_width, image_height, image_depth) = dimensions;

        let width_in_blocks = image_width / block_pixel_size;
        let height_in_blocks = image_height / block_pixel_size;

        let padded_width_in_blocks = (width_in_blocks + 31) & !31;
        let padded_height_in_blocks = (height_in_blocks + 31) & !31;

        let slice_size = padded_width_in_blocks * padded_height_in_blocks * texel_byte_pitch;

        for slice in 0..image_depth {
            let Some(slice_src) = image_data.get(slice * slice_size..) else {
                return Err(SwizzleError::OutOfBounds(TextureSlice::Source));
            };

            let Some(slice_dest) = dest.get_mut(slice * slice_size..) else {
                return Err(SwizzleError::OutOfBounds(TextureSlice::Dest));
            };

            for j in 0..padded_height_in_blocks {
                for i in 0..padded_width_in_blocks {
                    let block_offset = j * padded_width_in_blocks + i;

                    let x = xg_address_2d_tiled_x(
                        block_offset,
                        padded_width_in_blocks,
                        texel_byte_pitch,
                    );

                    let y = xg_address_2d_tiled_y(
                        block_offset,
                        padded_width_in_blocks,
                        texel_byte_pitch,
                    );

                    let src_byte_offset = block_offset * texel_byte_pitch;

                    let dest_byte_offset = (y * width_in_blocks + x) * texel_byte_pitch;

                    if dest_byte_offset + texel_byte_pitch > slice_dest.len()
                        || src_byte_offset + texel_byte_pitch > slice_src.len()
                    {
                        continue;
                    }

                    if deswizzle {
                        match slice_src.get(src_byte_offset..src_byte_offset + texel_byte_pitch) {
                            Some(source) => {
                                if source.iter().all(|&b| b == 0) {
                                    continue;
                                }
                                slice_dest[dest_byte_offset..dest_byte_offset + texel_byte_pitch]
                                    .copy_from_slice(source);
                            }
                            None => {
                                continue;
                            }
                        }
                    } else {
                        match slice_src.get(dest_byte_offset..dest_byte_offset + texel_byte_pitch) {
                            Some(source) => {
                                slice_dest[src_byte_offset..src_byte_offset + texel_byte_pitch]
                                    .copy_from_slice(source);
                            }
                            None => {
                                continue;
                            }
                        }
                    }
                }
            }
        }
        Ok(())
    }
}