From f31d9bc491808122d8c8cfb5973aad783d574744 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 28 Jan 2024 13:47:25 -0500 Subject: [PATCH] avm2: Make Math.random() more closely match Flash Player See the comments for details. Our previous implementation was 'too good', and broke Bloons Tower Defense 5 by generating `Number`s that Flash Player would never generate. --- core/src/avm2/globals/math.rs | 6 +- .../tests/swfs/avm2/cryptscore/CryptScore.as | 75 ++++++++++++++++++ tests/tests/swfs/avm2/cryptscore/Test.as | 28 +++++++ tests/tests/swfs/avm2/cryptscore/output.txt | 11 +++ tests/tests/swfs/avm2/cryptscore/test.fla | Bin 0 -> 3944 bytes tests/tests/swfs/avm2/cryptscore/test.swf | Bin 0 -> 1189 bytes tests/tests/swfs/avm2/cryptscore/test.toml | 7 ++ 7 files changed, 126 insertions(+), 1 deletion(-) create mode 100755 tests/tests/swfs/avm2/cryptscore/CryptScore.as create mode 100755 tests/tests/swfs/avm2/cryptscore/Test.as create mode 100644 tests/tests/swfs/avm2/cryptscore/output.txt create mode 100755 tests/tests/swfs/avm2/cryptscore/test.fla create mode 100644 tests/tests/swfs/avm2/cryptscore/test.swf create mode 100644 tests/tests/swfs/avm2/cryptscore/test.toml diff --git a/core/src/avm2/globals/math.rs b/core/src/avm2/globals/math.rs index 9eb065285..f6365fd9e 100644 --- a/core/src/avm2/globals/math.rs +++ b/core/src/avm2/globals/math.rs @@ -145,5 +145,9 @@ pub fn random<'gc>( _this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error<'gc>> { - Ok(activation.context.rng.gen_range(0.0f64..1.0f64).into()) + // See https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/MathUtils.cpp#L1731C24-L1731C44 + // This generated a restricted set of 'f64' values, which some SWFs implicitly rely on. + const MAX_VAL: u32 = 0x7FFFFFFF; + let rand = activation.context.rng.gen_range(0..MAX_VAL); + Ok(((rand as f64) / (MAX_VAL as f64 + 1f64)).into()) } diff --git a/tests/tests/swfs/avm2/cryptscore/CryptScore.as b/tests/tests/swfs/avm2/cryptscore/CryptScore.as new file mode 100755 index 000000000..51fcd322a --- /dev/null +++ b/tests/tests/swfs/avm2/cryptscore/CryptScore.as @@ -0,0 +1,75 @@ +package +{ + import flash.display.MovieClip; + import flash.events.Event; + + public class CryptScore + { + + private static var randamaroo:MovieClip; + + + private var bs1:Number; + + private var a:Number; + + private var b:Number; + + private var bs2:Number; + + private var c:Number; + + private var bs3:Number; + + private var bs4:Number; + + private var d:Number; + + private var count:int = 0; + + public function CryptScore(param1:Number = 0, param2:Boolean = false) + { + super(); + if(randamaroo == null) + { + randamaroo = new MovieClip(); + } + randamaroo.addEventListener(Event.ENTER_FRAME,this.randomise,false,0,true); + this.count = Math.random() * 30; + this.value = param1; + } + + private function randomise(param1:Event) : void + { + ++this.count; + if(this.count >= 15) + { + this.value = this.value; + this.count -= 15; + } + } + + public function get value() : Number + { + return this.a - this.b + (this.c - this.d); + } + + public function set value(param1:Number) : void + { + this.bs1 = param1; + this.a = Math.random() * 32; + this.bs2 = -this.a; + do + { + this.b = Math.random() * 14; + this.bs3 = -this.b; + } + while(false); + + var _loc2_:Number = param1 - (this.a - this.b); + this.c = Math.random() * 23; + this.d = this.c - _loc2_; + this.bs4 = -param1; + } + } +} diff --git a/tests/tests/swfs/avm2/cryptscore/Test.as b/tests/tests/swfs/avm2/cryptscore/Test.as new file mode 100755 index 000000000..5bfa0c561 --- /dev/null +++ b/tests/tests/swfs/avm2/cryptscore/Test.as @@ -0,0 +1,28 @@ +package { + + import flash.display.MovieClip; + import flash.events.Event; + + + + public class Test extends MovieClip { + + + public function Test() { + var score = new CryptScore(); + for (var i = 0; i < 10000; i ++) { + if (i % 1000 == 0) { + trace("i = " + i); + } + score.value = score.value; + var value = score.value; + if (value !== 0) { + trace("i = " + i + " Bad value: " + value); + } + } + trace("Done!"); + } + + } + +} diff --git a/tests/tests/swfs/avm2/cryptscore/output.txt b/tests/tests/swfs/avm2/cryptscore/output.txt new file mode 100644 index 000000000..94c9a25b9 --- /dev/null +++ b/tests/tests/swfs/avm2/cryptscore/output.txt @@ -0,0 +1,11 @@ +i = 0 +i = 1000 +i = 2000 +i = 3000 +i = 4000 +i = 5000 +i = 6000 +i = 7000 +i = 8000 +i = 9000 +Done! diff --git a/tests/tests/swfs/avm2/cryptscore/test.fla b/tests/tests/swfs/avm2/cryptscore/test.fla new file mode 100755 index 0000000000000000000000000000000000000000..88f776a15c3e6ae5a5e07b72e277e41362b6c393 GIT binary patch literal 3944 zcmbtXc{r5&7atm7jIG8t!bP%1BzwdVvLu5dVldgaj3QgsY}rMKhz2p1>`V4(lr{TW z$ewjVjiukr?N+z@-0nZW^FHU@zUOnk=Xu|Ap6Bz?(>el#002|~0IP-kH4a+qBh&x@ zfaJti02I<4CF*(8QBVK!HRIDT_&L2_!if<-j1;b|siLo}Z!GrvC{kh_H3Q|-nz|SN zBaD$4=IUYTXyQ1 zR_x?hVWbl?c7~zh>au^y#M!nlB`_mvK3k2*@^Q6n^C}@A<;1DmkTeRy%F4-m7Z`Rd z&Y|1R;-~OwZt;juTPMO%a6L>D23O-VqRHG+p+6UdbgCLZ99oUEN6J!-4)rXcy=tGS zy|pqe2u4FH4bdRcy$*d5TP3EDiZ@%e&KAnC&IOm;eUKDgY-sLs59C9Cbdq)8{a!Z$ zuFsB1Co%7t!g1~sgBL8?jN=4!4CcdMs~QGxP|j+VCNb1k&N&ejymGZlM!1BVxM>GQ zEJ_hyj>%NgE643K53-$g3^JE$4>Y=))jf?u&kN3>Y6r@Uv8fuomS6RS%mi%+JDOn< z!N&1gcbU@ogoWw?-I5@vM5!Khdeof3sup2sToQzUWC=iHW;r=8&t%+xr7IT3Q7nBc zPC6`|aZ__*sQgPC-d7rAnx=YOr9LL8Lk1fREM1Y4+QDvY9?0vN!#oxP6)@#*?lMOo zkeR;ee>g-3m=r2XF=9MDc()}%O*fg_U@)|W<{opdX3}uL@mKK2u7B9B`P&5=d{B|! zC(F8Kj`ou61)N%tMCaGSd@jZh&fnIohV*|TTU5@fW8k4W>^*pK%^n%B4qhuupSHEz zCW{{$PaKNFuN^I{lefJ9U2q<8ncQd;l&Rd2q!=1x1tU#Lw;Z`zjoYiU2h)YxKQD{u zIWx;9tn_Fv&(=0tr&W~ZXH&?0=o)=T==6R`8KYIf+37i)j4p{f)YFOu}<$GHBW;? zB_7MYoem8$gLj$Xuy}1bpWBYJ`73Xu9O2ft*hlHWlTCTEIU^&k8JmwDC*v}v9W4en zr+RhS2?~oTwBu5%7OwEKJOZ9Dd~Yp5QR?0M+2Wm9rEfDt{?}j<8~lec5$}G?kP{Mx zutuN|q@Mo{`8FWyRUF!+A;z2l0FDzOdf(;=jzppCoNe4mfOcai7pN@5p<5TGrgvWP54CQ~rmnInJXC|Q;<8^^NSmlyv}TassleIeiJfKh?tiQ< zV5WF7s#`M{{xumx0g+0{4aRasOc~c)3+n^<3+!y3DB`^#66}C=?NoxuUZ2hWs+HBt zQN+a?FG`tM#K0wF{D4suNwQjysMyh;$qcps`R}|b0WO*Gpi<8BPT1ThTAOlb=P%eg`YB@`~ zc#26z_t*&|@ElWL+x2voJl1#y(j0b=)N#=2M@kOv4| zX>bH8S4@c-2H6)II2fkpM&7J3v#(0)iu7yb`gE5?%?pf&#}w36FM0);)LgM2piI&) znhAJjUtP-|dp0uc88lT$S%1HT?jQe;=9kETwrbcz~%}Q+&``>kl@yz7}y+@H%2a< zqsT+eFT6!7S@K>VJbsVagi#S8&=w%FFHyC7M&93Kj!WZ-xDD$XWkIW9dk1dQD~{8Q z4~*V$wuR<{^=~D!Ux|>=@si@VSSn@YAoC;_muR)BjkbC1&t3|BVs3YnX(GkzAq78F zxgaRhew}aiT=#{?XEDzs!K*UXC#NrR$hc7rjYqZcmp3K%SgufN4{smI<5N`ec|>@W zZn+J@8q%CUvO2qQIlR&_Iq{+l#>)0YAIU{4Pg}EWvU5E?U*A6C1;0Qqog2(va7Ra zRKPHplXQ+YXXnX1U@fODg+7Q)@wn^OgtCM<{B5~5HZ?);S=8X3@2eC#5xUWo;#Ra` z=+h-{kG=;Sy*VnZMK+(;r!{hwu}d!>j5yL9QXs;33sa-dcro-0(S=yX5zZAC&J3v{n005X!yRIWR85>=i6G-Jrmx#~ zY)h6q4-rgHRkjO#=x8*0k~WUS>#$%4_r?r!TrS!tWv2=NOEeIFB@UqzMV3rQxmzV?JE-La)ER{g7W*oO0SR*qc~SO zYm7{4&)^H+&bxb~$>ICwXD@rsX4c-U-=3l&Fqg49m%M~q7wUHRJur8;TXdKG*wr%x zg>ZNNg0Z!Ijz)1XBYkq2&~C@2bU}7poy%Uz?qNiye(}kIM3~ z(&ZdagDSHV->he*N>pZeOJ<8~3vbt!SdaKvi0tCs*e1;mK33Pi{=(aB$}shcx+KYD zDQ=_X;RcdBES_&U!^X;GWBiub`mTC8VbpJTfW>vxqX|UR_|e(sig-VnBf7r1gHrbeM4~#ce)%fdHOB??!*%q(i>{dSKKAvp?(4?nbd>&A zChY#I8@toqwC z1Mwn8^q+F*_b8Ge`er1QA<=(OJH7wdj3ki0842b?^nZ~{-*)|-VEPMBygVoRKjhQ* zy?@6)e}W$!`CG}qve94n|0f^)nWB@J;$PV5f7L+h&!2Wc)PVoit-pTufA;H7{AuvN pcJKEBNWYe(j{n)CKNHjt6MXMjJuQl(q!keHh$RZt&%`wV;C~+sH>>~v literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/cryptscore/test.swf b/tests/tests/swfs/avm2/cryptscore/test.swf new file mode 100644 index 0000000000000000000000000000000000000000..342c64d3453be8754628cf9e58c46a47067a5fc8 GIT binary patch literal 1189 zcmV;W1X}w;S5pk#1^@tfoRw7FQrkup-j!Bb$$#=MHZ~a37)U9>!qBvsW}1W$LMD)D zpq)$xiWgg|)iD+$%WjkDMTV=+^oG6xFum+MB#$7-l-&2KcWj=3?pmHCG@Z^=Bb~E* z&bQzB_D??oX&V6T5P%E>cVY?vU{ScZxEP6c28%OAbFsL-@yvL%{_N|GCv(fo&T8$O z+orKx0xwcQhJ|3gX0(jL3;-{Zj?4q>iGpb$Fkb=kT4#61aPV#0ZW^SOT6CW7x$8CC zF-q^6sB2sGkl7^d#%f2~SmuhyW<%(9z zRo_aGdW?t@g?;sPtG(AGPt{esYZ;48YcIgTWS2O$J^!S#>2yr9X>7V|M7wX>KbY-J z(%mg@*0`27*RrtiPS-SP%X`EjyEDEk2bb11mL6^2eROMe39S;hA^U>l?WSETZ_oap zGF&<3}h8<6x4pIsE;;Wcdt?^k-EKOOxNt)(yjH`Qd!rpmv%Z<)3sXZ zOOvm>oqY0ge4Xu~YZNaB@A%$d{6)Lv5{vES$Ysz<%QcuX*XkNRF!vuML+>A?O7Dp9 zJaKCc!wD5`lX{`IZeiAFEjb3!h@?I+i}e(&t#3SNYWx z02I*A0u%!Q))kTRTm>+|S}^!r_3tkKLcSLySrq{cVct`?>0$U#5acGrO_-YqHyXS6 zP>iuMaRG>N5uij;l>r}~0x$_=#t0<@fC3W;LROkf@bq(-2kJ6I$s&@zdNV@vJXQet zb2tj~wBW4(I$BK?lB_7b@@;A>c|QqAijGxNRb&o$W^AGe#vi67I__07cLzZ+f85aM zgm==2)2obF5D}7YFycv{c#=+elV_&(^7ZK}qA8*x&}*Kl6*VovDwI6WjFULky!${^lonH4$#WU3-&kTBIAF;H|7Cj((I%{GP1%ki8H{&$u z>7)CvnOgzpZ~}zR;Z+!>p)KjY1or=+;Ud^Si#}jV&tX=G)5z)AU--8o=xv6!m7AJ8 zJ$#shGc^>K0Iq&mujcjYh+Zw|)lt2gWbb1*>_3eJ!|_>tnNa)ZmjANAP1g7u*Us@q D#|B4t literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/cryptscore/test.toml b/tests/tests/swfs/avm2/cryptscore/test.toml new file mode 100644 index 000000000..e94c41b4e --- /dev/null +++ b/tests/tests/swfs/avm2/cryptscore/test.toml @@ -0,0 +1,7 @@ +# This file tests that our Math.random() implementation works +# correctly for some weird memory-obfuscation logic. +# Flash Player's `Math.random()` implementation generates +# a limited range of `Number`s in the range [0.0, 1.0), which +# allows this swf to correctly compute the original value +# (instead of a very close value due to rounding). +num_ticks = 1 \ No newline at end of file