Hacking SWF – PlaceObject and the Ratio Field

According to the SWF10 specification, PlaceObject2

.. can both add a character to the display list, and modify the attributes of a character that is already on the display list.

The placed character is usually defined earlier in the SWF, and can be anything supported by SWF, e.g., Shape, MorphShape, Sprite, Text, EditText etc. It stays on the display list until it is explicitly removed by the RemoveObject tag. PlaceObject2 might also tell the Flash Player that the placed object is to be treated as a mask (and what depth range will be masked), and might give the character an instance name (if it is a Sprite).

Additionally, PlaceObject2 might carry an optional ratio parameter. According to the SWF10 specification, it

.. specifies a morph ratio for the character being added or modified. This field applies only to characters defined with DefineMorphShape, and controls how far the morph has progressed. A ratio of zero displays the character at the start of the morph. A ratio of 65535 displays the character at the end of the morph. For values between zero and 65535 Flash Player interpolates between the start and end shapes, and displays an in- between shape.

For Flash users, this is better known as a “Shape Tween”.

However, the statement that  “this field applies only to characters defined with DefineMorphShape” is incorrect. It also applies to Sprite characters (“MovieClips”).

The Flash Player uses the value of the Ratio field to determine whether or not to reset the playhead in the placed Sprite to frame 1, when jumping to arbitrary frames in the parent timeline.

To illustrate that, let’s create a simple Flash movie using the Flash IDE. We create a one-frame MovieClip (called “square”) containing a simple shape. We create another MovieClip (called “animatedSquare”), which contains “square”, animated by a motion tween over 20 frames. We place “animatedSquare” on the main timeline (one frame only). When the Flash Player executes the resulting SWF, we see “animatedSquare” looping over all its 20 frames, as we would expect.

Here are the guts of the resulting SWF (simplified). Nothing surprising in there:

Now, let’s make the main timeline 10 frames long (each frame containing “animatedSquare”). The behavior doesn’t change, “animatedSquare” still loops over all of its 20 frames as expected. Also, the SWF tags still don’t reveal anything surprising, 9 more ShowFrame tags were added:

Finally, let’s remove “animatedSquare” from frame 5, leaving it only on frames 1-4 and 6-10:

This is where things get interesting. The character at depth 1 (our “animatedSquare” Sprite) is removed after frame 4, frame 5 is displayed without any content, and then “animatedSquare” is placed back on depth 1. Only now, the PlaceObject2 tag carries a value (5) in the Ratio field. Now why is that?

If we let Flash Player execute this SWF, you will notice the following behavior:

  1. In frames 1-4, the first 4 frames of “animatedSquare” are displayed
  2. In frame 5, a blank frame is displayed
  3. In frames 6-10, the first 5 frames of “animatedSquare” are displayed
  4. The main timeline then loops and jumps back to frame 1, displaying the first frame of “animatedSquare” again. Back to 1. Etc.

If we would put a gotoAndPlay(6) action on frame 10 of the main timeline, “animatedSquare” would reset to frame 1 once, and then loop over all 20 frames infinitely.

So what happens here is that once the Flash Player encounters a PlaceObject tag that attempts to place a Sprite on a depth that was previously occupied by the same Sprite, it looks at the ratio fields of the current and previous PlaceObject tags. If both carry the same value, the child Sprite keeps on playing normally. If not, the child Sprite’s playhead is reset to frame 1.

There is a little more to it yet, so if you are interested in digging deeper you should take a look at the Gnash Wiki, which lists many cases.