Jump to content

C code to swizzle 4bpp ps2 textures


Go to solution Solved by ikskoks,

Recommended Posts

Hi everyone, I'm developing a tool to edit the textures of the NFS Carbon and Most Wanted games in your PlayStation 2 releases. Until now, this tool supports to open and rebuild the .tpk archives with the textures, visualize and export to the tm2 format all the textures with 4bpp and 8bpp, and also import a tm2 to a 8bpp texture replacing the original texture in the tpk archive. My goal now is to find an algorithm to swizzle again the 4bpp textures, so I can import a 4bpp texture to the tpk archive. I've found some C codes to swizzle 4bpp textures, but they won't work, only the 8bpp swizzling is working fine. There is a video in attachment where I can show the progress of tool.

Thanks in advance!

20240429_112840.zip

Link to comment
Share on other sites

  • Solution

Hi IksKoks, I've tested these codes and them only do the unswizzling, some of them I've already have here and unfortunately there is none to swizzle properly the 4bpp textures again. If I can send a sample of a 4bpp swizzled texture and a 4bpp unswizzled texture, can you create a swizzling algorithm to swizzle and leave it identical with the swizzled texture?

Link to comment
Share on other sites

Swizzling and unswizzling are bijective mirror operations. So if you have to code to successfully unswizzle, then you already have the code to reswizzle - just swap the reads and the writes of the linear offset and swizzled/twiddled offset.

BowsetteStandingOutOfPipeSwizzle.png

That said, I would like to see more real world 4-bpp samples either way, as I am coincidentally trying to figure out some issues in my swizzled texture diagnostics C++ app with non-square textures, seeing odd horizontal bands occurring vertically every 128 pixels (and both the linux no ip and FusionTool implementations appear to suffer from it).

Edited by piken
Link to comment
Share on other sites

The problem to me is, I don't know how to swap the read and the writes, the unswizzling is a complicated code, but in attachment there is 4 samples of textures with swizzle and without swizzle. The last 64 bytes of each sample are the colors pallete. The only data that need to be swizzled is the texture data. In this sample there is also the unswizzling code that I use to unswizzle the textures. If you can modify this code to swizzle again, I'll be grateful

samples.rar

Link to comment
Share on other sites

1 hour ago, Alpha 1001 said:

If you can modify this code to swizzle again, I'll be grateful.

Just swap the read and write:

out[swizzleid] = buffer[y * width + x];

--->

out[y * width + x] = buffer[swizzleid];

Thanks for the extra test data.

  • Like 1
Link to comment
Share on other sites

14 minutes ago, piken said:

Just swap the read and write:

out[swizzleid] = buffer[y * width + x];

--->

out[y * width + x] = buffer[swizzleid];

Thanks for the extra test data.

Thank you very much! If you want, I can send more test data and samples.

Link to comment
Share on other sites

1 hour ago, piken said:

> If you want, I can send more test data and samples.

Sure, especially desired are non-square sizes. Cheers (and good night :b).

Hi, almost all the textures of nfs mw are power of two, so, if I've understood good, a non-square size must be different. Well, I've found a texture with dimensions of 640x480, this texture is in attachment. About the swizzling code, only the texture with dimensions of 256x64 has been swizzled again after the swap of read and the writes. The textures with different dimensions won't work, unfortunatelly. I think it is needed to make more changes in that function.

unswizzled_640x480_8bpp.zip

  • Like 1
Link to comment
Share on other sites

Posted (edited)
On 4/30/2024 at 6:20 AM, Alpha 1001 said:

if I've understood good, a non-square size must be different.

A square texture has the same width and height. So I just hoped you had more 4-bpp swizzled cases that were not square, like the rectangular 4bpp_swizzled_256x64.

The 640x480 one is simple linear:

image.png.3b0d5146013ecdc0764c49937f22a04a.png

On 4/30/2024 at 6:20 AM, Alpha 1001 said:

The textures with different dimensions won't work, unfortunatelly

