avm2: Fix handling of Loader.loadBytes for images

I've switched back to the original code for creating
the bitmap/bitmapdata, rather than relying on custom
initialization logic that we only used in loader.

To make sure that the Bitmap/BitmapData are only exposed
to ActionScript at the correct time, I've added a new flag
to control when 'LoaderInfo.content' becomes non-null
This commit is contained in:
Aaron Hill 2024-01-24 17:31:07 -05:00
parent 38d58552f2
commit bb9e7c04f7
10 changed files with 80 additions and 61 deletions

View File

@ -211,10 +211,7 @@ pub fn copy_pixels<'gc>(
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(bitmap_data) = this.as_bitmap_data() {
bitmap_data.check_valid(activation)?;
let source_bitmap = args
.get(0)
.unwrap_or(&Value::Undefined)
.coerce_to_object(activation)?;
let source_bitmap = args.get_object(activation, 0, "sourceBitmapData")?;
let source_rect = args.get_object(activation, 1, "sourceRect")?;

View File

@ -146,10 +146,12 @@ pub fn get_content<'gc>(
this: Object<'gc>,
_args: &[Value<'gc>],
) -> Result<Value<'gc>, Error<'gc>> {
if let Some(loader_stream) = this
.as_loader_info_object()
.and_then(|o| o.as_loader_stream())
{
let loader_info = this.as_loader_info_object().unwrap();
if !loader_info.expose_content() {
return Ok(Value::Null);
}
if let Some(loader_stream) = loader_info.as_loader_stream() {
match &*loader_stream {
LoaderStream::Swf(_, root) | LoaderStream::NotYetLoaded(_, Some(root), _) => {
if root.movie().is_action_script_3() || !root.movie().is_movie() {

View File

@ -113,6 +113,8 @@ pub struct LoaderInfoObjectData<'gc> {
#[collect(require_static)]
content_type: ContentType,
expose_content: bool,
errored: bool,
}
@ -150,6 +152,7 @@ impl<'gc> LoaderInfoObject<'gc> {
.construct(activation, &[])?,
cached_avm1movie: None,
content_type: ContentType::Swf,
expose_content: false,
errored: false,
},
))
@ -197,6 +200,7 @@ impl<'gc> LoaderInfoObject<'gc> {
.construct(activation, &[])?,
cached_avm1movie: None,
content_type: ContentType::Unknown,
expose_content: false,
errored: false,
},
))
@ -239,12 +243,17 @@ impl<'gc> LoaderInfoObject<'gc> {
self.0.read().errored
}
pub fn init_event_fired(&self) -> bool {
self.0.read().init_event_fired
}
pub fn fire_init_and_complete_events(
&self,
context: &mut UpdateContext<'_, 'gc>,
status: u16,
redirected: bool,
) {
self.0.write(context.gc_context).expose_content = true;
if !self.0.read().init_event_fired {
self.0.write(context.gc_context).init_event_fired = true;
@ -307,6 +316,17 @@ impl<'gc> LoaderInfoObject<'gc> {
}
}
pub fn expose_content(&self) -> bool {
self.0.read().expose_content
}
/// Makes the 'content' visible to ActionScript.
/// This is used by certain special loaders (the stage and root movie),
/// which expose the loaded content before the 'init' event is fired.
pub fn set_expose_content(&self, mc: &Mutation<'gc>) {
self.0.write(mc).expose_content = true;
}
pub fn set_loader_stream(&self, stream: LoaderStream<'gc>, mc: &Mutation<'gc>) {
self.0.write(mc).loaded_stream = Some(stream);
}

View File

@ -11,6 +11,7 @@ use crate::avm1::{Object as Avm1Object, Value as Avm1Value};
use crate::avm2::api_version::ApiVersion;
use crate::avm2::object::LoaderInfoObject;
use crate::avm2::Activation as Avm2Activation;
use crate::avm2::TObject as _;
use crate::avm2::{Avm2, Object as Avm2Object, SoundChannelObject};
use crate::backend::{
audio::{AudioBackend, AudioManager, SoundHandle, SoundInstanceHandle},
@ -397,6 +398,10 @@ impl<'a, 'gc> UpdateContext<'a, 'gc> {
let stage_loader_info =
LoaderInfoObject::not_yet_loaded(&mut activation, swf, None, Some(root), true)
.expect("Failed to construct Stage LoaderInfo");
stage_loader_info
.as_loader_info_object()
.unwrap()
.set_expose_content(activation.context.gc_context);
activation
.context
.stage

View File

@ -358,14 +358,6 @@ impl<'gc> TDisplayObject<'gc> for Bitmap<'gc> {
)
.expect("can't throw from post_instantiation -_-");
if bitmap_data_obj.as_bitmap_data().unwrap().disposed() {
// Set the real bitmapdata, in case this Bitmap was constructed from a Loader
// (it will have real data that doesn't come from a linked class)
bitmap_data_obj.init_bitmap_data(
activation.context.gc_context,
self.bitmap_data_wrapper(),
);
}
self.set_bitmap_data(
&mut activation.context,
bitmap_data_obj.as_bitmap_data().unwrap(),

View File

@ -335,10 +335,9 @@ impl<'gc> MovieClip<'gc> {
let loader_info =
LoaderInfoObject::not_yet_loaded(activation, movie.clone(), None, None, false)
.expect("Failed to construct LoaderInfoObject");
loader_info
.as_loader_info_object()
.unwrap()
.set_content_type(ContentType::Swf, activation.context.gc_context);
let loader_info_obj = loader_info.as_loader_info_object().unwrap();
loader_info_obj.set_expose_content(activation.context.gc_context);
loader_info_obj.set_content_type(ContentType::Swf, activation.context.gc_context);
Some(loader_info)
} else {
None
@ -387,14 +386,15 @@ impl<'gc> MovieClip<'gc> {
));
if movie.is_action_script_3() {
mc.0.read()
let mc_data = mc.0.read();
let loader_info = mc_data
.static_data
.loader_info
.as_ref()
.unwrap()
.as_loader_info_object()
.unwrap()
.set_loader_stream(
.unwrap();
loader_info.set_loader_stream(
LoaderStream::Swf(movie, mc.into()),
activation.context.gc_context,
);

View File

@ -10,8 +10,8 @@ use crate::avm2::object::{
TObject as _,
};
use crate::avm2::{
Activation as Avm2Activation, Avm2, Domain as Avm2Domain, Object as Avm2Object,
Value as Avm2Value,
Activation as Avm2Activation, Avm2, BitmapDataObject, Domain as Avm2Domain,
Object as Avm2Object, Value as Avm2Value,
};
use crate::backend::navigator::{OwnedFuture, Request};
use crate::backend::ui::DialogResultFuture;
@ -19,7 +19,7 @@ use crate::bitmap::bitmap_data::Color;
use crate::bitmap::bitmap_data::{BitmapData, BitmapDataWrapper};
use crate::context::{ActionQueue, ActionType, UpdateContext};
use crate::display_object::{
Bitmap, DisplayObject, MovieClip, TDisplayObject, TDisplayObjectContainer, TInteractiveObject,
DisplayObject, MovieClip, TDisplayObject, TDisplayObjectContainer, TInteractiveObject,
};
use crate::events::ClipEvent;
use crate::frame_lifecycle::catchup_display_object_to_frame;
@ -1863,22 +1863,29 @@ impl<'gc> Loader<'gc> {
let bitmap = ruffle_render::utils::decode_define_bits_jpeg(data, None)?;
let transparency = true;
let bitmap_data = BitmapDataWrapper::new(GcCell::new(
activation.context.gc_context,
BitmapData::new_with_pixels(
let bitmap_data = BitmapData::new_with_pixels(
bitmap.width(),
bitmap.height(),
transparency,
bitmap.as_colors().map(Color::from).collect(),
),
));
let bitmap_dobj = Bitmap::new_with_bitmap_data(
activation.context.gc_context,
0,
bitmap_data,
false,
&activation.caller_movie_or_root(),
);
let bitmapdata_wrapper =
BitmapDataWrapper::new(GcCell::new(activation.context.gc_context, bitmap_data));
let bitmapdata_class = activation.context.avm2.classes().bitmapdata;
let bitmapdata_avm2 = BitmapDataObject::from_bitmap_data_internal(
&mut activation,
bitmapdata_wrapper,
bitmapdata_class,
)
.unwrap();
let bitmap_avm2 = activation
.avm2()
.classes()
.bitmap
.construct(&mut activation, &[bitmapdata_avm2.into()])
.unwrap();
let bitmap_dobj = bitmap_avm2.as_display_object().unwrap();
if let MovieLoaderVMData::Avm2 { loader_info, .. } = vm_data {
let fake_movie = Arc::new(SwfMovie::fake_with_compressed_len(
@ -1890,7 +1897,7 @@ impl<'gc> Loader<'gc> {
.as_loader_info_object()
.unwrap()
.set_loader_stream(
LoaderStream::NotYetLoaded(fake_movie, Some(bitmap_dobj.into()), false),
LoaderStream::NotYetLoaded(fake_movie, Some(bitmap_dobj), false),
activation.context.gc_context,
);
}
@ -1902,12 +1909,10 @@ impl<'gc> Loader<'gc> {
activation.context.swf.version(),
data.to_vec(),
));
let loader_info_obj = loader_info.as_loader_info_object().unwrap();
loader_info
.as_loader_info_object()
.unwrap()
.set_loader_stream(
LoaderStream::NotYetLoaded(fake_movie, Some(bitmap_dobj.into()), false),
loader_info_obj.set_loader_stream(
LoaderStream::NotYetLoaded(fake_movie, Some(bitmap_dobj), false),
activation.context.gc_context,
);
}
@ -1918,7 +1923,6 @@ impl<'gc> Loader<'gc> {
callback: Box::new(move |uc, bitmap_obj| {
uc.post_frame_callbacks.push(PostFrameCallback {
callback: Box::new(move |uc, bitmap_obj| {
bitmap_obj.post_instantiation(uc, None, Instantiator::Movie, true);
if let Err(e) = Loader::movie_loader_complete(
handle,
uc,
@ -1932,19 +1936,13 @@ impl<'gc> Loader<'gc> {
data: bitmap_obj,
})
}),
data: bitmap_dobj.into(),
data: bitmap_dobj,
});
} else {
bitmap_dobj.post_instantiation(
&mut activation.context,
None,
Instantiator::Movie,
true,
);
Loader::movie_loader_complete(
handle,
&mut activation.context,
Some(bitmap_dobj.into()),
Some(bitmap_dobj),
status,
redirected,
)?;

View File

@ -10,6 +10,7 @@
import flash.events.HTTPStatusEvent;
import flash.display.MovieClip;
import flash.utils.ByteArray;
import flash.display.Bitmap;
public class Main extends MovieClip {
@ -92,6 +93,8 @@
loader.contentLoaderInfo.addEventListener(Event.INIT, function(e) {
dump(e);
trace("Init: loader.content = " + loader.content);
trace("Init: loader.content.bitmapData = " + Bitmap(loader.content).bitmapData);
});
loader.contentLoaderInfo.addEventListener(HTTPStatusEvent.HTTP_STATUS, function(e) {

View File

@ -18,6 +18,8 @@ Called super() in Main()
exitFrame in Test
exitFrame in Test
Event [Event type="init" bubbles=false cancelable=false eventPhase=2]: loader.numChildren = 1, loader.content = [object Bitmap], loader.contentLoaderInfo.bytesLoaded = 28417, loader.contentLoaderInfo.bytesTotal = 28417, loader.contentLoaderInfo.url = file:///
Init: loader.content = [object Bitmap]
Init: loader.content.bitmapData = [object BitmapData]
Event [Event type="complete" bubbles=false cancelable=false eventPhase=2]: loader.numChildren = 1, loader.content = [object Bitmap], loader.contentLoaderInfo.bytesLoaded = 28417, loader.contentLoaderInfo.bytesTotal = 28417, loader.contentLoaderInfo.url = file:///
exitFrame in Test
exitFrame in Test