Pages: 1
Posted on 04-11-15, 02:57 pm (rev. 7 by  MeroMero on 04-13-15, 04:30 pm)
Death by cuteness

Karma: 6564
Posts: 273/598
Since: 05-01-13
All you need to know about SPA files:

DISCLAIMER: The information in this present post before the edit is now obsolete, but will only be struck out and put into a spoiler instead of being deleted so that you know what part of this message  skawo originally responded to.
I also invite anyone to know something about SPA to post it there.
Thank you.

To know what bitmap format a TPS uses, you have to look at nybble 0 (the rightmost nybble) from byte 4 on the TPS header.
Here are the different values (modulo 8 ?):
0: nothing ?
1: 8bpp (1 color per byte / 1 byte per color)1
2: 2bpp (4 colors per byte)2
3: 4bpp (2 colors per byte)3
4: ?
5: ?
6: 8bpp (1 color per byte / 1 byte per color)4
7: 16bpp (2 bytes per color)5, 6

You can also force the first color to be transparent by setting byte 5 at 1.

1 P=32, C=1, B=1
2 C=4, B=1
3 C=2, B=1
4 P=8, C=1, B=1
5 C=1, B=2
6 Each word refers directly to the color rather than pointing to a 65536 colors-palette, essentially making said palette useless.

Opacity formula for cases 1 and 6 is:
O=math.floor(V/P) mod (256/P)
_V is the pixel's value (from 0 to 255);
_P is the number of colors allowed per palette before it loops again;
_O=0 is the highest transparency;
_O=(256/P)-1 is the highest opacity.

Bitmap size is determined by the following formula:
_T is the Texture Data Length;
_C/B is the number of colors per number of bytes.

Result S is always a number in the form of 2n:
_if n mod 2 is equal to 0, then the exported bitmap will be a square with a side's length equal to the square root of S;
_if n mod 2 is equal to 1, then the exported bitmap will be a rectangle with a width equal to the square root of S/2 and a height equal to twice the width.

At the time of writing, since there is no TPS to BMP converter, you'll have to do all those operations manually if you want to modify a texture inside a APS.

Don't hesitate to point out any mistake or misconception.
If you have any question or anything to add, feel free to do so.
This explanation is not complete, as such information in the post might be updated in the future.
Posted on 04-11-15, 07:34 pm

Karma: 19752
Posts: 115/1100
Since: 04-02-13
So, um, I have to wonder...
Why would you go from NCG -> SPA?

Wouldn't we want to shoot for converting pngs?
Posted on 04-11-15, 09:15 pm
Death by cuteness

Karma: 6564
Posts: 274/598
Since: 05-01-13
Mutual compatibility with NSMBeditor ?
That was my (retrospectively shitty) idea.

Even without thinking about the png format, I just realized that actually the TPS texture uses the exact same pixel-positioning than a 8bpp bitmap, the only difference being the transparency that is.

But yeah pretty sure everyone worth their salt already knew it, but I didn't… Fuck me, I guess…

That's what happens when I get too excited, I am TOO focused and then become oblivious to even time-saving details…
Posted on 04-13-15, 04:18 pm
Death by cuteness

Karma: 6564
Posts: 277/598
Since: 05-01-13
First post updated with new information about my understanding of the correlation between a TPS and a BMP.
Posted on 04-14-15, 06:45 pm
Death by cuteness

Karma: 6564
Posts: 279/598
Since: 05-01-13
This post is only pure theory as of now since a SPA editor doesn't exist as of yet.
And no, I don't have the skills to do a software like that, this is as far as I go; which is why I post the ideas here, so that maybe someone willing enough will pick up the momentum and work from there.

How would a theoretical SPA editor work ?

_It needs to be able to load each particle (SPT) contained into a SPA.
_It needs to be able to edit said particles.
_For a loaded SPT, the editor would have to look at nybble 0 from byte 4 to see which bitmap format is needed (see the first post in this thread).
_If bitmap format is 1 or 6, implement a palette-check to prevent more than 32 or 8 colors respectively.
_It should be able to allow/disable the forced transparency of the first color in the palette (via a checkbox maybe?).
_It should be able to edit the palette.
_Adding support for both importing and exporting images would be neat too.
_Other important things I have forgotten ?

Advanced details:
8bpp images are the ones who support transparency in a SPA file; while the editor in itself would be allowed to interpret the 8bpp SPT data as-is, Import and Export must use a SPT to ARGB32 bitmap algorithm conversion.
As for the features themselves, features like those in Pixelformer* would suffice.
This is a good alternative for the PNG format since the likeliest color-depth you would use with a PNG are more often than not either ARGB32 or RGB24, with an equivalent quality than that of a bitmap with the same color depth.

So what happens when you want to export the hypothetical texture?

Let's take for example a pixel within an image with a value of DA (218) to see how it will be converted.
To convert that value into a ARGB32 value, you first need to know that ARGB32 uses the following structure:
8 bits for blue, 8 bits for green, 8 bits for red and lastly 8 bits for the alpha value (BB GG RR AA).

