Users browsing this thread: 5 Guest(s)
A Boy and His Blob Wii ANB files
#1
I stumbled on some of the anb files for this game while organizing my computer, and to my surprise they were actually pretty easy to read.
[Image: mmrZEaN.png][Image: T3Ei5yq.png]


The problem, however, rises with the trademark wayforward piece image together from texture system.
[Image: B3khLaS.png]

Unlike the other wayforward games I've worked with, this one is really confusing.
I was wondering if someone could take a look.

Here is the anvil file: https://www.dropbox.com/s/q1iv1gn3s1pokg...l.anb?dl=1

The area of interest is at 0x2C
That's a coordinate table with like 64 bytes per entry
Anvil has 3 entries
Those coordinate have to somehow point to the locations on that texture. I can find no correlation.

I feel like the 0xC1 and 0x41 are flags that tell you to do something to the next byte, but I don't know what.
That's about as far as I've managed.

Thank you for your time
Reply
Thanked by:
#2
I don't think 0xC1 and 0x41 (or 0xC2 and 0x42, for that matter) are flags. Every 64 bytes seems to be a collection of 16-byte chunks, each which contains 4 32-bit integers (looks like little-endian).
It looks to me like each chunk of 16 bytes contains coordinates to 2 pairs of something. The first pair looks like coordinates in the source image, and the second pair looks like coordinates for the same point in the destination image? Or something like that. I can see a correlation between the first value of each pair and the second value of each pair.

For example, let's look at the first entry (64-byte chunk). We'll name each 16-byte chunk A, B, C, and D. We use A1 to refer to the first 32-bit value in chunk A, A2 for the second, etc.

Look at A1. The D chunk has an identical value at D1. And we'll also see that D3 is the same as A3. So we can see that value 1 and 3 of each chunk are the same (at least within the same entry).
Now A2 and A4 aren't the same as D2 and D4. However, they are the same as B2 and B4. D2 and D4 are the same as C2 and C4.

So for each 16-byte chunk, values 1 and 2 seem to be some sort of X and Y, and values 3 and 4 seem to have a specific value for each X and Y (within in the same entry). This is at least what it looks like to me
(By the way I hope I'm not over-explaining this, I'm just afraid of under-explaining it.)

Of course the big issue to actually finding out what they mean is that these are such large values. Obviously, if these are points on the image then they need to be somehow manipulated to give us useful values.
You may have a fresh start any moment you choose, for this thing that we call "failure" is not the falling down, but the staying down. -Mary Pickford
Reply
Thanked by:
#3
Yes, that's the way all wayforward games format it. I know which value goes to what point, but I can't figure out how to calculate the values.
I noticed that the first byte is usually something like c0 c1 or whatever, so I figured the calculation may not be from actual values, but represented some other way.
Sorry, I don't know how to explain it very well.

For example, the adventure time anim coordinates required you to multiply 2 bytes together, then divide by 0xFFFF, then multiply by the dimensions of the texture. (Would you believe I figured that out through guess and check?) So I think there might be something similar, but it's hard to tell.

By the correlation, I mean I can't figure out how the image coordinates relate to the given values.
i.e. how does 0xC351 represent x = 1?

Tis confusing to me.

Edit:
Turns out I was right!.. but I was looking at the wrong bytes.
Then second chunks tell the location of the image on the texture.
The flags each have special functions and what not. I haven't figured them all out yet, but it's getting there.

So that leaves the first set. They tell the coordinates to draw the image taken from the texture to.
I'm not sure how those will be calculated. I feel like I'm missing a piece of information there.
Reply
Thanked by:
#4
(Apologies for slight thread necro)

