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.
This commit is contained in:
parent
f2880975ba
commit
f31d9bc491
|
@ -145,5 +145,9 @@ pub fn random<'gc>(
|
|||
_this: Object<'gc>,
|
||||
_args: &[Value<'gc>],
|
||||
) -> Result<Value<'gc>, 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())
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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!");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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!
|
Binary file not shown.
Binary file not shown.
|
@ -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
|
Loading…
Reference in New Issue