Which file are you having difficulty with specifically? This is 4bpp_swizzled_256x256:

image.png.5ce14791bcfc42499841575d03fbd760.png

Edited by piken
  • Like 1
Link to comment
Share on other sites

3 hours ago, piken said:

I hoped you had more 4-bpp swizzled cases that were not square, like the rectangular 4bpp_swizzled_256x64. The 640x480 one is simple linear:

image.png.3b0d5146013ecdc0764c49937f22a04a.png

Which file are you having difficulty with specifically? 4bpp_swizzled_256x256?

image.png.5ce14791bcfc42499841575d03fbd760.png

I am having difficult with all the unswizzled 4_bpp textures, I can't swizzle them again, only unswizzle them. I'll be going soon send more 4bpp samples for you, I don't know if these textures are non-square size because I didn't understood very well this concept, but I'll send.

Link to comment
Share on other sites

Posted (edited)
On 5/1/2024 at 8:20 AM, Alpha 1001 said:

I am having difficult with all the unswizzled 4_bpp textures

Let's focus on one specific file that you're having trouble with, because the ones I tried work. e.g.

4bpp_swizzled_256x64_original -> unswizzled -> reswizzled.

image.png.0a90e37eb990a5b90ae1fee367424e4e.png

9_256x64 4bpp unswizzled -> reswizzled -> unswizzled:

image.png.746247a0f55c92e10c1352180b6b1870.png --> image.png.501c53d7606d63f34db7ebb60c809738.png --> 

image.png.a7f366061def1e239a6cb70511d53f7c.png

image.png.547dde8bd0997bac6b5c47e4c637a183.png

5_128x128 4bpp unswizzled -> reswizzled -> unswizzled:

image.png.6ce7643ae740f9df26a138469c4a21df.png

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <memory.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>

void Unswizzle4(
    uint8_t const* inputBuffer, // (width * height + 1) / 2 bytes
    uint8_t* outputBuffer, // (width * height + 1) / 2 bytes
    uint32_t width,
    uint32_t height,
    bool swizzle // reswizzle instead of unswizzle
);
void Unswizzle8(
    uint8_t const* inputBuffer, // width * height bytes
    uint8_t* outputBuffer, // width * height bytes
    uint32_t width,
    uint32_t height,
    bool swizzle // reswizzle instead of unswizzle
);

// nullptr was added in C23, but most compilers do not support it yet.
#define nullptr NULL

