diff --git a/src/main/java/kaptainwutax/seedcracker/magic/MagicMath.java b/src/main/java/kaptainwutax/seedcracker/magic/MagicMath.java new file mode 100644 index 0000000..735fd88 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/magic/MagicMath.java @@ -0,0 +1,41 @@ +package kaptainwutax.seedcracker.magic; + +/** + * Math utility library-- I have no idea how it works, don't ask. + * All credits to Matthew. (The man doesn't have a GitHub, shame!) + * */ +public class MagicMath { + + public static final long MASK_16 = 0xFFFFL; + public static final long MASK_32 = 0xFFFF_FFFFL; + public static final long MASK_48 = 0xFFFF_FFFF_FFFFL; + + public static int countTrailingZeroes(long v) { + int c; // output: c will count v's trailing zero bits, + // so if v is 1101000 (base 2), then c will be 3 + v = (v ^ (v - 1)) >> 1; // Set v's trailing 0s to 1s and zero rest + + for(c = 0; v !=0; c++) { + v >>>= 1; + } + + return c; + } + + public static long modInverse(long x, int mod) { //Fast method for modular inverse mod powers of 2 + long inv = 0; + long b = 1; + + for(int i = 0; i < mod; i++) { + if((b & 1) == 1) { + inv |= 1L << i; + b = (b - x) >> 1; + } else { + b >>= 1; + } + } + + return inv; + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/magic/PopulationReversal.java b/src/main/java/kaptainwutax/seedcracker/magic/PopulationReversal.java new file mode 100644 index 0000000..e863292 --- /dev/null +++ b/src/main/java/kaptainwutax/seedcracker/magic/PopulationReversal.java @@ -0,0 +1,121 @@ +package kaptainwutax.seedcracker.magic; + +import kaptainwutax.seedcracker.util.Rand; +import kaptainwutax.seedcracker.util.Seeds; +import kaptainwutax.seedcracker.util.math.LCG; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +public class PopulationReversal { + + private static final LCG SKIP_2 = Rand.JAVA_LCG.combine(2); + private static final LCG SKIP_4 = Rand.JAVA_LCG.combine(4); + + public static ArrayList getWorldSeeds(long populationSeed, int x, int z) { + ArrayList worldSeeds = new ArrayList<>(); + + if (x == 0 && z == 0) { + worldSeeds.add(populationSeed); + return worldSeeds; + } + + long c; //a is upper 16 bits, b middle 16 bits, c lower 16 bits of worldseed. + long e = populationSeed & MagicMath.MASK_32; //The algorithm proceeds by solving for worldseed in 16 bit groups + long f = populationSeed & MagicMath.MASK_16; //as such, we need the 16 bit groups of chunkseed for later eqns. + + boolean xEven = (x & 1) == 0; + boolean zEven = (z & 1) == 0; + + long firstMultiplier = (SKIP_2.multiplier * x + SKIP_4.multiplier * z) & MagicMath.MASK_16; + int multTrailingZeroes = MagicMath.countTrailingZeroes(firstMultiplier); //TODO currently code blows up if this is 16, but you can use it to get bits of seed anyway if it is non zero + long firstMultInv = MagicMath.modInverse(firstMultiplier >> multTrailingZeroes,16); + + //TODO We can recover more initial bits when x + z is divisible by a power of 2 + if (xEven ^ zEven) { //bottom bit of x*a + z*b is odd so we xor by 1 to get bottom bit of worldseed. + c = (populationSeed & 1) ^ 1; + } else { //bottom bit of x*a + z*b is even so we xor by 0 to get bottom bit of worldseed. + c = (populationSeed & 1); + } + + for (; c < (1L << 16); c += 2) { //iterate through all possible lower 16 bits of worldseed. + long target = (c ^ f) & MagicMath.MASK_16; //now that we've guessed 16 bits of worldseed we can undo the mask + + //We need to handle the four different cases of the effect the two | 1s have on the seed + long magic = x * ((SKIP_2.multiplier * ((c ^ Rand.JAVA_LCG.multiplier) & MagicMath.MASK_16) + SKIP_2.addend) >>> 16) + z * ((SKIP_4.multiplier * ((c ^ Rand.JAVA_LCG.multiplier) & MagicMath.MASK_16) + SKIP_4.addend) >>> 16); + addWorldSeed(worldSeeds, target - (magic & MagicMath.MASK_16), multTrailingZeroes, firstMultInv, c, e, x, z, populationSeed); //case both nextLongs were odd + addWorldSeed(worldSeeds, target - ((magic + x) & MagicMath.MASK_16), multTrailingZeroes, firstMultInv, c, e, x, z, populationSeed); //case where x nextLong even + addWorldSeed(worldSeeds, target - ((magic + z) & MagicMath.MASK_16), multTrailingZeroes, firstMultInv, c, e, x, z, populationSeed); //case where z nextLong even + addWorldSeed(worldSeeds, target - ((magic + x + z) & MagicMath.MASK_16), multTrailingZeroes, firstMultInv, c, e, x, z, populationSeed); //case where both nextLongs even + } + + return worldSeeds; + } + + public static long makeSecondAddend(int x, long k, int z) { + return ((x*((((SKIP_2.multiplier * ((k ^ Rand.JAVA_LCG.multiplier) & MagicMath.MASK_32) + SKIP_2.addend) & MagicMath.MASK_48) >>> 16) | 1L) + + z*((((SKIP_4.multiplier * ((k ^ Rand.JAVA_LCG.multiplier) & MagicMath.MASK_32) + SKIP_4.addend) & MagicMath.MASK_48) >>> 16) | 1L)) >>> 16) & MagicMath.MASK_16; + } + + public static void addWorldSeed(List worldSeeds, long firstAddend, int multTrailingZeroes, long firstMultInv, long c, long e, int x, int z, long populationSeed){ + if(MagicMath.countTrailingZeroes(firstAddend) >= multTrailingZeroes) { //Does there exist a set of 16 bits which work for bits 17-32 + long b = ((((firstMultInv * firstAddend)>>> multTrailingZeroes) ^ (Rand.JAVA_LCG.multiplier >> 16)) & ((1L << (16 - multTrailingZeroes)) - 1)); + + for(; b < (1L << 16); b += (1L << (16 - multTrailingZeroes))) { //if the previous multiplier had a power of 2 divisor, we get multiple solutions for b + long k = (b << 16) + c; + long target2 = (k ^ e) >> 16; //now that we know b, we can undo more of the mask + long secondAddend = makeSecondAddend(x, k, z); + + if (MagicMath.countTrailingZeroes(target2 - secondAddend) >= multTrailingZeroes) { //Does there exist a set of 16 bits which work for bits 33-48 + long a = ((((firstMultInv * (target2 - secondAddend)) >>> multTrailingZeroes) ^ (Rand.JAVA_LCG.multiplier >> 32)) & ((1L << (16-multTrailingZeroes)) - 1)); + + for(; a < (1L << 16); a += (1L << (16 - multTrailingZeroes))) { //if the previous multiplier had a power of 2 divisor, we get multiple solutions for a + if(Seeds.setPopulationSeed(null, (a << 32) + k, x, z) == populationSeed) { //lazy check if the test has succeeded + worldSeeds.add((a << 32) + k); + } + } + } + } + } + } + + /* + * Left as reference if I need to test this mess again. :P + * */ + public static void main(String[] args) { + long seed; + int x , z; + ArrayList seeds; + /*long seed = 40820992642153L; + int x = 2; + int z = 4; + ArrayList seeds = getSeedFromChunkseed(getChunkseed(seed, x, z), x, z); + System.out.println("start"); + for (long a : seeds) { + System.out.println(a); + } + System.out.println("done");*/ + Random r = new Random(); + int failcount = 0; + System.out.println("start"); + long start = System.currentTimeMillis(); + for(int i = 0; i < 100000; i++){ + seed = r.nextLong() & ((1L << 48)-1); + x = r.nextInt(16) - 8; + z = r.nextInt(16) - 8; + seeds = getWorldSeeds(Seeds.setPopulationSeed(null, seed,x,z), x, z); + + if (!seeds.contains(seed)) { + System.out.println(seed); + System.out.println(x); + System.out.println(z); + failcount++; + System.out.println(); + } + } + System.out.println("End: "+((System.currentTimeMillis()-start)/1000.0)); + System.out.println(failcount+" failures."); + } + +} diff --git a/src/main/java/kaptainwutax/seedcracker/util/Seeds.java b/src/main/java/kaptainwutax/seedcracker/util/Seeds.java index 9f8fb5a..15a585a 100644 --- a/src/main/java/kaptainwutax/seedcracker/util/Seeds.java +++ b/src/main/java/kaptainwutax/seedcracker/util/Seeds.java @@ -4,11 +4,12 @@ public class Seeds { public static long setRegionSeed(Rand rand, long worldSeed, int regionX, int regionZ, int salt) { long seed = (long)regionX * 341873128712L + (long)regionZ * 132897987541L + worldSeed + (long)salt; - rand.setSeed(seed, true); + if(rand != null)rand.setSeed(seed, true); return seed; } public static long setPopulationSeed(Rand rand, long worldSeed, int posX, int posZ) { + if(rand == null)rand = new Rand(0L); rand.setSeed(worldSeed, true); long a = rand.nextLong() | 1L; long b = rand.nextLong() | 1L; @@ -18,6 +19,7 @@ public class Seeds { } public static long setStructureStartSeed(Rand rand, long worldSeed, int chunkX, int chunkZ) { + if(rand == null)rand = new Rand(0L); rand.setSeed(worldSeed, true); long a = rand.nextLong(); long b = rand.nextLong(); @@ -30,14 +32,18 @@ public class Seeds { int sX = chunkX >> 4; int sZ = chunkZ >> 4; long seed = (long)(sX ^ sZ << 4) ^ worldSeed; - rand.setSeed(seed, true); - rand.nextInt(); + + if(rand != null) { + rand.setSeed(seed, true); + rand.nextInt(); + } + return seed; } public static long setSlimeChunkSeed(Rand rand, long worldSeed, int chunkX, int chunkZ, long salt) { long seed = worldSeed + (long)(chunkX * chunkX * 4987142) + (long)(chunkX * 5947611) + (long)(chunkZ * chunkZ) * 4392871L + (long)(chunkZ * 389711) ^ salt; - rand.setSeed(seed, true); + if(rand != null)rand.setSeed(seed, true); return seed; }