ruffle-rs / ruffle

A Flash Player emulator written in Rust
https://ruffle.rs
Other
15.45k stars 800 forks source link

Handle PlaceObject ratio field for non-morph shapes #1291

Open Herschel opened 3 years ago

Herschel commented 3 years ago

The "ratio" field in the SWF PlaceObject tag has additional behavior for movie clips: it specifies the initial frame that the object was created on. This is used during a rewinding goto to determine whether the clip should be recreated or reused.

When doing a rewind, the display object should be reused if new_id == old_id and new_ratio == old_ratio. Otherwise, it should recreated. This behavior isn't documented in the SWF specs; it only mentions this field being used for shape tweens. But this field serves the double purpose of handling rewind-gotos.

Currently Ruffle handles this implicitly; Ruffle manually stores the placement frame of a display object when it's instantiated, and uses this during rewinds to determine whether an object should be recreated. However, Ruffle always recreates the object in this case. In the official Flash Player, if an SWF had the ratio field manually removed or edited, the clip may not be recreated and would instead be re-used (the clip's animation wouldn't restart). A better name for the ratio field in this case could be "instance ID" or "place frame".

The Flash IDE always seems to generate the appropriate ratio fields that matches Ruffle's behavior, so this isn't an issue in normal SWFs; however, it could occur in SWFs generated by third-party tools (maybe causing #1060?).

This possibly implies that Shapes always get re-created on a rewind, because they never seem to get exported with a ratio field. This matters in AS3 where even Shapes are first-class display objects that can be manipulated from AS; for example, the following could test if the object was recreated.

var s = getChildAt(0);
// later, after a rewind, test if the shape gets recreated:
trace(s == getChildAt(0));

More info: http://wahlers.com.br/claus/blog/hacking-swf-2-placeobject-and-ratio/

Thanks to @TylerGlaiel for tipping me off on this one.

Samples: frames.zip frames.swf: Unmodified SWF. The clip never reaches the bottom right corner, because it gets recreated as the timeline loops back to frame 1. frames_edited.swf: Hex edited to remove ratio field. The clips reaches the bottom right corner, because it does not get recreated when the movie loops; the ratio of both clips matches when the rewind happens, and the clip gets reused.

TylerGlaiel commented 3 years ago

also should figure out exactly what happens when placeflagratio isn't set, does it default to 0, or the current frame number, or does it function as if it has a unique id entirely?