int main(int argc, char* argv[])
{
    if (argc < 8)
    {
        printf(
            "Usage:\n"
            "    action bitdepth width height byteOffset inputFilename outputFilename\n"
            "       action: swizzle, unswizzle\n"
            "       bitdepth: 4, 8\n"
            "       byte offset: 0 to any valid position in file\n"
            "       file names: raw binary data\n"
            "\n"
            "e.g.:\n"
            "    swizzle 4 256 64 0 input.dat output.dat\n"
            "    unswizzle 8 128 128 0x40 input.bin output.bin\n"
        );
        return EXIT_FAILURE;
    }
    char const* action = argv[1];
    char const* bitDepthString = argv[2];
    char const* widthString = argv[3];
    char const* heightString = argv[4];
    char const* byteOffsetString = argv[5];
    char const* inputFilename = argv[6];
    char const* outputFilename = argv[7];

    bool swizzle = false;
    if (strcmp(action, "swizzle") == 0)
    {
        swizzle = true;
    }
    else if (strcmp(action, "unswizzle") == 0)
    {
        swizzle = false;
    }
    else
    {
        printf("Expect action \"swizzle\" or \"unswizzle\", not %s.", action);
        return EXIT_FAILURE;
    }

    uint32_t bitDepth = atoi(bitDepthString);
    if (bitDepth != 4 && bitDepth != 8)
    {
        printf("Expect bit depth of 4 or 8, not %s.", bitDepthString);
        return EXIT_FAILURE;
    }

    uint32_t width = atoi(widthString);
    if (width <= 0)
    {
        printf("Invalid width: %s.", widthString);
        return EXIT_FAILURE;
    }
    uint32_t height = atoi(heightString);
    if (height <= 0)
    {
        printf("Invalid height: %s.", heightString);
        return EXIT_FAILURE;
    }
    uint32_t byteOffset = strtoul(byteOffsetString, nullptr, 0);
    if (byteOffset == 0 && byteOffsetString[0] != '0')
    {
        printf("Invalid byte offset: %s.", byteOffsetString);
        return EXIT_FAILURE;
    }

    FILE* inputFile = fopen(inputFilename, "rb");
    if (inputFile == nullptr)
    {
        printf("Could not open input file: \"%s\"", inputFilename);
        return EXIT_FAILURE;
    }

    FILE* outputFile = fopen(outputFilename, "wb");
    if (outputFile == nullptr)
    {
        printf("Could not open output file: \"%s\"", outputFilename);
        return EXIT_FAILURE;
    }

    printf(
        "action:      %s\n"
        "bit depth:   %s\n"
        "width:       %s\n"
        "height:      %s\n"
        "byte offset: %s\n"
        "input:       %s\n"
        "output:      %s\n",
        action,
        bitDepthString,
        widthString,
        heightString,
        byteOffsetString,
        inputFilename,
        outputFilename
    );

    // Allocate buffers.
    fseek(inputFile, 0, SEEK_END);
    long inputFileSize = ftell(inputFile);
    fseek(inputFile, 0, SEEK_SET);
    uint32_t minimumBufferSize = bitDepth * width * height / CHAR_BIT;
    if ((uint32_t)inputFileSize < byteOffset + minimumBufferSize)
    {
        inputFileSize = byteOffset + minimumBufferSize;
    }
    uint8_t* inputFileData = (uint8_t*)malloc(inputFileSize);
    uint8_t* outputFileData = (uint8_t*)malloc(inputFileSize);

    // Read input data, perform conversion, write output data.
    fread(inputFileData, 1, inputFileSize, inputFile);
    fclose(inputFile);
    // Copy data before and after the pixels.
    memcpy(outputFileData, inputFileData, byteOffset);
    uint32_t byteOffsetAfterPixels = byteOffset + minimumBufferSize;
    memcpy(outputFileData + byteOffsetAfterPixels, inputFileData + byteOffsetAfterPixels, inputFileSize - byteOffsetAfterPixels);

    switch (bitDepth)
    {
    case 4: Unswizzle4(inputFileData + byteOffset, outputFileData + byteOffset, width, height, swizzle); break;
    case 8: Unswizzle8(inputFileData + byteOffset, outputFileData + byteOffset, width, height, swizzle); break;
    }

    fwrite(outputFileData, 1, inputFileSize, outputFile);
    fclose(outputFile);

    free(inputFileData);
    free(outputFileData);

    printf("Converted\n");

    return EXIT_SUCCESS;
}

void Unswizzle4(
    uint8_t const* inputBuffer, // (width * height + 1) / 2 bytes
    uint8_t* outputBuffer, // (width * height + 1) / 2 bytes
    uint32_t width,
    uint32_t height,
    bool swizzle
)
{
    uint8_t* inputPixels8bpp = (uint8_t*)malloc(width * height);
    uint8_t* outputPixels8bpp = (uint8_t*)malloc(width * height);
    const uint32_t bufferByteSize = (width * height + 1) / 2;

    // Unpack 4-bpp pixels to 8-bpp.
    for (uint32_t i = 0; i < bufferByteSize; ++i)
    {
        uint8_t byteValue = inputBuffer[i];
        uint8_t nybbleLow = byteValue & 0xF;
        uint8_t nybbleHigh = byteValue >> 4;
        inputPixels8bpp[i * 2] = nybbleLow;
        inputPixels8bpp[i * 2 + 1] = nybbleHigh;
    }

    // Swizzle/Unswizzle them as if 8-bpp.
    Unswizzle8(inputPixels8bpp, outputPixels8bpp, width, height, swizzle);

    // Repack 8-bpp pixels to 4-bpp.
    for (uint32_t i = 0; i < bufferByteSize; ++i)
    {
        uint8_t nybbleLow = outputPixels8bpp[i * 2 + 0];
        uint8_t nybbleHigh = outputPixels8bpp[i * 2 + 1];
        uint8_t byteValue = (nybbleHigh << 4) | nybbleLow;
        outputBuffer[i] = byteValue;
    }

    free(inputPixels8bpp);
    free(outputPixels8bpp);
}

