xdanieldzd / GXTConvert

Somewhat rudimentary PS Vita GXT to PNG converter - UNMAINTAINED
Other
33 stars 6 forks source link

3d textures support? #5

Closed soywiz closed 7 years ago

soywiz commented 7 years ago

I have successfully extracted PsVita's Criminal Girls images/textures. (I'm already translating it into spanish, but I need to update the font and some textures to get a proper localization). It doesn't use gxt directly, but a custom format "imy"; that mixes a non-standard compression with swizzling for power of two textures. I have managed to determine the compression algorithm and to create a decompressor.

Most textures are normal 2d power-of-two textures, and I converted your PostProcessing.UnswizzleTexture to kotlin and works just fine decoding them. But some textures are swizzled and have a size that is something like 2048x512. I suspect this is instead a 512x512x4 image. And decoding with 2048x512, causes 3/4 of the image to be empty.

I don't have too much experience with Morton, but if I can remember right, the xbox 360 sdk had functions for translating swizzled address/x/y/z with 3d textures, but won't work directly for psvita as far as I know.

So do you know how to implement the 3d lookup?

Example of texture working fine: ev_ui_00 0000 imy

Example of texture 1024x512 that is cut. ca_ui_00 0001 imy

Font is cut too (2048x512) (font is white so it won't be displayed on a white background): font_00 imy

xdanieldzd commented 7 years ago

I have to admit, certain "more mathematical" parts of programming and reverse-engineering are lost on me, including Morton codes. The unswizzling code was originally written by @FireyFly in C - see the comments in PostProcessing.cs -, which I just ported to C# and adapted to GXTConvert.

Said C code refers to this site, which does show how to get the original X, Y and Z coordinates from Morton codes, but likely won't be sufficient in this case as you'll still need to adapt the code from UnswizzleTexture to support the Z coordinate.

Sorry that I'm not being much of a help here, but as I mentioned, this is something that's still somewhat beyond my abilities.

FireyFly commented 7 years ago

Oh, hmm.. I could take a look at the imy files and see if I can figure something out. Could you post the 1024×512 texture and the font texture as png's as they appear without any de-swizzling at all, just reading the pixels line-by-line? That's roughly what I worked with when I wrote the aforementioned unswizzling code. Either that, or the imy files + your decompressor would be neat. :)

soywiz commented 7 years ago

Thanks! I will put here the decompression code + some samples already decompressed, just give me an hour or so.

soywiz commented 7 years ago

https://github.com/talestra/criminalgirls/releases/download/sample1/criminalgirlsimy.7z

Ok. So this should do the work:

soywiz commented 7 years ago

FYI. The game as far as I discovered includes at least two texture formats:

And they are either swizzled or unswizzled. Unually non-pot + non-sqare are unswizzled. But some of them have POT sides but rectangular. And those are the ones that I suspect that are 3d textures. The height seems to be the side for the 2D and it must have width / height layers.

About the compression: It's LZ-like, specific to images, but a bit strange one. It splits the control codes and the data in two different chunks of memory instead of having them interleaved. I suspect they did this to support aligned reads from the data. Though vita ARMv7 support unligned access, original MIPS PSP did not. Codes support 3 kind of codes: read from uncompressed data, write one chunk from the generated output with an offset, and read one or more chunks from either (x,y offset of "chunks"), (-1,0), (0, -0), (-1,-1), (+1,-1). Each chunk is not a byte or a pixel, but a SHORT (16-bit) or an INT (32-bit). So for example, even on a palletized image, it uses the SHORT compression. So the LZ handles two pixels at once.

FireyFly commented 7 years ago

Hmm, got a bit distracted by other things this weekend and didn't really feel motivated to poke around with the IMY things, sorry--I'll take a closer look throughout this week when I have time.

That said, I didn't realise Criminal Girls was a NIS game--since it is, it doesn't really surprise me that the formats are similar to the ones I've looked at earlier for Disgaea DS. IMY in particular is used in D:DS too, so the compression is very familiar-looking :). This is also a really nice reminder that I really should put docs for things I've reversed up somewhere for others to use...

soywiz commented 7 years ago

Btw. I forgot to mention. Yesterday night I fixed this. But just remembered now, so I put the resolution here. It was not a 3d texture, but a plain non-pot 2d texture.

font_00 imy

I noticed that the X component was masked with the min edge size, so some information was lost. I tried several stuff, and ended appending that extra information after the Y component. And it worked. So this function:

