-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathReplicatedRandom.java
More file actions
68 lines (59 loc) · 3.25 KB
/
ReplicatedRandom.java
File metadata and controls
68 lines (59 loc) · 3.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.util.ArrayList;
import java.util.Random;
public class ReplicatedRandom extends Random {
// Replicate the state of a Random using a single value from its nextDouble
public boolean replicateState(double nextDouble) {
// nextDouble() is generated from ((next(26) << 27) + next(27)) / (1L << 53)
// Inverting those operations will get us the values of next(26) and next(27)
long numerator = (long)(nextDouble * (1L << 53));
int first26 = (int)(numerator >>> 27);
int last27 = (int)(numerator & ((1L << 27) - 1));
return replicateState(first26, 26, last27, 27);
}
// Replicate the state of a Random using a single value from its nextLong
public boolean replicateState(long nextLong) {
int last32 = (int)(nextLong & ((1L << 32) - 1));
int first32 = (int)((nextLong - last32) >> 32);
return replicateState(first32, 32, last32, 32);
}
// Replicate the state of a Random using two consecutive values from its nextInt
public boolean replicateState(int firstNextInt, int secondNextInt) {
return replicateState(firstNextInt, 32, secondNextInt, 32);
}
// Replicate the state of a Random using two consecutive values from its nextFloat
public boolean replicateState(float firstNextFloat, float secondNextFloat) {
return replicateState((int)(firstNextFloat * (1 << 24)), 24, (int)(secondNextFloat * (1 << 24)), 24);
}
public boolean replicateState(int nextN, int n, int nextM, int m) {
// Constants copied from java.util.Random
final long multiplier = 0x5DEECE66DL;
final long addend = 0xBL;
final long mask = (1L << 48) - 1;
long upperMOf48Mask = ((1L << m) - 1) << (48 - m);
// next(x) is generated by taking the upper x bits of 48 bits of (oldSeed * multiplier + addend) mod (mask + 1)
// So now we have the upper n and m bits of two consecutive calls of next(n) and next(m)
long oldSeedUpperN = ((long)nextN << (48 - n)) & mask;
long newSeedUpperM = ((long)nextM << (48 - m)) & mask;
// Bruteforce the lower (48 - n) bits of the oldSeed that was truncated.
// Calculate the next seed for each guess of oldSeed and check if it has the same top m bits as our newSeed.
// If it does then the guess is right and we can add that to our candidate seeds.
ArrayList<Long> possibleSeeds = new ArrayList<Long>();
for (long oldSeed = oldSeedUpperN; oldSeed <= (oldSeedUpperN | ((1L << (48 - n)) - 1)); oldSeed++) {
long newSeed = (oldSeed * multiplier + addend) & mask;
if ((newSeed & upperMOf48Mask) == newSeedUpperM) {
possibleSeeds.add(newSeed);
}
}
if (possibleSeeds.size() == 1) {
// If there's only one candidate seed, then we found it!
setSeed(possibleSeeds.get(0) ^ multiplier); // setSeed(x) sets seed to `(x ^ multiplier) & mask`, so we need another `^ multiplier` to cancel it out
return true;
}
if (possibleSeeds.size() >= 1) {
System.out.println("Didn't find a unique seed. Possible seeds were: " + possibleSeeds);
} else {
System.out.println("Failed to find seed!");
}
return false;
}
}