void Unswizzle8(
    uint8_t const* inputBuffer, // width * height bytes
    uint8_t* outputBuffer, // width * height bytes
    uint32_t width,
    uint32_t height,
    bool swizzle
)
{
    uint32_t pixelCount = width * height;
    for (uint32_t y = 0; y < height; y++)
    {
        for (uint32_t x = 0; x < width; x++)
        {
            uint32_t blockLocation = (y & (~0xF)) * width + (x & (~0xF)) * 2;
            uint32_t swapSelector = (((y + 2) >> 2) & 0x1) * 4;
            uint32_t posY = (((y & (~3)) >> 1) + (y & 1)) & 0x7;
            uint32_t columnLocation = posY * width * 2 + ((x + swapSelector) & 0x7) * 4;
            uint32_t byteNumber = ((y >> 1) & 1) + ((x >> 2) & 2);
            uint32_t swizzleOffset = blockLocation + columnLocation + byteNumber;
            uint32_t linearOffset = y * width + x;

            if (linearOffset < pixelCount && swizzleOffset < pixelCount)
            {
                if (swizzle)
                {
                    outputBuffer[swizzleOffset] = inputBuffer[linearOffset];
                }
                else
                {
                    outputBuffer[linearOffset] = inputBuffer[swizzleOffset];
                }
            }
        }
    }
}

 

Edited by piken
  • Thanks 1
Link to comment
Share on other sites

Thankyou very much! I managed to do it a little while ago with that small change in the code that you suggested, changing the reading and writing operations, I think that before that I was entering some wrong parameter for the function, but now it's working fine, thanks for all the help.

  • Like 1
Link to comment
Share on other sites

Posted (edited)
2 hours ago, Alpha 1001 said:

Thankyou very much!

