Closed soywiz closed 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.
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. :)
Thanks! I will put here the decompression code + some samples already decompressed, just give me an hour or so.
https://github.com/talestra/criminalgirls/releases/download/sample1/criminalgirlsimy.7z
Ok. So this should do the work:
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.
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...
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.
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;
}
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. :)
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.
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!
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?
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!
Sure, thank you!
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?
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](https://cloud.githubusercontent.com/assets/570848/18221354/42924fa6-717c-11e6-9324-6e5b9d9e1a8f.png)
Example of texture 1024x512 that is cut.![ca_ui_00 0001 imy](https://cloud.githubusercontent.com/assets/570848/18221355/47734200-717c-11e6-9f02-37095ce0d8d1.png)
Font is cut too (2048x512) (font is white so it won't be displayed on a white background):![font_00 imy](https://cloud.githubusercontent.com/assets/570848/18221363/5d22c0da-717c-11e6-814a-b09c5b966fd5.png)