Jump to content

PS2 Swizzled texture


Recommended Posts

Hi everyone, i'm trying to translate a Bleach japanese game from PS2, worked on the text routine and now i want to change the font, i don't quite like it and doesn't fit the game IMO.

BUT, i can't edit it, tried Console Texture Explorer (Where i can edit 8bpp textures) but 4bpp i can't...

anyone knows how can i edit it?

font.zip

Edited by wsldecoded
Link to comment
Share on other sites

Yeah, Console Texture Explorer's 4-bpp swizzled format is different than this 4-bpp swizzled format. I think you could export/import it by calling some Python functions in ReverseBox or Noesis's imageUntwiddlePS2. Note, I used the C++ code below to convert it, just because that's what I'm familiar with.

Ikskoks also had a list of potential leads here: 

 

It appears to have 4 256x256 textures at different offsets (unsure where the pointers are). Here is the first one at 0x200.

image.png.ee624062da7d7e4c214513c02c93e368.png

...

    uint32_t rrw = width / 2;
    uint32_t rrh = height / 4;
    std::vector<uint8_t> tempInput(inputData.size_in_bytes());
    std::vector<uint8> outputData(inputData.size_in_bytes());
    writeTexPSMCT32(0, rrw / 64, 0, 0, rrw, rrh, inputData, tempInput);
    readTexPSMT4(0, width / 64, 0, 0, width, height, tempInput, outputData);

...

    int ps2_32bpp_mediumBlockIndices[32] =
    {
         0,  1,  4,  5, 16, 17, 20, 21,
         2,  3,  6,  7, 18, 19, 22, 23,
         8,  9, 12, 13, 24, 25, 28, 29,
        10, 11, 14, 15, 26, 27, 30, 31
    };


    int ps2_32bpp_smallBlockWordIndices[16] =
    {
        0,  1,  4,  5,  8,  9, 12, 13,
        2,  3,  6,  7, 10, 11, 14, 15
    };

    void writeTexPSMCT32(
        uint32_t basePointer,
        uint32_t pageBufferWidth, // in pages (2 = 16KB stride per page)
        uint32_t xStart,
        uint32_t yStart,
        uint32_t width,
        uint32_t height,
        std::span<uint8_t const> inputData,
        std::span<uint8> outputData
    )
    {
        uint32_t const* src = reinterpret_cast<uint32_t const*>(inputData.data());
        uint32_t startBlockPos = basePointer * 64;

        for (uint32_t y = yStart; y < yStart + height; ++y)
        {
            uint32_t pageY = y / 32;
            uint32_t py = y - (pageY * 32);
            uint32_t blockY = py / 8;
            uint32_t by = py - blockY * 8;
            uint32_t column = by / 2;
            uint32_t cy = by - column * 2;

            for (uint32_t x = xStart; x < xStart + width; ++x)
            {
                uint32_t pageX = x / 64;
                uint32_t page  = pageX + pageY * pageBufferWidth;
                uint32_t px = x - (pageX * 64);

                uint32_t blockX = px / 8;
                uint32_t block  = ps2_32bpp_mediumBlockIndices[blockX + blockY * 8];
                uint32_t bx = px - blockX * 8;

                uint32_t cx = bx;
                uint32_t cw = ps2_32bpp_smallBlockWordIndices[cx + cy * 8];

                uint32* dest = (uint32*)outputData.data();
                uint32_t const inputWordOffset = startBlockPos + page * 2048 + block * 64 + column * 16 + cw;
                dest[inputWordOffset] = *src;
                src++;
            }
        }
    }

    // 128x128 pixel blocks contain 32 32x16 blocks.
    constexpr uint8_t ps2_4bpp_mediumBlockIndices[32] =
    {
         0,  2,  8, 10,
         1,  3,  9, 11,
         4,  6, 12, 14,
         5,  7, 13, 15,
        16, 18, 24, 26,
        17, 19, 25, 27,
        20, 22, 28, 30,
        21, 23, 29, 31
    };

    // Maps the pixel x,y within a 32x4 block to uint32_t element index in VRAM.
    constexpr uint8_t ps2_4bpp_smallBlockWordIndices[2][128] =
    {
        {
             0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,
             2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,

             8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,
            10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7
        },
        {
             8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,
            10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,

             0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,
             2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15
        }
    };

    // Combine this 32x4 array with the array above (no point in having both arrays).
    constexpr uint8_t ps2_4bpp_smallBlockNybbleIndices[128] =
    {
        0, 0, 0, 0, 0, 0, 0, 0,  2, 2, 2, 2, 2, 2, 2, 2,  4, 4, 4, 4, 4, 4, 4, 4,  6, 6, 6, 6, 6, 6, 6, 6,
        0, 0, 0, 0, 0, 0, 0, 0,  2, 2, 2, 2, 2, 2, 2, 2,  4, 4, 4, 4, 4, 4, 4, 4,  6, 6, 6, 6, 6, 6, 6, 6,

        1, 1, 1, 1, 1, 1, 1, 1,  3, 3, 3, 3, 3, 3, 3, 3,  5, 5, 5, 5, 5, 5, 5, 5,  7, 7, 7, 7, 7, 7, 7, 7,
        1, 1, 1, 1, 1, 1, 1, 1,  3, 3, 3, 3, 3, 3, 3, 3,  5, 5, 5, 5, 5, 5, 5, 5,  7, 7, 7, 7, 7, 7, 7, 7
    };

    // Adapted from: https://github.com/neko68k/FusionTool/blob/3c90521fec34b9025baaa1bf9306736f289bdd66/PS2Textures.cpp#L784
    void readTexPSMT4(
        uint32_t basePointer,
        uint32_t pageBufferWidth, // in pages (2 = 16KB stride per page)
        uint32_t xStart,
        uint32_t yStart,
        uint32_t width,
        uint32_t height,
        std::span<uint8_t const> inputData,
        std::span<uint8> outputData
    )
    {
        uint32_t constexpr largeBlockByteSize  = 8192;    // called "pages"
        uint32_t constexpr mediumBlockByteSize = 256;     // called "blocks"
        uint32_t constexpr smallBlockByteSize  = 64;      // called "columns", even though they are rows.
        uint32_t constexpr largeBlockSizeX     = 128;
        uint32_t constexpr largeBlockSizeY     = 128;
        uint32_t constexpr mediumBlockSizeX    = 32;
        uint32_t constexpr mediumBlockSizeY    = 16;
        uint32_t constexpr smallBlockSizeX     = 32;
        uint32_t constexpr smallBlockSizeY     = 4;
        uint32_t constexpr mediumBlocksPerLargeBlockX = largeBlockSizeX / mediumBlockSizeX; // 4 = 128 / 32
        uint32_t constexpr smallBlocksPerMediumBlockY = mediumBlockSizeY / smallBlockSizeY; // 4 = 16 / 4

        uint32_t const pixelCount = width * height;
        uint32_t const xEnd = xStart + width;
        uint32_t const yEnd = yStart + height;
        assert(inputData.size() * 2 >= pixelCount);
        assert(outputData.size() * 2 >= pixelCount);

        pageBufferWidth >>= 1;
        uint8* outputPtr = outputData.data();
        uint32_t startBlockByteOffset = basePointer * mediumBlockByteSize;

        uint32_t outputNybbleShift = 0;
        uint32_t outputNybbleMask = 0x0F;

        for (uint32_t y = yStart; y < yEnd; ++y)
        {
            uint32_t pageY       = y & (largeBlockSizeY - 1);
            uint32_t blockY      = y & (mediumBlockSizeY - 1);
            uint32_t columnY     = y & (smallBlocksPerMediumBlockY - 1);
            uint32_t pageIndexY  = y / largeBlockSizeY;
            uint32_t blockIndexY = pageY / mediumBlockSizeY;
            uint32_t columnIndex = blockY / smallBlocksPerMediumBlockY;

            for (uint32_t x = xStart; x < xEnd; ++x)
            {
                uint32_t pageIndexX   = x / largeBlockSizeX;
                uint32_t pageIndex    = pageIndexX + pageIndexY * pageBufferWidth;
                uint32_t pageX        = x & (largeBlockSizeX - 1);

                uint32_t blockIndexX  = pageX / mediumBlockSizeX;
                uint32_t blockIndex   = ps2_4bpp_mediumBlockIndices[blockIndexX + blockIndexY * mediumBlocksPerLargeBlockX];
                uint32_t blockX       = x & (mediumBlockSizeX - 1);

                uint32_t columnX      = blockX;
                uint32_t columnWord   = ps2_4bpp_smallBlockWordIndices[columnIndex & 1][columnX + columnY * smallBlockSizeX];
                uint32_t columnNybble = ps2_4bpp_smallBlockNybbleIndices[columnX + columnY * smallBlockSizeX];

                uint32_t const inputByteOffset = startBlockByteOffset
                                               + pageIndex * largeBlockByteSize
                                               + blockIndex * mediumBlockByteSize
                                               + columnIndex * smallBlockByteSize
                                               + columnWord * sizeof(uint32)
                                               + (columnNybble >> 1); // nybble index / 2 to get byte offset
                assert(inputByteOffset < inputData.size_in_bytes());
                uint8_t inputValue = inputData[inputByteOffset];

                uint32_t inputNybbleShift = (columnNybble & 1) << 2; // Select high nybble (<<4) or low nybble (<<0).
                uint32_t newValue = (inputValue >> inputNybbleShift) << outputNybbleShift;
                uint8_t previousOutputValue = *outputPtr;
                *outputPtr = (previousOutputValue & ~outputNybbleMask) | (newValue & outputNybbleMask);

                // Flush the new value.
                if (outputNybbleShift)
                {
                    outputPtr++;
                }
                outputNybbleShift ^= 4;
                outputNybbleMask ^= 0xFF;
            }
        }
    }

 

 

