Change the usage of the stencil buffer to avoid running out of
stencil bits when too many nested masks are active.
This also cleans things up on wgpu which requires us to make
pipeline states in advice; now we only need a few stencil states
for masking as opposed to hundreds.
If the shape converter encountered a fill/line style with an
ID > the number of styles, Ruffle would panic as it tried to grab
the non-existent style. This could occur if we mis-parsed some
shape data, or the SWF contained incorrect data. Now we the invalid
style is gracefully ignored.
This is the same way that AVM1 actions run and it appears that frame scripts work exactly the same way. It fixes all outstanding bugs with movie clip navigation in AVM2 and allows me to remove a lot of weird workarounds I was writing for the old, incorrect behavior.
I'm also removing the "last run script frame" rule as `run_frame_internal` already had rules to prevent stopped clips from rerunning actions.
Note: This relies on the fact that SWF files do not stick `SymbolClass` declarations in child movieclips. If this isn't the case, then it will fail horribly, and we would then need to actually store clip 0 in the library somehow.
This uses a "VM tendency" system wherein the presence of `DoAction` or `DoInitAction` tags defaults the movie to AVM1, while the presence of `DoABC` defaults to AVM2. The presence of a `FileAttributes` tag allows setting the VM tendency in the same manner using it's AS3 bit.
Particularly malformed SWFs may cause execution issues if Flash Player uses a dramatically different system from this.
This requires the use of an intermediary enum called `AvmObject` which can hold either object representation. Currently, it's mostly just being unwrapped as AVM1 objects, which we will need to fix.
- removed default implementations for `play()` and `pause()` methods for AudioBackend trait
- Implemented `play()` and `pause()` methods for CPAL audio backend
- Implemented empty block for `play()` and `pause()` methods for NULL audio backend
Adds a suspend_audio method to compliment prime_audio on WebAudioBackend, as well as logic in player.rs on the set_is_playing method to suspend audio when is_playing is set to false. Exposes pause method for the ruffle player in JavaScript with logic to display the play button when paused.
The Array constructor with a single param sets the length if the
parameter is a number (no coercion is done); otherwise, it is
creates an 1-length Array containing the parameter. Previously
we coerced the parameter to a float.
Fixes a specific pattern of preloader design where animations were handled by just making the box bigger every frame until it's 100. Of course, direct equality of f64 is a terrible idea, but it works in Flash, which apparantly must store scale in percentages. So we must, too.
Was only consdering the world bounds, but buttons can have separate
hit areas that don't actually affect the bounds of the parent clip.
(TODO: Could have keep track of a separate mouse_bounds instead.)
Fixes regression in Mini-Putt 2 (#1120).
I originally added this with the anticipation that `impl` return syntax only allowed one trait plus OIBITs. This was prior experience in Rust but apparantly the compiler accepts this just fine, so I suppose my defensive coding practice was a bad/outdated idea.
This appears to work almost like it's own TObject method; you can run `Object.prototype.toLocaleString` on all sorts of things and it has separate behavior to what the class method for it might be. I have attempted to match Flash Player as best as I can.
The array being iterated is explicitly handed to all callbacks, and it is legal for the callback to mutate the array. Hence, we can't actually hold a `Ref` to the array storage when we call user code. Instead, we implement a custom `Iterator` which iterates over the object like user code would.
This actually can't be an `Iterator` impl due to limitations of the underlying trait. Hence, we have to `while let` instead of `for`.
This is for the sake of methods that want to change behavior based on if they're working with a number or some other kind of value. It should not be used otherwise.
This code also ensures that the prototypes of each system object are created in the appropriate `TObject` impl. This ensures that, for example, `new Array` hands you back an actual array.
The `fake_root` did not have an object, which could cause the
player to panic if the SWF was not completely loaded when playing.
Calling `post_instantiate` ensures that this dummy root has an
object.
There is a difference between empty/default (change value to default)
and none (don't modify), so make this explicit for some PlaceObject
parameters where it wasn't.
Fixes#1104.
Use eq_ignore_ascii_case when parsing HTML tags. Different versions
of Flash may export HTML tags with different cases, so this will
work a little better; however, we'll need a true HTML parser to
handle this robustly (for opening and closing tags with different
cases, for example).
* Implement `add`, with tests.
* Implement `add_i`.
There's no test, because for whatever reason, I can't figure out how to emit this from Animate CC 2020.
* avm2: Implement `bitand` with tests.
* Implement `bitnot` with tests.
* Implement `bitor` with tests.
* avm2: Implement `bitxor`
* avm2: Implement `declocal`, `declocal_i`, `decrement`, and `decrement_i`.
* tests: `swf_approx` tests should be allowed to print NaNs.
* avm2: Implement `divide`.
* avm2: Implement `inclocal`, `inclocal_i`, `increment`, and `increment_i`.
* avm2: Implement `lshift`.
* Implement `modulo`.
* avm2: Implement `multiply` and `multiply_i` (no tests for the latter)
* avm2: Implement `negate` and `negate_i` (no tests for the latter)
* avm2: Implement `rshift`
* avm2: Implement `subtract` and `subtract_i` (the latter without tests)
* avm2: Implement `urshift`.
removeMovieClip should only function on objects within a certain
depth range, usually to prevent removing timeline clips. However,
this wasn't working properly in some cases because the depth was
being biased incorrectly (removeMovieClip never takes a depth
parameter, so we should not bias the depth).
same_item_push was added on nightly, but is currently throwing
a false negative. I added an allow for it, but this causes a
warning on stable for an unknown lints, so allow unknown lints for
now.
This has some particularly annoying consequences for initialization order: notably, we can't actually create any ES4 classes using the standard machinery until after the three objects I just mentioned get created. Ergo, we have to create them through lower-level means, handing prototypes around, and then initialize AVM2's system prototypes list for it.
When we start adding more system prototypes, we'll also have to fill the extras with blank objects and then slot them in as we create them.
We're about to massively change the initialization process, and we really don't want to create another situation where the player can get caught with it's pants down.
This was surprisingly tricky - due to the need to look up superclasses, class trait instantiation requires an active `Activation` and `UpdateContext`. We can't get those during VM instance creation, since the player needs the VM first before it can give it a context to work with. Ergo, we have to tear the global scope initialization in two. At the first possible moment, the player calls a new `load_player_globals` method that initializes all class traits in global scope.
I have no idea why this is necessary - I was in a context where what *should* have been a `NativeMethod<'gc>` was instead being interpreted as some different function type with all the same lifetimes, but with an extra `'gc` lifetime as well. Funneling this through a non-trait method bypasses whatever is going on with the trait solver, and then at that point the trait solver knows what to do. Consider this an extra level of conversion.
ECMA-262 3rd ed. doesn't mention anything about different number types, so the standard as-if rule applies. If we are going to distinguish number types, we have to treat them as if they were the same type, promoting to `f64` as necessary to facilitate the conversion. I took a cursory look at an ECMA-262 4th ed. draft and it appears to do the same, although it calls everything `GeneralNumber` and has some really confusing psuedo-Pascal syntax for some reason.
I am extremely glad AVM2 does not provide access to 64-bit integer types (for now, at least).
Namespaces as values adds a bunch of extra special cases to the coercion and equality rules that don't really belong there. Namespace itself just returns it's URI as a string, so we can just make `NamespaceObject` do that and then treat it the same way we treat boxed primitives.
These include:
* Name resolution in `newobject`
* All runtime & late-bound multinames
* `Object.hasOwnProperty`
* `Object.propertyIsEnumerable`
* `Object.setPropertyIsEnumerable`
So, I overlooked this reading the 1.45 documentation, but the first thing they did is completely change f64 conversions. Apparantly, what I was doing (and what JavaScript spec dictates) is actually considered UB in LLVM, and my ability to actually write a concise wrapping u32 conversion is actually a soundness hole in Rust. Ergo, I'm now emulating the wrapping and sign calculation, which makes this both passing it's tests again and free of soundness holes and UB.
I don't know why I'm doing this - tests are failing in CI but not locally, and I can only assume that the most obvious conversion is broken in some way on whatever other architecture GitHub Actions uses. This will explicitly mask the integer result as a u64, and then convert it down to u32. A not-broken compiler should treat this code identically.
AVM2 is based on ES4, which as far as I'm aware, does not distinguish between "primitive values" and "objects". Thus, it is expedient to interpret any statement requiring something to be an Object to mean "not null or undefined".
Since we internally represent register values with primitive types, it is important that the VM always coerces to object before doing any other sort of type checking. Hence, something like `as_object` is unhelpful as it accidentally enforces a primitive/object distinction that ES4 attempted to remove.
The test is also far more in-depth than the `if_eq`/`if_ne` tests, which use the same set of vectors as the strict-equality tests from a while ago. Interestingly, this test passed on first run
Implementation is limited to generating exceptions on `null` or `undefined`. I'm not sure if primitive values don't exist in AVM2 or if this is supposed to box them like ES3, so I have decided to handle neither at this time.
This code is slightly over/under-precise compared to AVM2. This is because we handle precision limiting in binary floats rather than as part of the float printing process. Flash Player may also be rounding differently than us. However, I'm pretty sure ECMA-262 allows us to slightly differ here.
The ECMA-262 documentation is awfully overwrought for something that boils down to "chop off the non-whole part, wrap to 32 bits, then reinterpret as signed". Bitwise operations are *hell* to describe mathematically, and such descriptions are even harder to understand.
For whatever reason, `pushbyte` appears to be processed as a *signed* byte, despite the clear wording of "*byte_value* is an unsigned byte" in avm2overview.pdf. I guess it's supposed to be manually converted and promoted in this manner.
Functions that need to assert Boolness without coercion should either:
1. Ensure their function declaration requires a Boolean. (We don't enforce type errors on ES4 typehints yet, but we should.)
2. Check the value type themselves and raise their own errors if necessary.
As it stands the only users of `as_bool` either needed to check the type themselves or use `coerce_to_bool`. Notably, `setPropertyIsEnumerable` doesn't appear to coerce *or* throw an error: it instead fails silently if you hand it a non-`Boolean` value.
Notably, all of the `Avm1` "run stack frame" functions can no longer take a self parameter as the update context they will be getting also has that same parameter. Ergo, they're associated functions that get the moral equivalent of self from the update context.
This also introduces a new `Activation::from_stub` which creates a stub frame that runs everything on the main movie in layer 0. This significantly reduces boilerplate code elsewhere in the project.
This also removes the function parameter on `sort_compare_numeric`. As it was only being used for string comparisons, and it was causing unfixable lifetime issues, I have instead had it take the case-sensitivity flag and call the two functions it would have been passed anyway. This fixes the lifetime issue.
The process of constructing an `Activation` now involves calling `UpdateContext.reborrow`, which "sheds" a lifetime by copying all of the borrows into a new "owned" context with that lifetime.
Likewise, to call out to functions that don't need an `Activation`, just borrow the context out of the current activation. You can also construct child-frame activations by reborrowing the parent activation's context.
There is a race condition inadvertently caused by allowing movies to be fetched in slot 0: it is possible for the player to be caught mid-load without a root movie. A lot of code assumes level 0 always exists (e.g. `levels.get(0).unwrap()`), while our initialization methods assumed no Player methods would be called until the root movie is installed. This is an unreasonable assumption, as among other things users can trigger the race condition by just playing the movie too quickly.
This allows us to remove the conditionals on implementations of `from_path` that need to call this function, as the function is now always guaranteed to be there, even if it's just a no-op/`Err` generator.
During the small period of time when a player is created but has no root movie, a temporary empty movie is installed with an assumed stage size and framerate of 550x400@12fps. This is Flash default for new projects, so it seemed appropriate. User ActionScript cannot see these values, and I'm not even sure JavaScript can, either.
Calling loadMovieNum with a variable parameter compiles into a
GetURL2 call with a `_level` window target parameter. Previously
this triggered Ruffle to try to navigate to the SWF. Now it
properly loads the SWF inside the current movie.