WayForward tends to save texture offset and UV coordinates in 32-bit floating-point representation, so here's what I'm getting as values starting from 0x2C (After byte swapping from big endian):
Piece 1:
-22, 17, 0.0147059, 0.0208333, 26, 17, 0.720588, 0.0208333, 26, -27, 0.720588, 0.9375, -22, -27, 0.0147059, 0.9375
Piece 2:
-38, 14, 0.75, 0.0208333, -22, 14, 0.985294, 0.0208333, -22, -0, 0.985294, 0.3125, -38, -0, 0.75, 0.3125
Piece 3:
26, 14, 0.75, 0.354167, 32, 14, 0.838235, 0.354167, 32, -3, 0.838235, 0.708333, 26, -3, 0.75, 0.708333

The pieces are stored as:


Code:
typedef struct
{
    Vec2 topLeft;
    Vec2 topLeftUV;
    Vec2 topRight;
    Vec2 topRightUV;
    Vec2 bottomRight;
    Vec2 bottomRightUV;
    Vec2 bottomLeft;
    Vec2 bottomLeftUV;
} piece;

Where:


Code:
typedef struct
{
    float32 x;
    float32 y;
} Vec2;

Note that a lot of this info is redundant (Other titles I've worked with just stored top left and bottom right coordinates), hence the overlap you both noticed.

Negative values for the first fields are totally A-OK, as these are pixel coordinates are stored relative from the center of the final image. UV coordinates are in the range [0, 1] and are multiplied by the width and height of the input image to get input texture coordinates.

Whew. It took me a while to figure all this out, even though it was staring me in the face the whole time.

So just using our topLeft/Right and bottomLeft/Right info, our final image for the anvil will be in the form:
[Image: boyblobanvilUV.png]
Where 50, 50 in this image is 0, 0 as far as texture coordinates are calculated. Looks correct! All that remains is to multiply out the UV coordinates and piece the image together.

The issue here is that each piece is larger than drawn here. The main anvil chunk in the image you provided is 50x46, rather than the 48x44 result we get here. From a game engine standpoint, drawing it slightly smaller is totally easy, but this degrades the quality a bit if you don't scale it all up a tad.

Another small caveat is the repeated pixels on each side of the image, which you can see from the anvil image you posted. This is pretty standard from WayForward games (as you can see from the titles I've ripped, I just left those there cause I didn't feel like cropping every. single. image. And my calculations were a bit too off to crop the images programmatically).

There's enough similarities here to other WayForward titles that I can reuse a lot of my old code and whip up a program to extract the images and piece them together without too much hassle. I just couldn't figure out the image format itself (I suck at image formats), so I need either that explained to me, or you can just use this info to piece it all together yourself.

Have fun!

Oh, and from what I can tell, the first part of the ANB is a header in the form:


Code:
typedef struct
{
    uint32_t numImages;
    uint32_t unknown1;
    uint32_t pieceOffset;
    uint32_t imageOffset;
    uint16_t imageW;
    uint16_t imageH;
    uint8_t unknown0[8];
    uint16_t numPieces;
} anbHeader;


Again, all in big endian.
Reply
Thanked by: Ploaj, puggsoy
#5
That you so so much!
You have no idea how long I've been wanting to rip these sprites Big Grin

Edit:
Oh, I would be super mondo great-full if you could whip something up!

Edit:
Here is the quick notes on the header of the anim file.

4bytes - number of frames
4bytes - filesize
Then I wrote this, I don't know if it's accurate, but it should be close
Number of frames is the number of entries.

Code:
        // then there's a table with some entries
        /*
         * 4 bytes offset to frame data?
         * 4 bytes offset to image data
         * 2 bytes for width and height it seems
         * 6 bytes nothing??
         * 4 bytes num of layers
         * 2 bytes zeros padding?
         * 4 bytes offset to unknown data
         * 4 bytes end offset of unknown
         * 4 bytes another end offset?
         */

The image format:
It has been a while, but here's what I remember.

Image Offset is in the frame data. I think it's like the first or second set of 32bit values. In anvil it's at 0x0C.
It is LZE compressed. (Magic byte is 0x11)
The pixel data is weird. I'll edit the explanation once I find my code.

It's confusing to explain.
Could I just give you my code? It's written in java.
bytes is the decompressed pixel data. Width and height can be found in the header.
Code:
    public static BufferedImage makeTexture(int[] bytes, int width, int height) throws IOException{

        int[] buffer1 = new int[bytes.length / 2];
        int[] buffer2 = new int[bytes.length / 2];

        int i1 = 0, i2 = 0;
        int pointer = 0;

        while (i1 + i2 < bytes.length ) {
            for (int j = 0; j < 32; j++) {
                buffer1[i1++] = bytes[pointer++];
            }
            for (int j = 0; j < 32; j++) {
                buffer2[i2++] = bytes[pointer++];
            }
        }
        pointer = 0;

        BufferedImage img = new BufferedImage(width, height,BufferedImage.TYPE_INT_ARGB);

        int x = 0, y = 0;
        i1 = 0;
        i2 = 0;

        while (i1 + i2 < bytes.length) {
            for (int h = 0; h < 4; h++) {
                for (int w = 0; w < 4; w++) {
                    img.setRGB(x + w,y + h,makeColor(buffer2[i2++], buffer2[i2++],buffer1[i1++ + 1], buffer1[i1++ - 1]));
                }
            }
            x += 4;
            if (x >= img.getWidth()) {
                x = 0;
                y += 4;
            }
        }
        
        return img;

    }

Edit:
Oh, and the make color part
Code:
    public static int makeColor(int a, int r, int b, int g) {
        return (g << 24) | (b << 16) | (a << 8) | (r);
    }

Edit:
Lastly, here is the wayforward logo animation. It was among the ones that my script had the most trouble reading.
https://www.dropbox.com/s/kjzq1i3ur87lz8...d.anb?dl=1
If you can get it working, then it should work for all of them Smile
Reply
Thanked by:
#6
LZE compression? I'm having trouble finding much on that online. Can you link me to somewhere that explains it, or a library that uses it?

Thanks for the code; this'll help a lot.
Reply
Thanked by:
#7
I wrote my own decompression thing, but it's in java, so that would take some translation.
lze is lz11. That's probably why it was hard to find.

Here, you can use the code from 3DSExplorer.
https://github.com/svn2github/3DS-Explor...ro/LZ11.cs

Edit:
Also there's some source code here: http://www.romhacking.net/utilities/826/

Whichever.
Reply
Thanked by:
#8
Thanks, the romhacking one looks good.


Juuust double-checking here before I beat my head against the wall too much... I'm currently getting an output image like the following:
[Image: hud_anvil_1.png]
Now, being flipped is no issue, but the colors are quite off, and I want to figure out if this is an issue in your method or in the decompression algorithm (as I changed both).
Currently, my raw output from the decompression algorithm is this, which looks correct-ish? I guess? Just wanting to be sure here.

And in makeTexture(int[] bytes, int width, int height), are the bytes supposed to be 32-bit int? I'm assuming this is supposed to be actual bytes, as in 8 bits.

Thanks!
Reply
Thanked by:
#9
Yes, is was supposed to be a byte array. I have no idea why I made it int.
They are bytes, yes.

Looks like is was incorrectly decompressed.
Which code did you use?
The DS compression link I gave labeled the format as "LZX". The LZE one shown there is a completely different thing. I forgot about that.


Edit:
Here's what it should look like when decoded.
Reply
Thanked by:
#10
Well... both the above decompressors you linked give the exact same output, so mind sharing yours?  Tongue Java's not that far off from C.
Reply
Thanked by:
#11
Okay, sure!

It's a modified version of something. It returns an int array (Why didn't I use a bytes?).

