All this was done by Vidar Holen (mail). Send me questions or comments, especially if you piece together something I have not.
El-Fish, the wonderful fish simulator by Animatek/Maxis, has long been a love of mine. Unfortunately, I haven't actually been able to run the program for years since it's very picky with what it will run on. My dream has long been an xscreensaver with elfish fishies, but Maxis turned down my plea to open source it (2002-08-01), so I decided to try to reverse engineer it. Two years later, after on and off (mostly off, obviously; most of it was done in two days) hacking, I can extract the frames.
I'm currently able to extract animation frames (and description/icon view) but I haven't been able to piece together fluid animations. This same file format is used for .mvy (animations like the crab) and .isb (custom art). Even .aqu (aquariums) and .iso (image libraries) use it to some extent, but I haven't studied it closely.
8 bytes Unknown (magic?) 2 bytes Unknown (zero) --(offset now 0A)-- 8 bytes Fish name, zero-padded at end. 4 bytes Unknown (zero) 2 bytes Unknown (rendering progress?) 2 bytes Unknown (rendering total?) 2 bytes Unknown (Total number of animation frames + 7 ?) 4 bytes Unknown --(offset now 20)-- 4 bytes Total file length 103 bytes Unknown --(offset now 8B)-- 4 bytes Stream offset - Detailed frame 4 bytes Stream offset - Icon frame 4 bytes Stream offset - Animation framesEach stream is a linked list of frames. Each frame has a 30 byte header:
4 bytes Frame length (from start of header) 4 bytes Frame number 4 bytes Previous entry (absolute seek), or 0 if first frame in the series 4 bytes Next entry (absolute seek), or 0 if last frame in the series 4 bytes Unknown (same as frame length) 2 bytes Unknown (movement increment?) 2 bytes X offset (?) 2 bytes Y offset (?) 2 bytes Frame width 2 bytes Frame heightThen follows frame data. Each frame is encoded in lines, each line is built up from plot sections. Each plot section has a length, position, and pixels:
2 bytes 2 bytes 'Length' bytes [ Data length | Position | Pixel data (1 byte per) ]If the position is positive (two's complement), this is the start of a line. If it's negative, take its absolute value and plot on the current line (the first line only have negative sections). Simply put the pixel data in the line buffer linearly at the specified position. Any part of a line that is not explicitly colored is transparent. Each pixel is encoded in a fixed palette, which can be extracted from an elfish screenshot or downloaded here.
This isn't perfect, but it's what I have so far: The frame number from the frame header, when encoded in decimal, is on the form 'DANN' where D is the direction, A is the action, and NN is the position of this frame within the sequence. Some fish have six directions, others have four:
Direction: 4-dir: 6-dir: 0 Right Right 1 Up Up-right 2 Left Up-left 3 Down Left 4 Down-left 5 Down-rightThe only way I know to determine if a fish has 4 or 6 directions is to count. The actions are as follows:
Action: 0 Swim 1 First section in Turn from dir to dir+1 2 Middle section in Turn from dir-1 to dir+1 3 Last section in Turn from dir+1 to dir 4 Paddle 5 Idle 6 Stop (Swim to Idle)As you can see, there are only left turns. Fish rely on their symmetry for complete animation. Swin animations are done by first playing Swim Right then mirroring Swim Left. The X/Y offsets from the frame header is probably the relative position in which to paint the frame (since each has a varying width and height depending on fin positions and such).
I don't know what will be useful (read: I don't want to sort), so I'll just tar up all of the java code I wrote while reverse engineering it (GPL): Fetch! Interesting ones are ExtractFrames to dump all the frames, CountFrames to walk the .fsh file and print out structure information, and Swim to show animations
It's a fish allright | |
Kind of mushed, but alteast linebroken. | |
Head's ok | |
Tail's ok | |
Perfect frame | |
Animation! |