From c6c021f7f651ab07cc922ba5bd3c5ef2bdb17b0d Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 5 Mar 2023 23:19:17 -0600 Subject: [PATCH] core: Fix setting certain DisplayObject properties to NaN This doesn't perfectly match Flash's behavior - I haven't been able to reproduce the values produces when the DisplayObject starts out with certain 'Matrix' values (a non-zero 'b' or 'd'). Howver, when the 'b' and 'd' matrix values are both 0, setting 'dobj.rotation = NaN' has no effect on the matrix, while 'dobj.scaleX = NaN' and 'dobj.scaleY = NaN' both treat 'NaN' as 0 for the purposes of updating the matrix. This fixes the tack shooter in Bloons Tower Defense 3, which tries to set 'rotation = NaN' for spawned tacks. --- core/src/display_object.rs | 34 +++++++++- .../avm2/displayobject_invalid_floats/Test.as | 45 +++++++++++++ .../displayobject_invalid_floats/output.txt | 60 ++++++++++++++++++ .../displayobject_invalid_floats/test.fla | Bin 0 -> 3976 bytes .../displayobject_invalid_floats/test.swf | Bin 0 -> 1070 bytes .../displayobject_invalid_floats/test.toml | 1 + 6 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 tests/tests/swfs/avm2/displayobject_invalid_floats/Test.as create mode 100644 tests/tests/swfs/avm2/displayobject_invalid_floats/output.txt create mode 100644 tests/tests/swfs/avm2/displayobject_invalid_floats/test.fla create mode 100644 tests/tests/swfs/avm2/displayobject_invalid_floats/test.swf create mode 100644 tests/tests/swfs/avm2/displayobject_invalid_floats/test.toml diff --git a/core/src/display_object.rs b/core/src/display_object.rs index 425cab7eb..33c8e0109 100644 --- a/core/src/display_object.rs +++ b/core/src/display_object.rs @@ -262,6 +262,20 @@ impl<'gc> DisplayObjectBase<'gc> { self.set_transformed_by_script(true); self.cache_scale_rotation(); self.rotation = degrees; + + // FIXME - this isn't quite correct. In Flash player, + // trying to set rotation to NaN does nothing if the current + // matrix 'b' and 'd' terms are both zero. However, if one + // of those terms is non-zero, then the entire matrix gets + // modified in a way that depends on its starting values. + // I haven't been able to figure out how to reproduce those + // values, so for now, we never modify the matrix if the + // rotation is NaN. Hopefully, there are no SWFs depending + // on the weird behavior when b or d is non-zero. + if degrees.into_radians().is_nan() { + return; + } + let cos_x = f64::cos(degrees.into_radians()); let sin_x = f64::sin(degrees.into_radians()); let cos_y = f64::cos(degrees.into_radians() + self.skew); @@ -278,10 +292,18 @@ impl<'gc> DisplayObjectBase<'gc> { self.scale_x } - fn set_scale_x(&mut self, value: Percent) { + fn set_scale_x(&mut self, mut value: Percent) { self.set_transformed_by_script(true); self.cache_scale_rotation(); self.scale_x = value; + + // Note - in order to match Flash's behavior, the 'scale_x' field is set to NaN + // (which gets reported back to ActionScript), but we treat it as 0 for + // the purposes of updating the matrix + if value.percent().is_nan() { + value = 0.0.into(); + } + let cos = f64::cos(self.rotation.into_radians()); let sin = f64::sin(self.rotation.into_radians()); let mut matrix = &mut self.transform.matrix; @@ -294,10 +316,18 @@ impl<'gc> DisplayObjectBase<'gc> { self.scale_y } - fn set_scale_y(&mut self, value: Percent) { + fn set_scale_y(&mut self, mut value: Percent) { self.set_transformed_by_script(true); self.cache_scale_rotation(); self.scale_y = value; + + // Note - in order to match Flash's behavior, the 'scale_y' field is set to NaN + // (which gets reported back to ActionScript), but we treat it as 0 for + // the purposes of updating the matrix + if value.percent().is_nan() { + value = 0.0.into(); + } + let cos = f64::cos(self.rotation.into_radians() + self.skew); let sin = f64::sin(self.rotation.into_radians() + self.skew); let mut matrix = &mut self.transform.matrix; diff --git a/tests/tests/swfs/avm2/displayobject_invalid_floats/Test.as b/tests/tests/swfs/avm2/displayobject_invalid_floats/Test.as new file mode 100644 index 000000000..0f0f158eb --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_invalid_floats/Test.as @@ -0,0 +1,45 @@ + +package { + import flash.display.MovieClip; + import flash.geom.Matrix; + + public class Test { + public function Test(main: MovieClip) { + + var props = ["rotation", "x", "y", "scaleX", "scaleY"]; + // FIXME - we should also be testing Infinity and -Infinity here, + // but those give very weird values back in the matrix, + // and I havne't yet figured out how to reproduce them. Hopefully, + // there are no SWFs relying on the behavior. + for each (var prop in props) { + var clip = new MovieClip(); + clip.transform.matrix = new Matrix(2, 0, 4, 0, 5, 6); + trace("Setting initial matrix: " + clip.transform.matrix); + trace("Initial value: clip[" + prop + "] = " + clip[prop]); + tryValue(clip, prop, NaN); + + clip = new MovieClip(); + var newMat = new Matrix(2, 0, 4, 0, 5, 6); + clip.transform.matrix = newMat; + trace("Setting initial matrix: " + clip.transform.matrix); + trace("Initial value: clip[" + prop + "] = " + clip[prop]); + tryValue(clip, prop, NaN); + + clip = new MovieClip(); + var newMat = new Matrix(7, 0, 9, 0, 11, 12); + clip.transform.matrix = newMat; + trace("Setting initial matrix: " + clip.transform.matrix); + trace("Initial value: clip[" + prop + "] = " + clip[prop]); + tryValue(clip, prop, NaN); + } + } + } +} + +import flash.display.MovieClip; + +function tryValue(clip:MovieClip, prop:String, value:*) { + clip[prop] = value; + trace("Setting clip[" + prop + "] = " + value + " gave: " + clip[prop]); + trace("Matrix: " + clip.transform.matrix); +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/displayobject_invalid_floats/output.txt b/tests/tests/swfs/avm2/displayobject_invalid_floats/output.txt new file mode 100644 index 000000000..0c64fd187 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_invalid_floats/output.txt @@ -0,0 +1,60 @@ +Setting initial matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Initial value: clip[rotation] = 0 +Setting clip[rotation] = NaN gave: NaN +Matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Setting initial matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Initial value: clip[rotation] = 0 +Setting clip[rotation] = NaN gave: NaN +Matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Setting initial matrix: (a=7, b=0, c=9, d=0, tx=11, ty=12) +Initial value: clip[rotation] = 0 +Setting clip[rotation] = NaN gave: NaN +Matrix: (a=7, b=0, c=9, d=0, tx=11, ty=12) +Setting initial matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Initial value: clip[x] = 5 +Setting clip[x] = NaN gave: 0 +Matrix: (a=2, b=0, c=4, d=0, tx=0, ty=6) +Setting initial matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Initial value: clip[x] = 5 +Setting clip[x] = NaN gave: 0 +Matrix: (a=2, b=0, c=4, d=0, tx=0, ty=6) +Setting initial matrix: (a=7, b=0, c=9, d=0, tx=11, ty=12) +Initial value: clip[x] = 11 +Setting clip[x] = NaN gave: 0 +Matrix: (a=7, b=0, c=9, d=0, tx=0, ty=12) +Setting initial matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Initial value: clip[y] = 6 +Setting clip[y] = NaN gave: 0 +Matrix: (a=2, b=0, c=4, d=0, tx=5, ty=0) +Setting initial matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Initial value: clip[y] = 6 +Setting clip[y] = NaN gave: 0 +Matrix: (a=2, b=0, c=4, d=0, tx=5, ty=0) +Setting initial matrix: (a=7, b=0, c=9, d=0, tx=11, ty=12) +Initial value: clip[y] = 12 +Setting clip[y] = NaN gave: 0 +Matrix: (a=7, b=0, c=9, d=0, tx=11, ty=0) +Setting initial matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Initial value: clip[scaleX] = 2 +Setting clip[scaleX] = NaN gave: NaN +Matrix: (a=0, b=0, c=4, d=0, tx=5, ty=6) +Setting initial matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Initial value: clip[scaleX] = 2 +Setting clip[scaleX] = NaN gave: NaN +Matrix: (a=0, b=0, c=4, d=0, tx=5, ty=6) +Setting initial matrix: (a=7, b=0, c=9, d=0, tx=11, ty=12) +Initial value: clip[scaleX] = 7 +Setting clip[scaleX] = NaN gave: NaN +Matrix: (a=0, b=0, c=9, d=0, tx=11, ty=12) +Setting initial matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Initial value: clip[scaleY] = 4 +Setting clip[scaleY] = NaN gave: NaN +Matrix: (a=2, b=0, c=0, d=0, tx=5, ty=6) +Setting initial matrix: (a=2, b=0, c=4, d=0, tx=5, ty=6) +Initial value: clip[scaleY] = 4 +Setting clip[scaleY] = NaN gave: NaN +Matrix: (a=2, b=0, c=0, d=0, tx=5, ty=6) +Setting initial matrix: (a=7, b=0, c=9, d=0, tx=11, ty=12) +Initial value: clip[scaleY] = 9 +Setting clip[scaleY] = NaN gave: NaN +Matrix: (a=7, b=0, c=0, d=0, tx=11, ty=12) diff --git a/tests/tests/swfs/avm2/displayobject_invalid_floats/test.fla b/tests/tests/swfs/avm2/displayobject_invalid_floats/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..69db953ed835eb1bdbaf0550a35772e1a1964b1c GIT binary patch literal 3976 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 WW8wNVwB$DjNk=S6z_K%t@BRl{ggey$ literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/displayobject_invalid_floats/test.swf b/tests/tests/swfs/avm2/displayobject_invalid_floats/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..d420f26e9f241fa48179ad849d765f6336cee972 GIT binary patch literal 1070 zcmV+}1kw9LS5qsq1^@tfoSjwuQrkulzdL=LhVl3uu$#Oxt9j$v}tD z8D|(u@nBh;usXKvTEf788J@&%l2ha0ykBtKQFdl?h zj7mDQSb7Pe4`a_o4}dF%^4|7!*r;oEEq`d*t-5w5Z#6q6TdSKbB$k}EX&sHeW44pm zY-Qz}qSHRx(dx%c_OODtJa%9?0E*(4W?Jve2CJJEE8L?-TWheT#s7#JMtP4`+^+u0 zbVs_22n|P!KX^{`Qw9DmSGp@#dVOr^j@h($9d3+owVN%W<2kHOFZc3kB`4S!H>>*(kg!trV73^;w~MY}Osq z5}dZCvv7$yU>v2r^uO@s^tWl6KGHg@n3lFQ=<;bXEkmSb8_jk@Z1f?%0XHXjwc1j< z>1ba4_!OUswyxFLuD|^(TI>WU0{#$>ShFGejLEy4re!*&R!=tuiN-btK)(Y;zb5Xj z_BM6b*8Ff=i-QqHw8O*Ko>tP^X3LrXOz^N3@{3{}W;u5B2X9-4wrY-EW9{&|-(i2y z@eRvi&??95Fds?$!@t(F9AIW`vD9jt4tw^$2Dcwc-~^YH6TxJ7A}Bq zL|)(sl_-cD;DVT7Iz=&|gr`KNBtZEP6(f|!D2Y#FN+u|op(G{G5E^hF0U4JUX!c5#An^bav+zy?B*uBSKZuH z_u|r!yW0Wm<^Z3*FoH&?Z$5nk9|ep#p6jWg5=r8P8v=B+r)JJSX3ljUM7YMYhyu2O zuJIg}Au);M(z#6TQEon`xblv0{<|AdkS<=H>ohitkzA!!asK=x6v{=f@nb&WEh#rz z#+w)ctsATIMz|bNh=LRzL{P-dh^R%~IF$IM=QN`I8b`p+UU|1t<*$rb9~&)GFe>qF z%DLnBl!<@QLGA8@$5j=8Ir$0m?q4OMpDP(#ov|cQaRCu+*@1j(V13OzDeTixmv?mY<&j38A{b>mV z2ZAv;v*_Y7*W2W9)+K<89N>wb3gaq|-8^-%f_jFyJx2Oo3Yp}^p30DyWn8T!)XIcf oNvf4ewSwWdi&Oqp6K=k8lW^qw(JAErc|7>?qnF_M3;NC1a1EOm@c;k- literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/displayobject_invalid_floats/test.toml b/tests/tests/swfs/avm2/displayobject_invalid_floats/test.toml new file mode 100644 index 000000000..dbee897f5 --- /dev/null +++ b/tests/tests/swfs/avm2/displayobject_invalid_floats/test.toml @@ -0,0 +1 @@ +num_frames = 1