Code:
    public static int[] decompress(int[] instream, long inLength){

        int pointer = 0;

        byte type = (byte) instream[pointer++];
        int decompressedSize = (instream[pointer++])
                | (instream[pointer++] << 8) | (instream[pointer++] << 16);

        int[] outstream = new int[decompressedSize];
        int outpointer = 0;

        if (decompressedSize == 0) {
            decompressedSize = (instream[pointer++])
                    | (instream[pointer++] << 8) | (instream[pointer++] << 16)
                    | (instream[pointer++] << 24);
            ;
        }

        int bufferLength = 0x1000;
        int[] buffer = new int[bufferLength];
        int bufferOffset = 0;

        int currentOutSize = 0;
        int flags = 0, mask = 1;

        while (currentOutSize < decompressedSize) {
            if (mask == 1) {
                flags = instream[pointer++];
                mask = 0x80;
            } else {
                mask >>= 1;
            }

            if ((flags & mask) > 0) {
                int byte1 = instream[pointer++];

                int length = byte1 >> 4;
                int disp = -1;

                if (length == 0) {
                    int byte2 = instream[pointer++];
                    int byte3 = instream[pointer++];

                    length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11;
                    disp = (((byte2 & 0x0F) << 8) | byte3) + 0x1;

                } else if (length == 1) {
                    int byte2 = instream[pointer++];
                    int byte3 = instream[pointer++];
                    int byte4 = instream[pointer++];

                    length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111;
                    disp = (((byte3 & 0x0F) << 8) | byte4) + 0x1;

                } else {
                    int byte2 = instream[pointer++];

                    length = ((byte1 & 0xF0) >> 4) + 0x1;
                    disp = (((byte1 & 0x0F) << 8) | byte2) + 0x1;
                }

                int bufIdx = bufferOffset + bufferLength - disp;
                for (int i = 0; i < length; i++) {
                    int next = buffer[bufIdx % bufferLength];
                    bufIdx++;
                    outstream[outpointer++] = next;
                    buffer[bufferOffset] = next;
                    bufferOffset = (bufferOffset + 1) % bufferLength;
                }
                currentOutSize += length;
            } else {
                int next = instream[pointer++];

                outstream[outpointer++] = next;
                currentOutSize++;
                buffer[bufferOffset] = next;
                bufferOffset = (bufferOffset + 1) % bufferLength;
            }

        }
        return outstream;

    }