Welcome, and thank you for sharing the samples, as they were a new memory layout for me (evidently there are two different ways of storing swizzled 4bpp inside PS2 textures, the one like Dageron's Console Texture Explorer where 4bpp is swizzled as if it was 8bpp, and the true hardware 4bpp 🤔).

I think I'll publish the C code above as a small CLI utility, after adding the other 4bpp layout (plus the 16bpp and 32bpp layouts too, after finding some sample data ). Cheers.

Edited by piken
  • Like 1
Link to comment
Share on other sites

On 5/5/2024 at 10:52 AM, piken said:

Let's focus on one specific file that you're having trouble with, because the ones I tried work. e.g.

4bpp_swizzled_256x64_original -> unswizzled -> reswizzled.

image.png.0a90e37eb990a5b90ae1fee367424e4e.png

9_256x64 4bpp unswizzled -> reswizzled -> unswizzled:

image.png.746247a0f55c92e10c1352180b6b1870.png --> image.png.501c53d7606d63f34db7ebb60c809738.png --> 

image.png.a7f366061def1e239a6cb70511d53f7c.png

image.png.547dde8bd0997bac6b5c47e4c637a183.png

5_128x128 4bpp unswizzled -> reswizzled -> unswizzled:

image.png.6ce7643ae740f9df26a138469c4a21df.png

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <memory.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>

void Unswizzle4(
    uint8_t const* inputBuffer, // (width * height + 1) / 2 bytes
    uint8_t* outputBuffer, // (width * height + 1) / 2 bytes
    uint32_t width,
    uint32_t height,
    bool swizzle // reswizzle instead of unswizzle
);
void Unswizzle8(
    uint8_t const* inputBuffer, // width * height bytes
    uint8_t* outputBuffer, // width * height bytes
    uint32_t width,
    uint32_t height,
    bool swizzle // reswizzle instead of unswizzle
);

// nullptr was added in C23, but most compilers do not support it yet.
#define nullptr NULL

int main(int argc, char* argv[])
{
    if (argc < 8)
    {
        printf(
            "Usage:\n"
            "    action bitdepth width height byteOffset inputFilename outputFilename\n"
            "       action: swizzle, unswizzle\n"
            "       bitdepth: 4, 8\n"
            "       byte offset: 0 to any valid position in file\n"
            "       file names: raw binary data\n"
            "\n"
            "e.g.:\n"
            "    swizzle 4 256 64 0 input.dat output.dat\n"
            "    unswizzle 8 128 128 0x40 input.bin output.bin\n"
        );
        return EXIT_FAILURE;
    }
    char const* action = argv[1];
    char const* bitDepthString = argv[2];
    char const* widthString = argv[3];
    char const* heightString = argv[4];
    char const* byteOffsetString = argv[5];
    char const* inputFilename = argv[6];
    char const* outputFilename = argv[7];

    bool swizzle = false;
    if (strcmp(action, "swizzle") == 0)
    {
        swizzle = true;
    }
    else if (strcmp(action, "unswizzle") == 0)
    {
        swizzle = false;
    }
    else
    {
        printf("Expect action \"swizzle\" or \"unswizzle\", not %s.", action);
        return EXIT_FAILURE;
    }

    uint32_t bitDepth = atoi(bitDepthString);
    if (bitDepth != 4 && bitDepth != 8)
    {
        printf("Expect bit depth of 4 or 8, not %s.", bitDepthString);
        return EXIT_FAILURE;
    }

    uint32_t width = atoi(widthString);
    if (width <= 0)
    {
        printf("Invalid width: %s.", widthString);
        return EXIT_FAILURE;
    }
    uint32_t height = atoi(heightString);
    if (height <= 0)
    {
        printf("Invalid height: %s.", heightString);
        return EXIT_FAILURE;
    }
    uint32_t byteOffset = strtoul(byteOffsetString, nullptr, 0);
    if (byteOffset == 0 && byteOffsetString[0] != '0')
    {
        printf("Invalid byte offset: %s.", byteOffsetString);
        return EXIT_FAILURE;
    }

    FILE* inputFile = fopen(inputFilename, "rb");
    if (inputFile == nullptr)
    {
        printf("Could not open input file: \"%s\"", inputFilename);
        return EXIT_FAILURE;
    }

    FILE* outputFile = fopen(outputFilename, "wb");
    if (outputFile == nullptr)
    {
        printf("Could not open output file: \"%s\"", outputFilename);
        return EXIT_FAILURE;
    }

    printf(
        "action:      %s\n"
        "bit depth:   %s\n"
        "width:       %s\n"
        "height:      %s\n"
        "byte offset: %s\n"
        "input:       %s\n"
        "output:      %s\n",
        action,
        bitDepthString,
        widthString,
        heightString,
        byteOffsetString,
        inputFilename,
        outputFilename
    );

    // Allocate buffers.
    fseek(inputFile, 0, SEEK_END);
    long inputFileSize = ftell(inputFile);
    fseek(inputFile, 0, SEEK_SET);
    uint32_t minimumBufferSize = bitDepth * width * height / CHAR_BIT;
    if ((uint32_t)inputFileSize < byteOffset + minimumBufferSize)
    {
        inputFileSize = byteOffset + minimumBufferSize;
    }
    uint8_t* inputFileData = (uint8_t*)malloc(inputFileSize);
    uint8_t* outputFileData = (uint8_t*)malloc(inputFileSize);

    // Read input data, perform conversion, write output data.
    fread(inputFileData, 1, inputFileSize, inputFile);
    fclose(inputFile);
    // Copy data before and after the pixels.
    memcpy(outputFileData, inputFileData, byteOffset);
    uint32_t byteOffsetAfterPixels = byteOffset + minimumBufferSize;
    memcpy(outputFileData + byteOffsetAfterPixels, inputFileData + byteOffsetAfterPixels, inputFileSize - byteOffsetAfterPixels);

    switch (bitDepth)
    {
    case 4: Unswizzle4(inputFileData + byteOffset, outputFileData + byteOffset, width, height, swizzle); break;
    case 8: Unswizzle8(inputFileData + byteOffset, outputFileData + byteOffset, width, height, swizzle); break;
    }

    fwrite(outputFileData, 1, inputFileSize, outputFile);
    fclose(outputFile);

    free(inputFileData);
    free(outputFileData);

    printf("Converted\n");

    return EXIT_SUCCESS;
}

void Unswizzle4(
    uint8_t const* inputBuffer, // (width * height + 1) / 2 bytes
    uint8_t* outputBuffer, // (width * height + 1) / 2 bytes
    uint32_t width,
    uint32_t height,
    bool swizzle
)
{
    uint8_t* inputPixels8bpp = (uint8_t*)malloc(width * height);
    uint8_t* outputPixels8bpp = (uint8_t*)malloc(width * height);
    const uint32_t bufferByteSize = (width * height + 1) / 2;

    // Unpack 4-bpp pixels to 8-bpp.
    for (uint32_t i = 0; i < bufferByteSize; ++i)
    {
        uint8_t byteValue = inputBuffer[i];
        uint8_t nybbleLow = byteValue & 0xF;
        uint8_t nybbleHigh = byteValue >> 4;
        inputPixels8bpp[i * 2] = nybbleLow;
        inputPixels8bpp[i * 2 + 1] = nybbleHigh;
    }

    // Swizzle/Unswizzle them as if 8-bpp.
    Unswizzle8(inputPixels8bpp, outputPixels8bpp, width, height, swizzle);

    // Repack 8-bpp pixels to 4-bpp.
    for (uint32_t i = 0; i < bufferByteSize; ++i)
    {
        uint8_t nybbleLow = outputPixels8bpp[i * 2 + 0];
        uint8_t nybbleHigh = outputPixels8bpp[i * 2 + 1];
        uint8_t byteValue = (nybbleHigh << 4) | nybbleLow;
        outputBuffer[i] = byteValue;
    }

    free(inputPixels8bpp);
    free(outputPixels8bpp);
}

void Unswizzle8(
    uint8_t const* inputBuffer, // width * height bytes
    uint8_t* outputBuffer, // width * height bytes
    uint32_t width,
    uint32_t height,
    bool swizzle
)
{
    uint32_t pixelCount = width * height;
    for (uint32_t y = 0; y < height; y++)
    {
        for (uint32_t x = 0; x < width; x++)
        {
            uint32_t blockLocation = (y & (~0xF)) * width + (x & (~0xF)) * 2;
            uint32_t swapSelector = (((y + 2) >> 2) & 0x1) * 4;
            uint32_t posY = (((y & (~3)) >> 1) + (y & 1)) & 0x7;
            uint32_t columnLocation = posY * width * 2 + ((x + swapSelector) & 0x7) * 4;
            uint32_t byteNumber = ((y >> 1) & 1) + ((x >> 2) & 2);
            uint32_t swizzleOffset = blockLocation + columnLocation + byteNumber;
            uint32_t linearOffset = y * width + x;

            if (linearOffset < pixelCount && swizzleOffset < pixelCount)
            {
                if (swizzle)
                {
                    outputBuffer[swizzleOffset] = inputBuffer[linearOffset];
                }
                else
                {
                    outputBuffer[linearOffset] = inputBuffer[swizzleOffset];
                }
            }
        }
    }
}

 

Can you compile this code for me? thanks

  • Like 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...