We need to know what is the palette slot-number of the STP pixel:

S=V mod C

_S is the slot number (from 0 to C-1)
_V is the pixel's STP value;
_and C is the number of colors allowed per palette before it loops again (32 if type 1, 8 if type 6);

We get:
_type 1: S=218 mod 32  =>  S=26
_type 6: S=218 mod 8  =>  S=2

For simplicity sake, let's say that slot 2 from type 1 and slot 26 from type 6 yield the same color, FF7F (which is the highest grey value in NDS games).
From there it's just a matter of converting BGR555 into BGR888, an algorithm NSMBe is capable of using for example.
The pixel's data in ARGB32 is F8 F8 F8 AA.

Now to get the opacity value, use the following formula:


_O is the opacity value;
_V is the pixel's STP value;
_and S is the slot number (from 0 to C-1)

Let's do both cases:
_type 1: O=218-26  =>  O=192 (C0)
_type 6: O=218-2  =>  O=216 (D8)

So this is how the bytes referencing to this pixel would look like in ARGB32:
_type 1: F8 F8 F8 C0
_type 2: F8 F8 F8 D8

And you case you're wondering about importing a PNG, just convert them into ARGB32 bitmap with Pixelformer before importing, it won't suffer from any data loss.

*ARGB32 BMP can be opened with Pixelformer, but "can't" with Photoshop (yet funnily enough it can save in that format).
While Photoshop is the far more advanced editor, Pixelformer trumps Photoshop in 3 areas:
It can open ARGB32 bitmaps (as well as save in that format)
You can specify the transparency of the color before applying it to a pixel (Photoshop can do that but I think you need to download a specific plugin first?)
Said alpha value is specified by typing in the real value (a value between 0 and 255), which is far more accurate than typing in a percentage.

[offtopic]Why do I have the eerie feeling that I am being taken for a fool…[/offtopic]
Posted on 04-14-15, 07:42 pm (rev. 1 by  skawo on 04-14-15, 07:42 pm)

Karma: 19752
Posts: 119/1100
Since: 04-02-13
Wouldn't a theoretical SPA editor also have to edit the animation patterns? Unless that's done directly in-code and not defined in the spas.
Posted on 04-14-15, 07:58 pm
Death by cuteness

Karma: 6564
Posts: 280/598
Since: 05-01-13
According to the documentation, animation is part of the SPA, so it is definitely editable, or at the very least what particles will be used for 1 given animation.
But well, I think we shouldn't try to bite more than one can chew, editing the textures would already be a good enough of an improvement.
Sure, editing animation would be the icing on the cake, but I admit that I haven't looked at that specific data yet, though I can try something out.
Posted on 04-16-15, 10:15 pm
Death by cuteness

Karma: 6564
Posts: 282/598
Since: 05-01-13
I know this is somewhat what is already told in the SPA documentation, but I decided to do a breakthrough of how the data was read.
This is my attempt for a simpler-to-understand mechanics of the Particle data.

Particle length:
Generic data + Flag data

Generic data is always 96 bytes long.

Flag data is determined by the first 4 bytes (or 32 bits) of the Generic data.
The aforementionned bytes are in little-endian: GH IJ KL MN.
Reverse them: MN KL IJ GH.
Then convert the string from hexadecimal to binary.


M3M2M1M0N3N2N1N0 K3K2K1K0L3L2L1L0 I3I2I1I0J3J2J1J0 G3G2G1G0H3H2H1H0(2)

Bit 0 (H0) is the rightmost bit, while bit 31 (M3) is the leftmost bit.

Flag data's length is:
J0×12 + J1×12 + J2×8 + J3×12 + L0×24* + N0×8 + N1×8 + N2×16 + N3×4 + M0×8 + M1×16
If a bit is activated, it is equal to 1, else it is equal to 0.

Offsets of Flag data:
_the offset of the first bit activated is always 0×60;
_the offset of the second bit activated is 0×60 + length of the first bit activated;
_the offset of the third bit activated is 0×60 + length of the first bit activated + length of the second bit activated;
The bits are always activated in the order given by the formula.

For example if only J2, J3 and L0 are activated, the formula would be:
0×12 + 0×12 + 1×8 + 1×12 + 1×24 + 0×8 + 0×8 + 0×16 + 0×4 + 0×8 + 0×16
=> 8+12+24
=> 44
thus the Flag data would be 44 bytes long, which means:
_Particle data would be 140 bytes long;
_J2 offset would be 0×60;
_J3 offset would be 0×68;
_L0 offset would be 0×74;

Textures seems to be summoned by a given particle when J3 is activated, from there you can specify the textures who will be used, as well as how many (up to 8).

*Erratum: I had trouble at first because I was assigning a value of 20 for the length of L0, like specified in the documentation, but it simply didn't add up to the rest of the data.
This is where I realized that the data length for L0 really was 24, and then the data flowed like a charm.
Posted on 04-17-15, 02:28 pm (rev. 4 by  skawo on 04-17-15, 05:37 pm)

Karma: 19752
Posts: 120/1100
Since: 04-02-13
So, actually, this might be a bit off-topic, but a while ago I ran into a tiny bump trying to recolor one of the SPAs to fit my needs (essentially just FFing the palette so it all turned white)... this thing:

As you can see, that one dot particle refused to change, and I also could not find it's graphics anywhere, including using the MKDS editor. Is that a different kind of particle, perhaps? I know it's still in that particular SPA, as replacing the entire SPA with another one makes it go away... Any ideas?
Posted on 04-17-15, 03:43 pm (rev. 1 by  MeroMero on 04-17-15, 05:09 pm)
Death by cuteness

Karma: 6564
Posts: 283/598
Since: 05-01-13
I juggled a bit with the particles, and finally found out that the sand is somehow tied to texture 14 (if we consider the first texture to be texture 0).
I found out by blacking all the palettes and then counter-checking until one remained.

This is not a complete solution, but at least it gives us a direction to take.

EDIT: Lol, that sounds about right.
Somehow this particular sand uses texture 8 as a skeleton while using the palette for texture 14.
Sounds legit
Posted on 04-17-15, 05:15 pm

Karma: 19752
Posts: 121/1100
Since: 04-02-13
Is this the Boss 2 SPA you're checking or the generic level SPA?
Posted on 04-17-15, 05:17 pm (rev. 2 by  MeroMero on 04-17-15, 05:19 pm)
Death by cuteness

Karma: 6564
Posts: 285/598
Since: 05-01-13
The MummyPokey particles: spl_b02_snw
Pretty sure this is the right one.
Posted on 04-17-15, 05:23 pm (rev. 2 by  skawo on 04-17-15, 05:26 pm)

Karma: 19752
Posts: 122/1100
Since: 04-02-13
Oh, hey, you're completely right. I blanked out TPS number 9 and the sand's gone. Interesting.

But, since I had No. 14's palette blanked out as well... It must be somehow recolored by code or something
Posted on 04-17-15, 05:28 pm (rev. 3 by  MeroMero on 04-18-15, 05:28 pm)
Death by cuteness

Karma: 6564
Posts: 286/598
Since: 05-01-13
Due to the peculiar nature of this texture, next step would be to look at the particle data to single out anything out of the ordinary.

I already separated the 34 particles to ease the task:
Posted on 04-18-15, 05:30 pm
Death by cuteness

Karma: 6564
Posts: 287/598
Since: 05-01-13
Phew, I found the solution!
But first I'm going to explain what is going on for everyone there.


I remarked that turning palette 14 to all black did blacken the sand texture, but turning it to all white was turning it into a sort of yellow.
After thinking for a while I finally connected the dots: Color Multiplication!
Long things short, Color Multiplication is one way to blend 2 colors together, that's basically how it works:
_the algorithm multiplies both red values together and divides the result by 255, and does the same to the green and blue values.
_the result will always be a color with a darker tone (unless you multiply it with white).

What does it means?
If you multiply one given color by the color white (FFFFFF), which is the highest value you can multiply a color with, the color won't change.
If you multiply one given color by the color black (000000), which is the lowest value you can multiply a color with, the color will be all black.
The only way to get a white with the multiplication method is to blend the white with itself.

Back to NSMB:
A yellowish color did I say?
Yep, more precisely this color: #F8D800 (R=248, G=216, B=0)
So, we recapitulate:
This sand uses texture 8 as a base, texture 14 for the palette, then blends the palette with color F8D800.

The fix:

F8D800 is 7F03 in RGB555.
By searching the spa file, we can see 3 instances of 7F03 beginning at an even address within the particles:
_one at offset 0×09AE (particle 19)
_one at offset 0×0AA2 (particle 20)
_one at offset 0×0F6A (particle 30)

Particle 19 is for the sand that comes out when MummiPokey goes in and out the ground.
Particle 20 is for the trailing sand when MummiPokey fires its sand-ball from the mouth.
Particle 30 is for the sand that appears when the projectile impacts something.

The fix is simple, we're going to change F8D800 into F8F8F8 (since this is the max value allowed by the NDS).
F8F8F8 is FF7F in RGB555 (but FFFF works too).
Just change 7F03 into FFFF at offsets 0×09AE, 0×0AA2 and 0×0F6A and you're good to go.

Wait… the color blending still applies!

F8F8F8 multiplied with itself gives approximately F1F1F1, thus in NDS will be rendered as F0F0F0.
Well, 240 is close enough to 255 for the difference in lighting to be hardly perceptible for the human's eye.
Posted on 04-18-15, 06:04 pm

Karma: 19752
Posts: 125/1100
Since: 04-02-13
Yep, that does indeed work well enough.

Thank you very much for the help. Certainly earned another spot in the Newer DS credits.
Posted on 04-18-15, 07:31 pm
Death by cuteness

Karma: 6564
Posts: 288/598
Since: 05-01-13
Hey no problem haha, this is what communities are supposed to be for.
And also, being acknowledged by you is certainly a nice bonus, thank you for that.
Pages: 1