But maybe you're not getting the correct bytes because both of the decompression things work for me.
Could you output the compressed data and see?
Reply
Thanked by:
#12
Come to think of it, the example output you linked to can't be correct. The file's only 0x287F long, which is 3/4 the size we need. (68*48*4=0x3300) I can't get that one to properly form an image, either.

The compressed data I'm using is just bytes 0x100 through EOF of the hud_anvil.anb, here. The romhacking.net lzx.exe (and the cs file you linked that I ported to C) decompresses it and spits out this, which I'm starting to think is correct, I just translated your Java function improperly.

(Current output looks that way, too)
[Image: hud_anvil_1.png]
Reply
Thanked by:
#13
Hmm, I dunno.
Both the decompress-or and texture maker code work for me.

Could I look at your code?

It's uh... not supposed to be upside down.
Reply
Thanked by:
#14
http://pastebin.com/D2eDfArG

Upside-down is probably just FreeImage being wonky.

I'll give your decompressor a shot later today to see if the output is different.

EDIT: Still seems incorrect. Bit closer tho.
[Image: hud_anvil_stillderp.png]
Reply
Thanked by:
#15
That image is good!
The problem is it has the wrong start offset and the colors out of order.

I noticed the freeimage format says "RGBA" while bufferedimage uses "ARGB".
So with my script you would have to put alpha at the end.

This part
Code:
RGBQUAD quad = { r, a, g, b };
        return quad;

If RGBQUAD uses RGBA, it should be { b, a, r, g };
(the r a g b don't actually stand for their correct color values)

Edit:
I just noticed something, the file I decompressed earlier was "blob_anvil", but we're using "hud_anvil".
That's why they were different :p
Reply
Thanked by:


Forum Jump: