mirror of
https://github.com/exituser/Brewery.git
synced 2026-09-17 05:28:59 +00:00
209 lines
5.2 KiB
Java
209 lines
5.2 KiB
Java
package com.dre.brewery.lore;
|
|
|
|
import com.dre.brewery.P;
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
import java.io.FilterInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.security.InvalidKeyException;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* A Scramble Stream that uses XOR operations to unscramble an inputstream.
|
|
* <p>a byte generator feeded with the seed is used as xor source
|
|
* <p>Used to unscramble data generated by the XORScrambleStream
|
|
*/
|
|
public class XORUnscrambleStream extends FilterInputStream {
|
|
|
|
private long seed;
|
|
private final List<Long> prevSeeds;
|
|
private SeedInputStream xorStream;
|
|
private boolean running;
|
|
private boolean markRunning;
|
|
private boolean markxor;
|
|
|
|
private SuccessType successType = SuccessType.NONE;
|
|
|
|
/**
|
|
* Create a new instance of an XORUnscrambler, unscrambling the given inputstream.
|
|
*
|
|
* @param in The Inputstream to be unscrambled
|
|
* @param seed The seed used for unscrambling
|
|
*/
|
|
public XORUnscrambleStream(InputStream in, long seed) {
|
|
super(in);
|
|
this.seed = seed;
|
|
prevSeeds = null;
|
|
}
|
|
|
|
/**
|
|
* Create a new instance of an XORUnscrambler, unscrambling the given inputstream.
|
|
* <p>If given a List of previous Seeds, the unscrambler will try all of them for unscrambling the stream in case the seed fails.
|
|
*
|
|
* @param in The Inputstream to be unscrambled
|
|
* @param seed The seed used for unscrambling
|
|
* @param prevSeeds List of previously used seeds
|
|
*/
|
|
public XORUnscrambleStream(InputStream in, long seed, List<Long> prevSeeds) {
|
|
super(in);
|
|
this.seed = seed;
|
|
this.prevSeeds = prevSeeds;
|
|
}
|
|
|
|
/**
|
|
* Before unscrambling, this has to be called to tell the unscrambler that scrambled data will follow.
|
|
* <br>Before starting the unscrambler, any data will just be passed through unmodified to the underlying stream.
|
|
* <br>The Unscrambling can be started and stopped arbitrarily at any point, allowing for parts of already unscrambled data in the stream.
|
|
*
|
|
* @throws IOException IOException
|
|
* @throws InvalidKeyException If the scrambled data could not be read, very likely caused by a wrong seed. Thrown after checking all previous seeds.
|
|
*/
|
|
public void start() throws IOException, InvalidKeyException {
|
|
running = true;
|
|
if (xorStream == null) {
|
|
short id = (short) (in.read() << 8 | in.read());
|
|
if (id == 0) {
|
|
running = false;
|
|
successType = SuccessType.UNSCRAMBLED;
|
|
P.p.debugLog("Unscrambled data");
|
|
return;
|
|
}
|
|
int parity = in.read();
|
|
xorStream = new SeedInputStream(seed ^ id);
|
|
boolean success = checkParity(parity);
|
|
if (success) {
|
|
successType = SuccessType.MAIN_SEED;
|
|
P.p.debugLog("Using main Seed to unscramble");
|
|
}
|
|
|
|
if (!success && prevSeeds != null) {
|
|
for (int i = prevSeeds.size() - 1; i >= 0; i--) {
|
|
seed = prevSeeds.get(i);
|
|
xorStream = new SeedInputStream(seed ^ id);
|
|
if (success = checkParity(parity)) {
|
|
successType = SuccessType.PREV_SEED;
|
|
P.p.debugLog("Had to use prevSeed to unscramble");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!success) {
|
|
throw new InvalidKeyException("Could not read scrambled data, is the seed wrong?");
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean checkParity(int parity) {
|
|
return ((parity ^ xorStream.read()) & 0xFF) == ((int) (seed >> 48) & 0xFF); // Parity/Sanity
|
|
}
|
|
|
|
/**
|
|
* Stop the unscrambling, any following data will be passed through unmodified.
|
|
* <p>The unscrambling can be started again at any point after calling this
|
|
*/
|
|
public void stop() {
|
|
running = false;
|
|
}
|
|
|
|
@Override
|
|
public int read() throws IOException {
|
|
if (!running) {
|
|
return in.read();
|
|
}
|
|
return (in.read() ^ xorStream.read()) & 0xFF;
|
|
}
|
|
|
|
@Override
|
|
public int read(@NotNull byte[] b, int off, int len) throws IOException {
|
|
if (!running) {
|
|
return in.read(b, off, len);
|
|
}
|
|
len = in.read(b, off, len);
|
|
for (int i = off; i < len + off; i++) {
|
|
b[i] ^= xorStream.read();
|
|
}
|
|
return len;
|
|
}
|
|
|
|
/**
|
|
* What was used to unscramble the stream: it was already unscrambled | Main Seed | Prev Seed
|
|
*
|
|
* @return The Type of Seed used to unscramble this, if any
|
|
*/
|
|
public SuccessType getSuccessType() {
|
|
return successType;
|
|
}
|
|
|
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
|
@Override
|
|
public long skip(long n) throws IOException {
|
|
long skipped = in.skip(n);
|
|
if (running && skipped > 0) {
|
|
xorStream.skip(skipped);
|
|
}
|
|
return skipped;
|
|
}
|
|
|
|
@Override
|
|
public void close() throws IOException {
|
|
if (xorStream != null) {
|
|
xorStream.close();
|
|
xorStream = null;
|
|
}
|
|
running = false;
|
|
super.close();
|
|
}
|
|
|
|
@Override
|
|
public boolean markSupported() {
|
|
return in.markSupported();
|
|
}
|
|
|
|
@Override
|
|
public synchronized void reset() throws IOException {
|
|
in.reset();
|
|
if (markxor) {
|
|
xorStream.reset();
|
|
} else {
|
|
xorStream = null;
|
|
}
|
|
running = markRunning;
|
|
}
|
|
|
|
@Override
|
|
public synchronized void mark(int readlimit) {
|
|
in.mark(readlimit);
|
|
if (xorStream != null) {
|
|
xorStream.mark(readlimit);
|
|
markxor = true;
|
|
}
|
|
markRunning = running;
|
|
}
|
|
|
|
/**
|
|
* What succeeded in unscrambling the Stream.
|
|
*/
|
|
public static enum SuccessType {
|
|
/**
|
|
* The Stream was already unscrambled.
|
|
*/
|
|
UNSCRAMBLED,
|
|
|
|
/**
|
|
* The Main Seed was used to unscramble the Stream.
|
|
*/
|
|
MAIN_SEED,
|
|
|
|
/**
|
|
* One of the Previous Seeds was used to unscramble the Stream.
|
|
*/
|
|
PREV_SEED,
|
|
|
|
/**
|
|
* It was not successful.
|
|
*/
|
|
NONE;
|
|
}
|
|
}
|