Jump to content

NexusTK EPF/PAL Frame Color Animations


Go to solution Solved by piken,

Recommended Posts

NexusTK is a 2D MMORPG from 1996. This has been my first research project. The graphics formats are pretty old, but a lot of fun to reverse.

TKViewer is a Java Swing application I created to renders these assets - the file formats are in the README.md (I'll expand more below)

There are 2 things I am trying to figure out here:

  1. How to index the palettes to achieve this color animations on a frame/image (Example in Post 2)
  2. If the Alpha in the RGB[A] uint32 (that I currently ignore) for transparent frames is used to change the colors (shade/lighten/mask).. Transparency is handled with the frame's "Stencil" which appears like binary to determine whether or not to draw groups of pixels.. The Alpha value is unused at the moment..

Here's a quick run-down of what I know so far.

Rendering a tile/frame requires 2 files (sorta):

  • EPF: (Electronic Picture/Photo Frame?) Frame and stencil data for 1 or more frames (i.e., Shield0.epf) -- Known Format below:
     
    short frame_count                      (2 bytes)                         # number of frames in the EPF
    short height                           (2 bytes)                         # height of the EPF (?)
    short width                            (2 bytes)                         # width of the EPF (?)
    short unknown                          (2 bytes)                         # unknown id/flag (1)
    int pixel_data_length                  (4 bytes)                         # length of the pixel data
    byte[pixel_data_length] pixel_data     (pixel_data_length bytes)         # list of pixel data bytes
    frame[frame_count] frames              (frame_count * frame_size bytes)  # list of frame structures
    
    typedef struct {
      short top                            (2 bytes)                         # top offset of the frame (in pixels)
      short left                           (2 bytes)                         # left offset of the frame (in pixels)
      short bottom                         (2 bytes)                         # bottom offset of the frame (in pixels)
      short right                          (2 bytes)                         # right offset of the frame (in pixels)
      int pixel_data_offset                (4 bytes)                         # index of 'pixel_data' for the start of this frame's pixel data
      int stencil_data_offset              (4 bytes)                         # index of 'pixel_data' for the start of this frame's stencil data
    } frame                                (16 bytes)

     

I'm not sure that the animation information is in the EPF itself, unless there's something I'm overlooking.. A bit more info on EPFs on an old issue here (explains the stencil a bit more):
https://github.com/DizzyThermal/TKViewer/issues/1

 

  • PAL: (PALette) Collection of 1 or more palettes (array) of 256 RGBA colors (represented as uint32) (i.e., Shield.pal):
    • Essentially: PAL_File.palettes[0-n].colors[0-255] -- where 'n' is the palette index within the collection of palettes (I've seen this called a "PaletteTable" in other projects):
      int palette_count                                                        # number of palettes in file
      PAL[palette_count] palettes                                              # list of PAL structures
      
      typedef struct {
        byte[9] header                      (9 bytes)                           # DLPalette (literal)
        byte[15] unknown                    (15 bytes)                          # unknown bytes (1)
        byte animation_color_count          (1 byte)                            # number of animation colors
        byte[7] unknown2                    (7 bytes)                           # unknown bytes (2)
        short[animation_color_count]        (animation_color_count * 2 bytes)   # list of animation colors (short)
        color[256] palette                  (1024 bytes)                        # list of color structures
      
        typedef struct {
          byte red                          (1 byte)                            # blue value for color
          byte green                        (1 byte)                            # green value for color
          byte blue                         (1 byte)                            # red value for color
          byte alpha                        (1 byte)                            # alpha value for color
        } color                             (4 bytes)
      } PAL

If we create a gray-scale PAL (256 colors, starting from `Color.WHITE` and ending at `Color.BLACK`, we can actually render any EPF without the "correct/intended" palette (which is why I said 'sorta' earlier).

I'm going to break posts here and post an example I'm working through next.

(Is this a common graphics format? Is there a name for it so I can do more research on it? Thanks!)

 

 

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

Posted (edited)

(This is a compilation of some of my posts on my TKViewer application's Issue Tracker)
 

Many graphics in NexusTK have animations. This includes map tiles/objects, character items/weapons/armor, and monsters.

In order to achieve this, my current assumption is that the palette index OR the entire palette itself must be changed

An old issue was opened by another very helpful researcher (DDeokk).. They talk about the PAL header containing an animation count and an array of animation offsets, however, it wasn't clear exactly how to implement this.

Example:

Using the Masters ward as reference:
image.png.e4bfa37617344283ec2f4eeceda1dae5.png

**EPF**: shield0.dat:Shield0.epf
**PAL**: char.dat:Shield.pal

After dumping all the EPF frames, I see 20 frames (using the first Palette[0]):
image.png.edfeb7587141af5e181f6537c8fcac52.png

Focusing on Frame 129 (Front-face of the shield):
image.png.437d4d0d2ef18f90b7298635e0094b49.png

Shield.pal has 22 palettes, so if I scrub the palette from 0-21 for Frame 129, I get:
image.png.122ba2203d30f7b838ecd59f60c2616f.png 

With the above result, I don't think that simply changing the palette index (pointing to a different palette) will achieve the color animation of NexusTK.

Maybe the palette index itself doesn't change, but the color within the palette changes - not sure.


I tried shifting through all the colors of Palette[0] - so rendering the same frame 256 times (once for each color) and every pixel in the frame gets offset together (e.g., Frame[0] = 0x0 offset, Frame[1] = 0x1 offset, etc):
image.thumb.png.9ef8f30ea03a19e49e556099ea212e7b.png

One thought I had was that if there's 8 frames in the animation, I'd think 256 colors / 8 frames = 32 color offset, but if I do that it doesn't seem to work:

image.png.37c117be65d0134570d8506142702837.png

I think I'm getting close, just not there yet.. Here are the individual frames of the animation:
image.png.2f3e993e59e67be7642a47bd92a49e4a.png
(Used ezgif to split the frames from the NexusAtlas website's GIF of the shield - Frame #0 might not be the actual start of the animaiton (i.e., it could be Frame #3).. Either way, this is what I want to achieve)
 

Shield.pal[1] (Master wards Palette)
            DEBUG: Palette Index: 1
            DEBUG: Palette Animation Offset Count: 1
            DEBUG: Palette Animation Offsets: [64, 71]
            DEBUG: Palette Unknown Bytes 1: [0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 1, 0, 0]
            DEBUG: Palette Unknown Bytes 2: [0, 0, 0, 0, 0, 0, 0]

Note: I broke Animation Offset into 2 bytes (instead of 1 short) to see if it made sense that way, not sure. 


Thanks in advance for the help all! :)

-DizzyThermal

Edited by DizzyThermal
Link to comment
Share on other sites

Posted (edited)

Another note about Character Parts is there is a DSC File that is used to describe how the frames make sense (as individual parts and animations of the part).. Here's some info on the DSC format (lots of unknowns, maybe it's within here):

Each Part is an item within the game - for Shield0.epf / Shield.pal / Shield.dsc - a part is 1 shield (i.e., Masters ward -- reference in Post 2).

Each Part has Chunks - which I believe are Animations (i.e., Idle-Down, Idle-Right, Attack-Left, etc etc)

Each Chunk has Blocks which are the frames of each animation.. I've implemented the animations in TKViewer for each part (i..e, View > Bodies -- Animations Radio Button)

Edit: I don't believe DSC files have animations (for colors) in them.. These animations happen on Ground tiles as well (that don't have corresponding DSC files) -- Striking it out as I don't believe it is relevant to Color/Palette Animations

I also forgot to upload the files, here are the 3 files for Shields (attached)
 

NexusTK-Shield.zip

Edited by DizzyThermal
Link to comment
Share on other sites

  • DizzyThermal changed the title to NexusTK EPF/PAL/DSC Frame Color Animations
Quote

Thanks in advance for the help all! 🙂 

I see you've already figured out a lot 👍. It could help others to help you if your questions were consolidated somewhere, maybe a bulleted list with your primary concerns at the top (like right after defining what TKViewer is, but before what you already know) rather than buried in the middle of each post.

Quote

Is this a common graphics format?

Nope, the DLPalette is custom. Common palettes include the following (and none of these have animations in them):

  • Adobe Color Table (.act)
  • Photoshop Color Swatch (.aco)
  • Gimp Palette File (.gpl)
  • JASC Palette (.pal/.PspPalette)
  • Microsoft RIFF Palette (.pal)
  • Paint.NET palette file (.txt)
  • RGB Triplets (.pal)
Quote

 

With the above result, I don't think that simply changing the palette index (pointing to a different palette) will achieve the color animation of NexusTK.

Maybe the palette index itself doesn't change, but the color within the palette changes - not sure.

 

Yeah, cycling through entirely different palettes here evidently doesn't yield pleasantly smooth palette animations. I wouldn't have the answer without spending a bunch of time looking at the resources myself, but a hunch (just knowing what a number of other games do) is that they cycle a known subset of the palette, while leaving others constant, and in many games (especially older Amiga/SNES games), this is hard-coded rather than data driven.

Btw, ResHax supports Code blocks, if you want to post those directly rather than screenshots + the text again. e.g.

struct Palette
{
    uint8_t[9] header;
    uint8_t[15] unknown;
    uint8_t animationColorCount;
    uint8_t[7] unknown2;
    uint16_t[animationColorCount] unknown3; 
    
    struct Color
    {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
        uint8_t alpha;  // <------------------------------------ Is this premultiplied?
    };

    Color[256] palette;
};

 

  • Like 1
Link to comment
Share on other sites

Posted (edited)
Quote

Yeah, cycling through entirely different palettes here evidently doesn't yield pleasantly smooth palette animations. I wouldn't have the answer without spending a bunch of time looking at the resources myself, but a hunch (just knowing what a number of other games do) is that they cycle a known subset of the palette, while leaving others constant, and in many games (especially older Amiga/SNES games), this is hard-coded rather than data driven.

I have to agree here. It seems hard-coded and not data-driven.

I think I partially found what I'm looking for (after relooking through the images above).. It seems like the 8 frames around the beginning are what I need, just the color isn't exactly right:image.png.2c470a8096ffc4a244597e7f4d297b1b.png

The "structure" of the pixels seems like the animation I'm looking for, but the blue isn't quite right

When rendering, I am ignoring the Alpha in the RGBA integer because it in many cases where the frame had transparency, the value is always 0x00 or something really low, like, 0x04.. This would make the whole rendered frame invisible (if I used it as Alpha))..

struct Color
    {
        uint8_t red;
        uint8_t green;
        uint8_t blue;
        uint8_t alpha;  // <------------------------------------ Is this premultiplied?
    };

When you ask (Is this premultiplied)? how would this affect the colors red/green/blue? Could this be why some of my color palettes appear to "repeat" when in reality maybe the premultiplication is doing some sort of shading/darkening/brightening.

It's almost like the Alpha value isn't really Alpha, because I use a Stencil to determine which pixels to draw/or not, not this value.

Maybe this is my key! :D

Thanks for the response piken!

Edited by DizzyThermal
Link to comment
Share on other sites

Quote

I am ignoring the Alpha in the RGBA integer because it in many cases where the frame had transparency, the value was 0x00

Good, because it's not alpha, just alignment padding.

struct Color
{
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    uint8_t padding;
};

Looking at the quadruplets, they indeed all have 0's. I didn't notice any 0x04's or other values in Shield.pal, but if you're seeing them in other files, that field may be a flag with some special meaning for that color entry 🤷.

image.png.ecd17d08d0738901e4c61850f6964bcf.png

image.png.7443ca9de71a1423205df9b55a0dee65.png

Quote

how would this affect the colors red/green/blue?

The blending calculation would overflow and give unintended psychedelic color patterns.

  • Like 1
Link to comment
Share on other sites

Quote

Good, because it's not alpha, just alignment padding.

Okay, that makes a lot more sense, if I used it as the actual alpha, everything would render invisible - thanks for clarifying that!

To render the images, I override the Palette's Alpha to be 255 for each color - the transparency comes from the "stencil"  (screen-door approach) to render or not render pixel(s).

 

1 hour ago, piken said:

I didn't notice any 0x04's or other values in Shield.pal

I may be thinking of something completely different - after looking through all the different Parts (Shield/Helmet/Sword/etc), the Alpha is 0x00, so indeed it is alignment padding.. I'm going to disregard this tidbit for now (and update my previous post).

 

Regarding alpha premultiplication - with the alpha value being 0, I don't think it's applicable/needed here.

 

I can get the animation to play right if I make a list of offsets in the order I intend - I'm still having an issue finding the correct color to exactly match the game's color.

Shield.epf:Frame[129]
Shield.pal:Palette[0]

Game Color:
image.png.9b2bb85e2bfe37dac288dbde665eb678.pngframe_1_delay-0.5s-new.gif.0cadb7c773f1f00fa07e135ac6cd71ec.gifframe_2_delay-0.5s-new.gif.886fa0713e6737a6274d369b9722168f.gifframe_3_delay-0.5s-new.gif.28aed3db8ac8d4fa475cd20edc58ed37.gifframe_4_delay-0.5s-new.gif.8c477666d264325fe1bd1e4eea7d5790.gifframe_5_delay-0.5s-new.gif.6aa1ea290001337c4f8bcd5220b5a89c.gifframe_6_delay-0.5s-new.gif.8ca647fc77f63a666157aa8785e97de2.gifframe_7_delay-0.5s-new.gif.7c92ee07c59991fa601aa7984b85de20.gif

My Rendering: (Color Offset: -1 per frame => [0, 255, 254, 253, 252, 251, 250, 249]:

Shield0.epf-129-0.png.df72be3f504604f0af95f8159d9ce500.pngShield0.epf-129-255.png.bb15eb21519e143f4cb5a224fdf80060.pngShield0.epf-129-254.png.0d17cc1c5e7aa01c263310fe89d77b1f.pngShield0.epf-129-253.png.58ea81864a2d939007005d0cafe94d6b.pngShield0.epf-129-252.png.fe05690bafbe7652ba8cd3c3eef76b01.pngShield0.epf-129-251.png.2528544fa864333545abadd85b6a0abc.pngShield0.epf-129-250.png.f5b2b867b31f67ea44c721c5cfffe0e2.pngShield0.epf-129-249.png.7f2ea8b1c1b20040f34615d0b220654d.png

I thought maybe I need to jump more than just -1.. But out of all the 255 renderings - shifting the color 1 at a time - I'm not seeing a perfect match with all the 8 frames

Link to comment
Share on other sites

Posted (edited)

Created a PAL Viewer to help visualize ALL the PAL Files - it shows the RGB values on the right when a color in the palette is hovered:
image.png.4387062b0aa259b1ee08cdfbdffcdcb7.png

char.dat (GRA)
  > Shield.pal (Palette)
    > 22 palettes (256 colors / palette)

Maybe it's a combination of shifting the color offset within the palette AND changing the palette index (still in the same overall PAL file - Shield.pal)

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

Alright, I went for the grand slam of NexusTK EPF/PAL Debugging tools...

Created an EPF Viewer:
epf_viewer.thumb.gif.37c12dec2bcc26a03fb89d4fa63eace3.gif

This lets me debug by incorporating any EPF with any PAL - and it even let's you roll the color offset!

I should make a comparator to go through every palette / color offset to find static images I'm looking for... Hopefully it actually exists.

  • Like 1
Link to comment
Share on other sites

  • DizzyThermal changed the title to NexusTK EPF/PAL Frame Color Animations

Another example using a ground tile from the game's tile map - it is a water tile that has a color animation to make it look like it's moving:

Game Water Tile:
water_animated_game-10x.gif.e82a29cb1d5bfbc052d175e633ea63e8.gif

My Rendering (Color Offset: +1 per frame => [0, 1, 2, 3, 4, 5, 6, 7]):
water_animated_render.gif.84be73616fae8777906748bc4cc48ba4.gif

Maybe these offsets are somewhere in the files (PAL, DSC/TBL*) - maybe they're hard-coded into the client.. Not quite sure yet.

* - TBL is the "descriptor" for tiles (like DSC is for Parts)

Link to comment
Share on other sites

  • Solution
Posted (edited)
Quote

and it even let's you roll the color offset!

Cool, you wrote that palette viewer pretty fast. Rather than shift the entire palette offset, games typically adjust a subrange within the palette. In your gem example, you would only rotate that span of 8 blue colors, not all 256. The black outline (and possibly all other colors) remain anchored.

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

Posted (edited)
12 hours ago, piken said:

Rather than shift the entire palette offset, games typically adjust a subrange within the palette. In your gem example, you would only rotate that span of 8 blue colors, not all 256. The black outline (and possibly all other colors) remain anchored.

This was the key. Animation offsets in my example above were [64, 71]:
image.png.aa89815eefe7380921e644a3ef4152e5.png

Breaking the short up into individual bytes creates a minimum and maximum palette index range:
image.png.f8ade301c7094ecf43ddfd82b800db81.png

So, if the original palette index falls in this range and the offset color falls out-of-range, wrap the offset to the min or max (i.e., 63 becomes 71 - when decrementing).

I have a manual check that if the Color is intended to be BLACK before offsetting, ignore the offset (otherwise the border changes as well - maybe there's more missing data?) Instead of explicitly ignoring black, I only animate colors within the range.. This works for other parts/tiles in the game!

Either way, I think we found our animation shield:
shield-animation.thumb.gif.0562d752a3aa20e97f361b948fecbaad.gif

 

Thanks for all the advice @piken!

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

  • 3 weeks later...

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