Skip to content
View in the app

A better way to browse. Learn more.

ResHax

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
Help us keep the site running.
Zero Tolerance for Disrespect

PS2 Swizzled texture

Featured Replies

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

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

  • Author
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 :)

Create an account or sign in to comment

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.