.. | ||
blend | ||
types | ||
__init__.py | ||
container.py | ||
decompile.py | ||
geo.py | ||
README.md | ||
render.py | ||
swf.py | ||
util.py |
AFP Animation Format
As far back as I can see, Konami has used an animation format that I refer to as AFP in virtually all of their games. Basically, if it runs on a PC, it probably has a variant of AFP running on it. This also goes for several of their commercial PC titles, some console titles and mobile titles as well. The name "AFP" comes from the library that implements most of the format but I have no idea what this actually stands for. In very old PC-based arcade games such as The*BishiBashi the library code makes references to Flash. If it encounters a plain SWF file it returns an error stating that old data is not supported and encouraging the artist to convert the data to the "new" format. Many other similarities between SWF and AFP are present including a legacy bytecode table that is identical to Flash, a tag ID to string function that matches Flash exactly up to the point where Konami started adding their own, and the fact that the core engine works identically down to the affine transformations, additive and multiplicative colors, tag system, blending modes, masking and a whole host of other things.
At some point, most of the old SWF code was removed from the project. A modern AFP library is almost unrecognizeable from a classic one, save for some basic code that parses the still-supported tags. The legacy SWF bytecode parser has been removed completely, support for recognizing vanilla SWF tags has been removed, and support for bytecode that is interactive in nature has been removed leaving only the shell of a bytecode engine that can update properties for effects such as applying masks, setting objects visible or invisible and rewinding/advancing individual sprites on the main canvas. While Konami appears to have put a decent effort into making all of their format changes purely additive, if one was to try to render an animation from an older game inside a newer game's engine it would not be a surprise if the resulting graphics were subtly wrong.
The AFP file format itself is not an all-encompassing container. It superficially
resembles a SWF file but has an entirely different header and different ways of
encoding the tags and optional features of the format. Curiously, one cannot decode
a chunk of AFP data without the corresponding BSI data which is simply a list of
locations to byteswap as well as the length to byteswap. This appears to be a
rudimentary way of hiding plain strings in the AFP data itself so that they can't
be found in a hex editor. The byteswap instructions are trivial as well as reversible.
Along with AFP data, one would need shape structures as well as textures. These are
not found inside the AFP data but are instead inside a parent container. For older
games this is a TXP2 file which contains special sections for texture sheets,
individual textures, shapes, fonts, AFP data and BSI data. For newer games this is
a standard Konami IFS file where the AFP data is found in the afp/
folder, the
BSI data is found in the afp/bsi/
folder, the shapes are found in the geo/
folder and the textures are found in the tex/
folder. Aside from container format
differences the underlying files are identically parsed and have equivalent
meanings regardless of where one unpacks them from.
The basic concept of how an animation is stored and represented is fairly simple. Each animation has a specified number of frames and there is a lookup table in the AFP header specifying which tags should be acted upon for each frame. To play an animation, one must act upon each tag for a frame and then display or save the resulting image. Tags have actions such as defining a shape or sprite for later use, requests to place a previously defined shape or sprite on the canvas, requests to update the properties of a previously placed object on the canvas, requests to delete a previously placed object from the canvas and arbitrary bytecode to execute as part of the frame's processing. Placed objects on the canvas each have an associated additive and multiplicative color to apply to them before blending, a blending mode such as normal, additive or subtractive, an affine or perspective transform matrix used to determine how to position the object and finally either a bounding box for a solid color rectangle, a reference to a texture or a list of child objects to treat as a single sprite. Objects are placed onto their own depth planes and rendered from lowest plane to highest plane one by one until the final frame has been constructed.
Tags
Since tags are the heart of the format (as they are in SWF files), I go into a bit more detail about the crucial ones here. In the older versions of the format vanilla SWF tags could also be used but they are documented elsewhere online and in practice have never been seen inside an AFP animation.
AP2 Shape Tag
The shape tag defines a shape (either a reference to a texture quad or a solid color rectangle) and assigns it an internal ID for later use. Both texture quads and solid rectangles are represented by the same shape structure which contains the shape's bounding rectangle, UV vertex points and draw parameters (such as whether this shape is a rectangle or a quad in the first place). If the shape is a texture quad the structure will also include a string reference to the name of the texture itself. The UV coordinates and bounding rectangle are always identical to the size of the referenced texture in the case of texture quad shapes and appear to be in the format simply to pass forward to a graphics card. Acting upon a shape tag means adding the shape to an internal library of objects that can be placed on the canvas.
AP2 Image Tag
This appears to be functionally identical to a shape, save for missing all of the graphics card coordinates. Instead of pointing at a shape structure, image tags point directly at a texture itself. Acting upon an image tag means adding the image to an internal library of objects that can be placed on the canvas in a similar manner to a shape.
AP2 Sprite Tag
The sprite tag defines a sprite, which in SWF and AFP nomenclature just means an embedded animation complete with its own list of frames and tags. In reality, the root animation itself can be seen as a sprite tag, and in fact the format allows importing another animation as a sprite to be placed on the canvas just like an image or shape. Sprites are handy because they allow an animator to define things like characters with their own shapes, transform and move the objects that make up those characters, and then later place the character on the canvas just like a shape or image. As you might expect, since a sprite is just a set of tags to act upon each frame, sprites can include their own child sprites indefinitely. Acting upon a sprite tag means adding the sprite to an internal library of objects that can be placed on the canvas.
AP2 Place Object Tag
The place object tag is the meat of the format. Place object tags specify a placed object ID to refer later to the placed object as well as a depth to place the object on. They refer to sprites, images and shapes by their registered ID. They can come in one of two flavors: create or update. In create mode, a new object is placed onto the canvas at the specified depth. That object is looked up in the internal library of objects that was previously added to in a shape, image or sprite tag. In update mode, a previously placed object is looked up by object ID and depth and its properties are updated to match the update tag's specifications. In both cases, the place object tag can include a blend mode, an affine or projection transform, an additive and multiplicative color and a few other properties. The blend mode and colors specify to the renderer how to combine the pixels of the placed object with other objects that are placed on the canvas. The transform specifies how the object is positioned, rotated and stretched. Using a series of updates, an animator could rotate an object, move its position on the canvas, change its transparency to fade it in or out and basically any other operation.
Acting upon a place object tag means placing a new object on the canvas to be rendered this frame or updating an existing object with new properties which will be rendered this frame. Objects that are already on the canvas and not updated by a place object tag will keep their properties and be rendered identically on subsequent frames. The only exception to this is placed sprites, which automatically advance to the next frame of their own embedded animation in order to render correctly.
AP2 Remove Object Tag
The remove object tag looks up an object previously placed by a place object tag and removes it from the canvas. It can either remove an object by object ID and depth or it can remove the last placed object on a particular depth. Acting upon a remove object tag means removing an existing object from the canvas so that it is not rendered on this frame or subsequent frames.
AP2 Do Action Tag
The do action tag executes AFP bytecode in order to perform some action. This can mean requesting a mask to be applied to a placed object, requesting a placed sprite be advanced or rewound to a specific frame, requesting a placed object be made invisible or a host of other useful effects. AFP animations use this to provide looping effects on sprites since the format does not natively support loops. They also use this to apply masks to placed objects as the format does not natively support defining a mask. Originally do action tags could also provide interactivity such as in The*BishiBashi where the levels themselves were implemented entirely in AFP files and played using bytecode to read inputs, check game scores and the like. Modern games have stripped this functionality out and only use it to provide a few useful hooks such as the above documented effects and things such as triggering sounds to play or informing other systems what frame of the animation is being rendered.
Acting upon a do action tag means using a bytecode interpreter to execute the bytecode and apply the desired property changes to the currently placed objects on the canvas. Note also that the place object tag supports bytecode triggers including an "on load" trigger that requires bytecode to be executed when the object is placed. So, bytecode can be found in two different locations but is executed identically either way.
AP2 Place Camera Tag
The place camera tag registers a camera by specifying where in 3D the camera is and the focal length from the camera to the render plane. The camera always looks straight down at the canvas and the focal length in practice alwys matches the Z offset of the camera. This is only used in conjunction with objects that have a perspective transform to correctly render the object based on its depth in the Z plane. Acting upon a place camera tag means storing where the camera is for future projection perspective renders when the engine goes to display all of the placed objects for a frame.
Quirks
No format that is this complex will be without its quirks. At some point, Konami hacked in 3D perspective support to the format. This is extremely rudimentary and only allows for a camera looking straight down at the canvas and for 2D quads to be transformed using a 3D perspective transform. Its my best guess that artists were tired of having to render out individual frames and specify them in order to achieve correct 3D rotation of arbitrary objects so this system was put in place. There is no culling, no normals, no depth buffer, FOV, nothing. It exists simply as a way to specify what depth to project a 2D quad that has been rotated in 3D space. This is used in newer games mostly for perspective-correct rotation of objects such as records.
Now, unlike the 2D portion which was based on Flash and has had some 15 or so years of testing to mature, the 3D portion is much more hacked in and has a host of quriks. For instance, it appears that they did not do perspective-based masking correctly and as such some animations which should have masking animations applied to them appear to do so incorrectly. They also appear to have a bug where specifying 3D perspective projection on an object in a sprite and then placing that sprite down on the main canvas results in a 2D affine projection instead of a 3D one. These are all subtle enough that its quite possible QA never caught the problem or the artist complained and was told it was not a big enough deal to fix.
I briefly considered having a compatibility quirks flag system in order to attempt to fix these issues and present a renderer that exactly matches what games do. The problem with that is that this renderer already attempts to be compatible with about a decade and a half of format changes. Given how much of the format has been ripped out for newer games, this is already an impossible task. I would also have to spend a great deal more time trying to figure out exactly HOW they got these things subtly wrong to replicate them versus understanding how the format was intended to work. I've made the decision that it is not worth my time to do so.
References
Much of the understanding of this format came from the various online SWF file format
references as so much of the format mirrors Flash even today. Detailed documentation
on the format itself is entirely missing but the code in this directory provides a
working reference implementation of the format. Wherever this document is vague or
confusing, please refer to the code in swf.py
for how to parse AFP, render.py
for
how to render AFP, container.py
for how to parse TXP2 files and geo.py
for how to
parse shape structures. Please also refer to decompile.py
for a ton of details on
how the bytecode itself works.