Edited by piken
Link to comment
Share on other sites

On 9/4/2024 at 5:41 AM, piken said:

Yeah, Console Texture Explorer's 4-bpp swizzled format is different than this 4-bpp swizzled format. I think you could export/import it by calling some Python functions in ReverseBox or Noesis's imageUntwiddlePS2. Note, I used the C++ code below to convert it, just because that's what I'm familiar with.

Ikskoks also had a list of potential leads here: 

 

It appears to have 4 256x256 textures at different offsets (unsure where the pointers are). Here is the first one at 0x200.

image.png.ee624062da7d7e4c214513c02c93e368.png

...

    uint32_t rrw = width / 2;
    uint32_t rrh = height / 4;
    std::vector<uint8_t> tempInput(inputData.size_in_bytes());
    std::vector<uint8> outputData(inputData.size_in_bytes());
    writeTexPSMCT32(0, rrw / 64, 0, 0, rrw, rrh, inputData, tempInput);
    readTexPSMT4(0, width / 64, 0, 0, width, height, tempInput, outputData);

...

    int ps2_32bpp_mediumBlockIndices[32] =
    {
         0,  1,  4,  5, 16, 17, 20, 21,
         2,  3,  6,  7, 18, 19, 22, 23,
         8,  9, 12, 13, 24, 25, 28, 29,
        10, 11, 14, 15, 26, 27, 30, 31
    };


    int ps2_32bpp_smallBlockWordIndices[16] =
    {
        0,  1,  4,  5,  8,  9, 12, 13,
        2,  3,  6,  7, 10, 11, 14, 15
    };

    void writeTexPSMCT32(
        uint32_t basePointer,
        uint32_t pageBufferWidth, // in pages (2 = 16KB stride per page)
        uint32_t xStart,
        uint32_t yStart,
        uint32_t width,
        uint32_t height,
        std::span<uint8_t const> inputData,
        std::span<uint8> outputData
    )
    {
        uint32_t const* src = reinterpret_cast<uint32_t const*>(inputData.data());
        uint32_t startBlockPos = basePointer * 64;

        for (uint32_t y = yStart; y < yStart + height; ++y)
        {
            uint32_t pageY = y / 32;
            uint32_t py = y - (pageY * 32);
            uint32_t blockY = py / 8;
            uint32_t by = py - blockY * 8;
            uint32_t column = by / 2;
            uint32_t cy = by - column * 2;

            for (uint32_t x = xStart; x < xStart + width; ++x)
            {
                uint32_t pageX = x / 64;
                uint32_t page  = pageX + pageY * pageBufferWidth;
                uint32_t px = x - (pageX * 64);

                uint32_t blockX = px / 8;
                uint32_t block  = ps2_32bpp_mediumBlockIndices[blockX + blockY * 8];
                uint32_t bx = px - blockX * 8;

                uint32_t cx = bx;
                uint32_t cw = ps2_32bpp_smallBlockWordIndices[cx + cy * 8];

                uint32* dest = (uint32*)outputData.data();
                uint32_t const inputWordOffset = startBlockPos + page * 2048 + block * 64 + column * 16 + cw;
                dest[inputWordOffset] = *src;
                src++;
            }
        }
    }

    // 128x128 pixel blocks contain 32 32x16 blocks.
    constexpr uint8_t ps2_4bpp_mediumBlockIndices[32] =
    {
         0,  2,  8, 10,
         1,  3,  9, 11,
         4,  6, 12, 14,
         5,  7, 13, 15,
        16, 18, 24, 26,
        17, 19, 25, 27,
        20, 22, 28, 30,
        21, 23, 29, 31
    };

    // Maps the pixel x,y within a 32x4 block to uint32_t element index in VRAM.
    constexpr uint8_t ps2_4bpp_smallBlockWordIndices[2][128] =
    {
        {
             0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,
             2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,

             8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,
            10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7
        },
        {
             8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,   8,  9, 12, 13,  0,  1,  4,  5,
            10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,  10, 11, 14, 15,  2,  3,  6,  7,

             0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,   0,  1,  4,  5,  8,  9, 12, 13,
             2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15,   2,  3,  6,  7, 10, 11, 14, 15
        }
    };

    // Combine this 32x4 array with the array above (no point in having both arrays).
    constexpr uint8_t ps2_4bpp_smallBlockNybbleIndices[128] =
    {
        0, 0, 0, 0, 0, 0, 0, 0,  2, 2, 2, 2, 2, 2, 2, 2,  4, 4, 4, 4, 4, 4, 4, 4,  6, 6, 6, 6, 6, 6, 6, 6,
        0, 0, 0, 0, 0, 0, 0, 0,  2, 2, 2, 2, 2, 2, 2, 2,  4, 4, 4, 4, 4, 4, 4, 4,  6, 6, 6, 6, 6, 6, 6, 6,

        1, 1, 1, 1, 1, 1, 1, 1,  3, 3, 3, 3, 3, 3, 3, 3,  5, 5, 5, 5, 5, 5, 5, 5,  7, 7, 7, 7, 7, 7, 7, 7,
        1, 1, 1, 1, 1, 1, 1, 1,  3, 3, 3, 3, 3, 3, 3, 3,  5, 5, 5, 5, 5, 5, 5, 5,  7, 7, 7, 7, 7, 7, 7, 7
    };

    // Adapted from: https://github.com/neko68k/FusionTool/blob/3c90521fec34b9025baaa1bf9306736f289bdd66/PS2Textures.cpp#L784
    void readTexPSMT4(
        uint32_t basePointer,
        uint32_t pageBufferWidth, // in pages (2 = 16KB stride per page)
        uint32_t xStart,
        uint32_t yStart,
        uint32_t width,
        uint32_t height,
        std::span<uint8_t const> inputData,
        std::span<uint8> outputData
    )
    {
        uint32_t constexpr largeBlockByteSize  = 8192;    // called "pages"
        uint32_t constexpr mediumBlockByteSize = 256;     // called "blocks"
        uint32_t constexpr smallBlockByteSize  = 64;      // called "columns", even though they are rows.
        uint32_t constexpr largeBlockSizeX     = 128;
        uint32_t constexpr largeBlockSizeY     = 128;
        uint32_t constexpr mediumBlockSizeX    = 32;
        uint32_t constexpr mediumBlockSizeY    = 16;
        uint32_t constexpr smallBlockSizeX     = 32;
        uint32_t constexpr smallBlockSizeY     = 4;
        uint32_t constexpr mediumBlocksPerLargeBlockX = largeBlockSizeX / mediumBlockSizeX; // 4 = 128 / 32
        uint32_t constexpr smallBlocksPerMediumBlockY = mediumBlockSizeY / smallBlockSizeY; // 4 = 16 / 4

        uint32_t const pixelCount = width * height;
        uint32_t const xEnd = xStart + width;
        uint32_t const yEnd = yStart + height;
        assert(inputData.size() * 2 >= pixelCount);
        assert(outputData.size() * 2 >= pixelCount);

        pageBufferWidth >>= 1;
        uint8* outputPtr = outputData.data();
        uint32_t startBlockByteOffset = basePointer * mediumBlockByteSize;

        uint32_t outputNybbleShift = 0;
        uint32_t outputNybbleMask = 0x0F;

        for (uint32_t y = yStart; y < yEnd; ++y)
        {
            uint32_t pageY       = y & (largeBlockSizeY - 1);
            uint32_t blockY      = y & (mediumBlockSizeY - 1);
            uint32_t columnY     = y & (smallBlocksPerMediumBlockY - 1);
            uint32_t pageIndexY  = y / largeBlockSizeY;
            uint32_t blockIndexY = pageY / mediumBlockSizeY;
            uint32_t columnIndex = blockY / smallBlocksPerMediumBlockY;

            for (uint32_t x = xStart; x < xEnd; ++x)
            {
                uint32_t pageIndexX   = x / largeBlockSizeX;
                uint32_t pageIndex    = pageIndexX + pageIndexY * pageBufferWidth;
                uint32_t pageX        = x & (largeBlockSizeX - 1);

                uint32_t blockIndexX  = pageX / mediumBlockSizeX;
                uint32_t blockIndex   = ps2_4bpp_mediumBlockIndices[blockIndexX + blockIndexY * mediumBlocksPerLargeBlockX];
                uint32_t blockX       = x & (mediumBlockSizeX - 1);

                uint32_t columnX      = blockX;
                uint32_t columnWord   = ps2_4bpp_smallBlockWordIndices[columnIndex & 1][columnX + columnY * smallBlockSizeX];
                uint32_t columnNybble = ps2_4bpp_smallBlockNybbleIndices[columnX + columnY * smallBlockSizeX];

                uint32_t const inputByteOffset = startBlockByteOffset
                                               + pageIndex * largeBlockByteSize
                                               + blockIndex * mediumBlockByteSize
                                               + columnIndex * smallBlockByteSize
                                               + columnWord * sizeof(uint32)
                                               + (columnNybble >> 1); // nybble index / 2 to get byte offset
                assert(inputByteOffset < inputData.size_in_bytes());
                uint8_t inputValue = inputData[inputByteOffset];

                uint32_t inputNybbleShift = (columnNybble & 1) << 2; // Select high nybble (<<4) or low nybble (<<0).
                uint32_t newValue = (inputValue >> inputNybbleShift) << outputNybbleShift;
                uint8_t previousOutputValue = *outputPtr;
                *outputPtr = (previousOutputValue & ~outputNybbleMask) | (newValue & outputNybbleMask);

                // Flush the new value.
                if (outputNybbleShift)
                {
                    outputPtr++;
                }
                outputNybbleShift ^= 4;
                outputNybbleMask ^= 0xFF;
            }
        }
    }

 

 

hey thank you for the reply, i will test this tomorrow, sadly my PC broke, when i get it back i'll update :)

  • Sad 1
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...