public static byte[] UnswizzleTexture(byte[] pixelData, int width, int height, PixelFormat pixelFormat)
{
    int bytesPerPixel = (Bitmap.GetPixelFormatSize(pixelFormat) / 8);
    byte[] unswizzled = new byte[pixelData.Length];

    for (int i = 0; i < width * height; i++)
    {
        int min = width < height ? width : height;
        int k = (int)Math.Log(min, 2);

        int x, y;
        if (height < width)
        {
            // XXXyxyxyx → XXXxxxyyy
            int j = i >> (2 * k) << (2 * k)
                | (DecodeMorton2Y(i) & (min - 1)) << k
                | (DecodeMorton2X(i) & (min - 1)) << 0;
            x = j / height;
            y = j % height;
        }
        else
        {
            // YYYyxyxyx → YYYyyyxxx
            int j = i >> (2 * k) << (2 * k)
                | (DecodeMorton2X(i) & (min - 1)) << k
                | (DecodeMorton2Y(i) & (min - 1)) << 0;
            x = j % width;
            y = j / width;
        }

        if (y >= height || x >= width) continue;

        Buffer.BlockCopy(pixelData, i * bytesPerPixel, unswizzled, ((y * width) + x) * bytesPerPixel, bytesPerPixel);
    }

    return unswizzled;
}

Ending being this one (notice the optimizations):

Optimzations done:

public static byte[] UnswizzleTexture(byte[] pixelData, int width, int height, PixelFormat pixelFormat)
{
    int bytesPerPixel = (Bitmap.GetPixelFormatSize(pixelFormat) / 8);
    byte[] unswizzled = new byte[pixelData.Length];
    int min = width < height ? width : height;
    int k = (int)Math.Log(min, 2);
    int k2 = k * 2;
    int minMask = min - 1;
    int area = width * height;

    //if (y >= height || x >= width) continue; // Not required anymore

    if (height < width)
    {
        // XXXyxyxyx → XXXxxxyyy
        for (int i = 0; i < area; i++)
        {
            int mx = DecodeMorton2X(i);
            int my = DecodeMorton2Y(i);
            int j =  (i >> (2 * k) << (2 * k))
                 | ((my & minMask) << k)
                 | ((mx & minMask) << 0)
                 | ((mx & minMask) << k2)
                ;

            int x = j >> k;
            int y = j & minMask;

            Buffer.BlockCopy(pixelData, i * bytesPerPixel, unswizzled, ((y * width) + x) * bytesPerPixel, bytesPerPixel);
        }
    } else {
        // YYYyxyxyx → YYYyyyxxx
        for (int i = 0; i < area; i++)
        {

            int mx = DecodeMorton2X(i);
            int my = DecodeMorton2Y(i);

            int j = (i >> (2 * k) << (2 * k))
                    | ((mx & minMask) << k)
                    | ((my & minMask) << 0)
                    | ((my & minMask) << k2)
                    ;

            int x = j & minMask;
            int y = j >> k;

            Buffer.BlockCopy(pixelData, i * bytesPerPixel, unswizzled, ((y * width) + x) * bytesPerPixel, bytesPerPixel);
        }
    }

    return unswizzled;
}
FireyFly commented 7 years ago

Nice, glad you figured it out! Sorry I wasn't of more help. Regarding optimisation, I would imagine the compiler would be able to move loop-invariant code out of the loop, but neat nonetheless. :)

soywiz commented 7 years ago

Yup, probably a C compiler does, but I'm not that sure for .NET/JVM :) @xdanieldzd I'll do a PR with this so the whole project benefits from this.

xdanieldzd commented 7 years ago

Sorry for my late reply, I was busy over the last couple of days. Very nice that you figured this out and a PR would be much appreciated!

Eldinen commented 7 years ago

I know this issue is closed but I have been using the UnswizzleTexture methof and it is not Unswizzling this 2D texture

https://drive.google.com/file/d/0B2552rsEmf9wY0JjVklnbF9rblk/view?usp=sharing

Its a 1024x1024 and 8bpp, could you @soywiz help me with this?

soywiz commented 7 years ago

I'm a little busy right now and also don't remember this code. I have stopped my romhack/translations projects for a while now. I will probably have time in a couple of months, but not for sure. Maybe other people here can help you. Good luck!

Eldinen commented 7 years ago

Sure, thank you!

newdoria88 commented 6 years ago

Sorry for posting in this old thread. I'm trying to do some modding with criminal girls 2 which also uses imy format, but the link for the github project is down https://github.com/talestra/criminalgirls/releases/download/sample1/criminalgirlsimy.7z

Would it be possible to reupload it?

soywiz commented 6 years ago

https://github.com/talestra/talestrakt/blob/master/game-criminalgirls/src/com/talestra/criminalgirls/IMY.kt