Skip to content

Commit 59bbca8

Browse files
committed
core: Use same RNG algorithm as avmplus
1 parent 84bb5eb commit 59bbca8

File tree

8 files changed

+78
-13
lines changed

8 files changed

+78
-13
lines changed

core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ thiserror = { workspace = true }
3737
chrono = { workspace = true, features = ["clock"] }
3838
web-time = "1.1.0"
3939
encoding_rs = { workspace = true }
40-
rand = { version = "0.9.1", features = ["std", "small_rng", "os_rng"], default-features = false }
40+
rand = { version = "0.9.1", features = ["std", "os_rng"], default-features = false }
4141
serde = { workspace = true }
4242
serde_json = { workspace = true, features = ["preserve_order"] }
4343
nellymoser-rs = { git = "https://github.com/ruffle-rs/nellymoser", rev = "073eb48d907201f46dea0c8feb4e8d9a1d92208c", optional = true }

core/src/avm1/activation.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ use crate::vminterface::Instantiator;
1818
use crate::{avm_error, avm_warn};
1919
use gc_arena::{Gc, Mutation};
2020
use indexmap::IndexMap;
21-
use rand::Rng;
2221
use ruffle_macros::istr;
2322
use std::borrow::Cow;
2423
use std::cell::Cell;
@@ -1831,7 +1830,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
18311830
// The max value is clamped to the range [0, 2^31 - 1).
18321831
let max = self.context.avm1.pop().coerce_to_f64(self)? as i32;
18331832
let result = if max > 0 {
1834-
self.context.rng.random_range(0..max)
1833+
self.context.rng.generate_random_number() % max
18351834
} else {
18361835
0
18371836
};

core/src/avm1/globals/math.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use crate::avm1::error::Error;
33
use crate::avm1::property_decl::{DeclContext, Declaration};
44
use crate::avm1::{Object, Value};
55

6-
use rand::Rng;
76
use std::f64::consts;
87

98
macro_rules! wrap_std {
@@ -161,7 +160,7 @@ pub fn random<'gc>(
161160
// See https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/MathUtils.cpp#L1731C24-L1731C44
162161
// This generated a restricted set of 'f64' values, which some SWFs implicitly rely on.
163162
const MAX_VAL: u32 = 0x7FFFFFFF;
164-
let rand = activation.context.rng.random_range(0..MAX_VAL);
163+
let rand = activation.context.rng.generate_random_number();
165164
Ok(((rand as f64) / (MAX_VAL as f64 + 1f64)).into())
166165
}
167166

core/src/avm2/globals/math.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use crate::avm2::parameters::ParametersExt;
77
use crate::avm2::value::Value;
88
use crate::avm2::{ClassObject, Error};
99
use num_traits::ToPrimitive;
10-
use rand::Rng;
1110

1211
macro_rules! wrap_std {
1312
($name:ident, $std:expr) => {
@@ -159,6 +158,6 @@ pub fn random<'gc>(
159158
// See https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/MathUtils.cpp#L1731C24-L1731C44
160159
// This generated a restricted set of 'f64' values, which some SWFs implicitly rely on.
161160
const MAX_VAL: u32 = 0x7FFFFFFF;
162-
let rand = activation.context.rng.random_range(0..MAX_VAL);
161+
let rand = activation.context.rng.generate_random_number();
163162
Ok(((rand as f64) / (MAX_VAL as f64 + 1f64)).into())
164163
}

core/src/avm_rng.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
use crate::locale::get_current_date_time;
2+
3+
// https://github.com/adobe/avmplus/blob/858d034a3bd3a54d9b70909386435cf4aec81d21/core/MathUtils.cpp#L1546
4+
const C1: i32 = 1376312589;
5+
const C2: i32 = 789221;
6+
const C3: i32 = 15731;
7+
const K_RANDOM_PURE_MAX: i32 = 0x7FFFFFFF;
8+
9+
const U_XOR_MASK: u32 = 0x48000000;
10+
11+
// This struct should not be cloned or copied.
12+
#[derive(Debug, Default)]
13+
pub struct AvmRng {
14+
u_value: u32,
15+
}
16+
17+
impl AvmRng {
18+
fn init_with_seed(&mut self, seed: u32) {
19+
self.u_value = seed;
20+
}
21+
22+
fn random_fast_next(&mut self) -> i32 {
23+
if (self.u_value & 1) != 0 {
24+
self.u_value = (self.u_value >> 1) ^ U_XOR_MASK;
25+
} else {
26+
self.u_value >>= 1;
27+
}
28+
self.u_value as i32
29+
}
30+
31+
fn random_pure_hasher(&self, mut i_seed: i32) -> i32 {
32+
i_seed = ((i_seed << 13) ^ i_seed).wrapping_sub(i_seed >> 21);
33+
34+
let mut i_result = i_seed.wrapping_mul(i_seed);
35+
i_result = i_result.wrapping_mul(C3);
36+
i_result = i_result.wrapping_add(C2);
37+
i_result = i_result.wrapping_mul(i_seed);
38+
i_result = i_result.wrapping_add(C1);
39+
i_result &= K_RANDOM_PURE_MAX;
40+
41+
i_result = i_result.wrapping_add(i_seed);
42+
43+
i_result = ((i_result << 13) ^ i_result).wrapping_sub(i_result >> 21);
44+
45+
i_result
46+
}
47+
48+
pub fn generate_random_number(&mut self) -> i32 {
49+
// In avmplus, RNG is initialized on first use.
50+
if self.u_value == 0 {
51+
let seed = get_seed();
52+
self.init_with_seed(seed);
53+
}
54+
55+
let mut a_num = self.random_fast_next();
56+
57+
a_num = self.random_pure_hasher(a_num.wrapping_mul(71));
58+
59+
a_num & K_RANDOM_PURE_MAX
60+
}
61+
}
62+
63+
// https://github.com/adobe-flash/avmplus/blob/65a05927767f3735db37823eebf7d743531f5d37/VMPI/PosixSpecificUtils.cpp#L18
64+
fn get_seed() -> u32 {
65+
get_current_date_time().timestamp_micros() as u32
66+
}

core/src/context.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::avm1::{Object as Avm1Object, Value as Avm1Value};
88
use crate::avm2::api_version::ApiVersion;
99
use crate::avm2::Activation as Avm2Activation;
1010
use crate::avm2::{Avm2, LoaderInfoObject, Object as Avm2Object, SoundChannelObject};
11+
use crate::avm_rng::AvmRng;
1112
use crate::backend::{
1213
audio::{AudioBackend, AudioManager, SoundHandle, SoundInstanceHandle},
1314
log::LogBackend,
@@ -43,7 +44,6 @@ use crate::PlayerMode;
4344
use async_channel::Sender;
4445
use core::fmt;
4546
use gc_arena::{Collect, Mutation};
46-
use rand::rngs::SmallRng;
4747
use ruffle_render::backend::{BitmapCacheEntry, RenderBackend};
4848
use ruffle_render::commands::{CommandHandler, CommandList};
4949
use ruffle_render::transform::TransformStack;
@@ -119,7 +119,7 @@ pub struct UpdateContext<'gc> {
119119
pub video: &'gc mut dyn VideoBackend,
120120

121121
/// The RNG, used by the AVM `RandomNumber` opcode, `Math.random(),` and `random()`.
122-
pub rng: &'gc mut SmallRng,
122+
pub rng: &'gc mut AvmRng,
123123

124124
/// The current player's stage (including all loaded levels)
125125
pub stage: Stage<'gc>,

core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extern crate num_derive;
1515
#[macro_use]
1616
mod avm1;
1717
mod avm2;
18+
mod avm_rng;
1819
mod binary_data;
1920
pub mod bitmap;
2021
pub mod buffer;

core/src/player.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::avm1::VariableDumper;
66
use crate::avm1::{Activation, ActivationIdentifier};
77
use crate::avm2::object::{EventObject as Avm2EventObject, Object as Avm2Object};
88
use crate::avm2::{Activation as Avm2Activation, Avm2, CallStack};
9+
use crate::avm_rng::AvmRng;
910
use crate::backend::ui::FontDefinition;
1011
use crate::backend::{
1112
audio::{AudioBackend, AudioManager},
@@ -38,7 +39,6 @@ use crate::library::Library;
3839
use crate::limits::ExecutionLimit;
3940
use crate::loader::{LoadBehavior, LoadManager};
4041
use crate::local_connection::LocalConnections;
41-
use crate::locale::get_current_date_time;
4242
use crate::net_connection::NetConnections;
4343
use crate::orphan_manager::OrphanManager;
4444
use crate::prelude::*;
@@ -54,7 +54,6 @@ use crate::DefaultFont;
5454
use async_channel::Sender;
5555
use gc_arena::lock::GcRefLock;
5656
use gc_arena::{Collect, DynamicRootSet, Mutation, Rootable};
57-
use rand::{rngs::SmallRng, SeedableRng};
5857
use ruffle_macros::istr;
5958
use ruffle_render::backend::{null::NullRenderer, RenderBackend, ViewportDimensions};
6059
use ruffle_render::commands::CommandList;
@@ -316,7 +315,7 @@ pub struct Player {
316315

317316
transform_stack: TransformStack,
318317

319-
rng: SmallRng,
318+
rng: AvmRng,
320319

321320
gc_arena: Rc<RefCell<GcArena>>,
322321

@@ -2950,7 +2949,9 @@ impl PlayerBuilder {
29502949
mouse_cursor_needs_check: false,
29512950

29522951
// Misc. state
2953-
rng: SmallRng::seed_from_u64(get_current_date_time().timestamp_millis() as u64),
2952+
// TODO: AVM1 and AVM2 use separate RNGs (though algorithm is same), so this is technically incorrect.
2953+
// See: https://github.com/ruffle-rs/ruffle/issues/20244
2954+
rng: AvmRng::default(),
29542955
system: SystemProperties::new(language),
29552956
page_url: self.page_url.clone(),
29562957
transform_stack: TransformStack::new(),

0 commit comments

Comments
 (0)