diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8bddb52..18ad7e3 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index ecc28e2..2607615 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,7 @@ sourceSets {
java {
srcDirs(
"src/main/java",
- "src/teavm/java"
+ "src/lwjgl/java"
)
}
}
@@ -25,10 +25,10 @@ tasks.withType(JavaCompile) {
options.compilerArgs << "-Xmaxerrs" << "1000"
}
-//sourceSets.main.resources.srcDirs += 'src/lwjgl/java/javazoom/jl/decoder'
+sourceSets.main.resources.srcDirs += 'src/lwjgl/java/javazoom/jl/decoder'
dependencies {
- //implementation fileTree(dir: './lwjgl-rundir/', include: '*.jar')
+ implementation fileTree(dir: './lwjgl-rundir/', include: '*.jar')
teavm(teavm.libs.jso)
teavm(teavm.libs.jsoApis)
diff --git a/lwjgl-rundir/_eagstorage.g.dat b/lwjgl-rundir/_eagstorage.g.dat
index 42b2810..9330e16 100644
Binary files a/lwjgl-rundir/_eagstorage.g.dat and b/lwjgl-rundir/_eagstorage.g.dat differ
diff --git a/lwjgl-rundir/eaglercraft.jar b/lwjgl-rundir/eaglercraft.jar
deleted file mode 100644
index 47c8c90..0000000
Binary files a/lwjgl-rundir/eaglercraft.jar and /dev/null differ
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/BooleanResult.java b/src/main/java/net/lax1dude/eaglercraft/sp/BooleanResult.java
new file mode 100644
index 0000000..576a6bf
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/BooleanResult.java
@@ -0,0 +1,18 @@
+package net.lax1dude.eaglercraft.sp;
+
+public class BooleanResult {
+
+ public static final BooleanResult TRUE = new BooleanResult(true);
+ public static final BooleanResult FALSE = new BooleanResult(false);
+
+ public final boolean bool;
+
+ private BooleanResult(boolean b) {
+ bool = b;
+ }
+
+ public static BooleanResult _new(boolean b) {
+ return b ? TRUE : FALSE;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/CRC32.java b/src/main/java/net/lax1dude/eaglercraft/sp/CRC32.java
new file mode 100644
index 0000000..5277242
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/CRC32.java
@@ -0,0 +1,39 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.util.zip.Checksum;
+
+public class CRC32 implements Checksum {
+ private com.jcraft.jzlib.CRC32 impl = new com.jcraft.jzlib.CRC32();
+ long tbytes;
+
+ @Override
+ public long getValue() {
+ return impl.getValue();
+ }
+
+ @Override
+ public void reset() {
+ impl.reset();
+ tbytes = 0;
+ }
+
+ @Override
+ public void update(int val) {
+ impl.update(new byte[] { (byte) val }, 0, 1);
+ }
+
+ public void update(byte[] buf) {
+ update(buf, 0, buf.length);
+ }
+
+ @Override
+ public void update(byte[] buf, int off, int nbytes) {
+ // avoid int overflow, check null buf
+ if (off <= buf.length && nbytes >= 0 && off >= 0 && buf.length - off >= nbytes) {
+ impl.update(buf, off, nbytes);
+ tbytes += nbytes;
+ } else {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/EAGLogAgent.java b/src/main/java/net/lax1dude/eaglercraft/sp/EAGLogAgent.java
new file mode 100644
index 0000000..34a5804
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/EAGLogAgent.java
@@ -0,0 +1,45 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import net.minecraft.src.ILogAgent;
+
+public class EAGLogAgent implements ILogAgent {
+
+ private final Logger logger = Logger.getLogger("IntegratedServer");
+
+ public Logger getServerLogger() {
+ return this.logger;
+ }
+
+ public void logInfo(String par1Str) {
+ this.logger.log(Level.INFO, par1Str);
+ }
+
+ public void logWarning(String par1Str) {
+ this.logger.log(Level.WARNING, par1Str);
+ }
+
+ public void logWarningFormatted(String par1Str, Object... par2ArrayOfObj) {
+ this.logger.log(Level.WARNING, par1Str, par2ArrayOfObj);
+ }
+
+ public void logWarningException(String par1Str, Throwable par2Throwable) {
+ this.logger.log(Level.WARNING, par1Str, par2Throwable);
+ }
+
+ public void logSevere(String par1Str) {
+ this.logger.log(Level.SEVERE, par1Str);
+ }
+
+ public void logSevereException(String par1Str, Throwable par2Throwable) {
+ this.logger.log(Level.SEVERE, par1Str, par2Throwable);
+ }
+
+ @Override
+ public void logFine(String var1) {
+ this.logger.log(Level.FINE, var1);
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/EAGMinecraftServer.java b/src/main/java/net/lax1dude/eaglercraft/sp/EAGMinecraftServer.java
new file mode 100644
index 0000000..09e566e
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/EAGMinecraftServer.java
@@ -0,0 +1,158 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.IOException;
+
+import net.lax1dude.eaglercraft.sp.ipc.IPCPacket14StringList;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.src.EnumGameType;
+import net.minecraft.src.ILogAgent;
+import net.minecraft.src.WorldSettings;
+
+public class EAGMinecraftServer extends MinecraftServer {
+
+ protected int difficulty;
+ protected EnumGameType gamemode;
+ protected long lastTick;
+ protected WorkerListenThread listenThreadImpl;
+ protected WorldSettings newWorldSettings;
+ protected boolean paused;
+ private int tpsCounter = 0;
+ private int tpsMeasure = 0;
+ private long tpsTimer = 0l;
+
+ public EAGMinecraftServer(String world, String owner, WorldSettings currentWorldSettings) {
+ super(world);
+ this.setServerOwner(owner);
+ System.out.println("server owner: " + owner);
+ this.setConfigurationManager(new EAGPlayerList(this));
+ this.listenThreadImpl = new WorkerListenThread(this);
+ this.newWorldSettings = currentWorldSettings;
+ this.paused = false;
+ }
+
+ public void setBaseServerProperties(int difficulty, EnumGameType gamemode) {
+ this.difficulty = difficulty;
+ this.gamemode = gamemode;
+ this.setCanSpawnAnimals(true);
+ this.setCanSpawnNPCs(true);
+ this.setAllowPvp(true);
+ this.setAllowFlight(true);
+ }
+
+ public void mainLoop() {
+ long ctm = SysUtil.steadyTimeMillis();
+
+ long elapsed = ctm - tpsTimer;
+ if(elapsed >= 1000l) {
+ tpsTimer = ctm;
+ tpsMeasure = tpsCounter;
+ IntegratedServer.sendIPCPacket(new IPCPacket14StringList(IPCPacket14StringList.SERVER_TPS, getTPSAndChunkBuffer(tpsMeasure)));
+ tpsCounter = 0;
+ }
+
+ if(paused && this.playersOnline.size() <= 1) {
+ lastTick = ctm;
+ return;
+ }
+
+ long delta = ctm - lastTick;
+
+ if (delta > 2000L && ctm - this.timeOfLastWarning >= 15000L) {
+ this.getLogAgent().logWarning("Can\'t keep up! Did the system time change, or is the server overloaded? Skipping " + ((delta - 2000l) / 50l) + " ticks");
+ delta = 2000L;
+ this.timeOfLastWarning = ctm;
+ }
+
+ if (delta < 0L) {
+ this.getLogAgent().logWarning("Time ran backwards! Did the fucking system time change?");
+ delta = 0L;
+ }
+
+ if (this.worldServers[0].areAllPlayersAsleep()) {
+ this.tick();
+ ++tpsCounter;
+ lastTick = SysUtil.steadyTimeMillis();
+ } else {
+ if (delta > 50l) {
+ delta -= 50L;
+ lastTick += 50l;
+ this.tick();
+ ++tpsCounter;
+ }
+ }
+
+ }
+
+ public void setPaused(boolean p) {
+ paused = p;
+ if(!p) {
+ lastTick = SysUtil.steadyTimeMillis();
+ }
+ }
+
+ public boolean getPaused() {
+ return paused;
+ }
+
+ @Override
+ protected boolean startServer() throws IOException {
+ SkinsPlugin.reset();
+ //VoiceChatPlugin.reset();
+ this.loadAllWorlds(folderName, 0l, newWorldSettings);
+ this.lastTick = SysUtil.steadyTimeMillis();
+ return true;
+ }
+
+ @Override
+ public void stopServer() {
+ super.stopServer();
+ SkinsPlugin.reset();
+ //VoiceChatPlugin.reset();
+ }
+
+ @Override
+ public boolean canStructuresSpawn() {
+ return false;
+ }
+
+ @Override
+ public EnumGameType getGameType() {
+ return gamemode;
+ }
+
+ @Override
+ public int getDifficulty() {
+ return difficulty;
+ }
+
+ @Override
+ public boolean isHardcore() {
+ return false;
+ }
+
+ @Override
+ public boolean isDedicatedServer() {
+ return false;
+ }
+
+ @Override
+ public boolean isCommandBlockEnabled() {
+ return true;
+ }
+
+ @Override
+ public WorkerListenThread getNetworkThread() {
+ return listenThreadImpl;
+ }
+
+ @Override
+ public String shareToLAN(EnumGameType var1, boolean var2) {
+ return null;
+ }
+
+ @Override
+ public ILogAgent getLogAgent() {
+ return IntegratedServer.logger;
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/EAGPlayerList.java b/src/main/java/net/lax1dude/eaglercraft/sp/EAGPlayerList.java
new file mode 100644
index 0000000..90ddd75
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/EAGPlayerList.java
@@ -0,0 +1,28 @@
+package net.lax1dude.eaglercraft.sp;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.src.EntityPlayerMP;
+import net.minecraft.src.NBTTagCompound;
+import net.minecraft.src.ServerConfigurationManager;
+
+public class EAGPlayerList extends ServerConfigurationManager {
+
+ private NBTTagCompound hostPlayerNBT = null;
+
+ public EAGPlayerList(MinecraftServer par1MinecraftServer) {
+ super(par1MinecraftServer);
+ this.viewDistance = 4;
+ }
+
+ protected void writePlayerData(EntityPlayerMP par1EntityPlayerMP) {
+ if (par1EntityPlayerMP.getCommandSenderName().equals(this.getServerInstance().getServerOwner())) {
+ this.hostPlayerNBT = new NBTTagCompound();
+ par1EntityPlayerMP.writeToNBT(hostPlayerNBT);
+ }
+ super.writePlayerData(par1EntityPlayerMP);
+ }
+
+ public NBTTagCompound getHostPlayerData() {
+ return this.hostPlayerNBT;
+ }
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/EPK2Compiler.java b/src/main/java/net/lax1dude/eaglercraft/sp/EPK2Compiler.java
new file mode 100644
index 0000000..623b6ce
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/EPK2Compiler.java
@@ -0,0 +1,151 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import com.jcraft.jzlib.CRC32;
+
+public class EPK2Compiler {
+
+ private final ByteArrayOutputStream os;
+ private final CRC32 checkSum = new CRC32();
+ private int lengthIntegerOffset = 0;
+ private int totalFileCount = 0;
+
+ public EPK2Compiler(String name, String owner, String type) {
+ os = new ByteArrayOutputStream(0x200000);
+ try {
+
+ os.write(new byte[]{(byte)69,(byte)65,(byte)71,(byte)80,(byte)75,(byte)71,(byte)36,(byte)36}); // EAGPKG$$
+ os.write(new byte[]{(byte)6,(byte)118,(byte)101,(byte)114,(byte)50,(byte)46,(byte)48}); // 6 + ver2.0
+ Date d = new Date();
+
+ byte[] filename = (name + ".epk").getBytes(StandardCharsets.UTF_8);
+ os.write(filename.length);
+ os.write(filename);
+
+ byte[] comment = ("\n\n # Eagler EPK v2.0 (c) " + (new SimpleDateFormat("yyyy")).format(d) + " " +
+ owner + "\n # export: on " + (new SimpleDateFormat("MM/dd/yyyy")).format(d) + " at " +
+ (new SimpleDateFormat("hh:mm:ss aa")).format(d) + "\n\n # world name: " + name + "\n\n")
+ .getBytes(StandardCharsets.UTF_8);
+
+ os.write((comment.length >> 8) & 255);
+ os.write(comment.length & 255);
+ os.write(comment);
+
+ writeLong(d.getTime(), os);
+
+ lengthIntegerOffset = os.size();
+ os.write(new byte[]{(byte)255,(byte)255,(byte)255,(byte)255}); // this will be replaced with the file count
+
+ os.write('0'); // compression type: none
+
+ os.write(new byte[]{(byte)72,(byte)69,(byte)65,(byte)68}); // HEAD
+ os.write(new byte[]{(byte)9,(byte)102,(byte)105,(byte)108,(byte)101,(byte)45,(byte)116,(byte)121,
+ (byte)112,(byte)101}); // 9 + file-type
+
+ byte[] typeBytes = type.getBytes(StandardCharsets.UTF_8);
+ writeInt(typeBytes.length, os);
+ os.write(typeBytes); // write type
+ os.write('>');
+
+ ++totalFileCount;
+
+ os.write(new byte[]{(byte)72,(byte)69,(byte)65,(byte)68}); // HEAD
+ os.write(new byte[]{(byte)10,(byte)119,(byte)111,(byte)114,(byte)108,(byte)100,(byte)45,(byte)110,
+ (byte)97,(byte)109,(byte)101}); // 10 + world-name
+
+ byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
+ writeInt(nameBytes.length, os);
+ os.write(nameBytes); // write name
+ os.write('>');
+
+ ++totalFileCount;
+
+ os.write(new byte[]{(byte)72,(byte)69,(byte)65,(byte)68}); // HEAD
+ os.write(new byte[]{(byte)11,(byte)119,(byte)111,(byte)114,(byte)108,(byte)100,(byte)45,(byte)111,
+ (byte)119,(byte)110,(byte)101,(byte)114}); // 11 + world-owner
+
+ byte[] ownerBytes = owner.getBytes(StandardCharsets.UTF_8);
+ writeInt(ownerBytes.length, os);
+ os.write(ownerBytes); // write owner
+ os.write('>');
+
+ ++totalFileCount;
+
+ }catch(IOException ex) {
+ throw new RuntimeException("This happened somehow", ex);
+ }
+ }
+
+ public void append(String name, byte[] dat) {
+ try {
+
+ checkSum.reset();
+ checkSum.update(dat, 0, dat.length);
+ long sum = checkSum.getValue();
+
+ os.write(new byte[]{(byte)70,(byte)73,(byte)76,(byte)69}); // FILE
+
+ byte[] nameBytes = name.getBytes(StandardCharsets.UTF_8);
+ os.write(nameBytes.length);
+ os.write(nameBytes);
+
+ writeInt(dat.length + 5, os);
+ writeInt((int)sum, os);
+
+ os.write(dat);
+
+ os.write(':');
+ os.write('>');
+
+ ++totalFileCount;
+
+ }catch(IOException ex) {
+ throw new RuntimeException("This happened somehow", ex);
+ }
+ }
+
+ public byte[] complete() {
+ try {
+
+ os.write(new byte[]{(byte)69,(byte)78,(byte)68,(byte)36}); // END$
+ os.write(new byte[]{(byte)58,(byte)58,(byte)58,(byte)89,(byte)69,(byte)69,(byte)58,(byte)62}); // :::YEE:>
+
+ byte[] ret = os.toByteArray();
+
+ ret[lengthIntegerOffset] = (byte)((totalFileCount >> 24) & 0xFF);
+ ret[lengthIntegerOffset + 1] = (byte)((totalFileCount >> 16) & 0xFF);
+ ret[lengthIntegerOffset + 2] = (byte)((totalFileCount >> 8) & 0xFF);
+ ret[lengthIntegerOffset + 3] = (byte)(totalFileCount & 0xFF);
+
+ return ret;
+
+ }catch(IOException ex) {
+ throw new RuntimeException("This happened somehow", ex);
+ }
+ }
+
+ public static void writeInt(int i, OutputStream os) throws IOException {
+ os.write((i >> 24) & 0xFF);
+ os.write((i >> 16) & 0xFF);
+ os.write((i >> 8) & 0xFF);
+ os.write(i & 0xFF);
+ }
+
+ public static void writeLong(long i, OutputStream os) throws IOException {
+ os.write((int)((i >> 56) & 0xFF));
+ os.write((int)((i >> 48) & 0xFF));
+ os.write((int)((i >> 40) & 0xFF));
+ os.write((int)((i >> 32) & 0xFF));
+ os.write((int)((i >> 24) & 0xFF));
+ os.write((int)((i >> 16) & 0xFF));
+ os.write((int)((i >> 8) & 0xFF));
+ os.write((int)(i & 0xFF));
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/EPKDecompiler.java b/src/main/java/net/lax1dude/eaglercraft/sp/EPKDecompiler.java
new file mode 100644
index 0000000..58986fb
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/EPKDecompiler.java
@@ -0,0 +1,217 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+import com.jcraft.jzlib.CRC32;
+import com.jcraft.jzlib.GZIPInputStream;
+import com.jcraft.jzlib.InflaterInputStream;
+
+public class EPKDecompiler {
+
+ public static class FileEntry {
+ public final String type;
+ public final String name;
+ public final byte[] data;
+ protected FileEntry(String type, String name, byte[] data) {
+ this.type = type;
+ this.name = name;
+ this.data = data;
+ }
+ }
+
+ private ByteArrayInputStream in2;
+ private DataInputStream in;
+ private InputStream zis;
+ private SHA1Digest dg;
+ private CRC32 crc32;
+ private int numFiles;
+ private boolean isFinished = false;
+ private boolean isOldFormat = false;
+
+ public EPKDecompiler(byte[] data) throws IOException {
+ in2 = new ByteArrayInputStream(data);
+
+ byte[] header = new byte[8];
+ in2.read(header);
+
+ if(Arrays.equals(header, new byte[]{(byte)69,(byte)65,(byte)71,(byte)80,(byte)75,(byte)71,(byte)36,(byte)36})) {
+ byte[] endCode = new byte[] { (byte)':', (byte)':', (byte)':', (byte)'Y',
+ (byte)'E', (byte)'E', (byte)':', (byte)'>' };
+ for(int i = 0; i < 8; ++i) {
+ if(data[data.length - 8 + i] != endCode[i]) {
+ throw new IOException("EPK file is missing EOF code (:::YEE:>)");
+ }
+ }
+ in2 = new ByteArrayInputStream(data, 8, data.length - 16);
+ initNew();
+ }else if(Arrays.equals(header, new byte[]{(byte)69,(byte)65,(byte)71,(byte)80,(byte)75,(byte)71,(byte)33,(byte)33})) {
+ initOld();
+ }
+
+ }
+
+ public boolean isOld() {
+ return isOldFormat;
+ }
+
+ public FileEntry readFile() throws IOException {
+ if(!isOldFormat) {
+ return readFileNew();
+ }else {
+ return readFileOld();
+ }
+ }
+
+ private void initNew() throws IOException {
+ InputStream is = in2;
+
+ String vers = readASCII(is);
+ if(!vers.startsWith("ver2.")) {
+ throw new IOException("Unknown or invalid EPK version: " + vers);
+ }
+
+ is.skip(is.read()); // skip filename
+ is.skip(loadShort(is)); // skip comment
+ is.skip(8); // skip millis date
+
+ numFiles = loadInt(is);
+
+ char compressionType = (char)is.read();
+
+ switch(compressionType) {
+ case 'G':
+ zis = new GZIPInputStream(is);
+ break;
+ case 'Z':
+ zis = new InflaterInputStream(is);
+ break;
+ case '0':
+ zis = is;
+ break;
+ default:
+ throw new IOException("Invalid or unsupported EPK compression: " + compressionType);
+ }
+
+ crc32 = new CRC32();
+
+ }
+
+ private FileEntry readFileNew() throws IOException {
+ if(isFinished) {
+ return null;
+ }
+
+ byte[] typeBytes = new byte[4];
+ zis.read(typeBytes);
+ String type = readASCII(typeBytes);
+
+ if(numFiles == 0) {
+ if(!"END$".equals(type)) {
+ throw new IOException("EPK file is missing END code (END$)");
+ }
+ isFinished = true;
+ return null;
+ }else {
+ if("END$".equals(type)) {
+ throw new IOException("Unexpected END when there are still " + numFiles + " files remaining");
+ }else {
+ String name = readASCII(zis);
+ int len = loadInt(zis);
+ byte[] data;
+
+ if("FILE".equals(type)) {
+ if(len < 5) {
+ throw new IOException("File '" + name + "' is incomplete (no crc)");
+ }
+
+ int loadedCrc = loadInt(zis);
+
+ data = new byte[len - 5];
+ zis.read(data);
+
+ crc32.reset();
+ crc32.update(data, 0, data.length);
+ if((int)crc32.getValue() != loadedCrc) {
+ throw new IOException("File '" + name + "' has an invalid checksum");
+ }
+
+ if(zis.read() != ':') {
+ throw new IOException("File '" + name + "' is incomplete");
+ }
+ }else {
+ data = new byte[len];
+ zis.read(data);
+ }
+
+ if(zis.read() != '>') {
+ throw new IOException("Object '" + name + "' is incomplete");
+ }
+
+ --numFiles;
+ return new FileEntry(type, name, data);
+ }
+ }
+ }
+
+ private static final int loadShort(InputStream is) throws IOException {
+ return (is.read() << 8) | is.read();
+ }
+
+ private static final int loadInt(InputStream is) throws IOException {
+ return (is.read() << 24) | (is.read() << 16) | (is.read() << 8) | is.read();
+ }
+
+ public static final String readASCII(byte[] bytesIn) throws IOException {
+ char[] charIn = new char[bytesIn.length];
+ for(int i = 0; i < bytesIn.length; ++i) {
+ charIn[i] = (char)((int)bytesIn[i] & 0xFF);
+ }
+ return new String(charIn);
+ }
+
+ private static final String readASCII(InputStream bytesIn) throws IOException {
+ int len = bytesIn.read();
+ char[] charIn = new char[len];
+ for(int i = 0; i < len; ++i) {
+ charIn[i] = (char)(bytesIn.read() & 0xFF);
+ }
+ return new String(charIn);
+ }
+
+ private void initOld() throws IOException {
+ isOldFormat = true;
+ dg = new SHA1Digest();
+ in = new DataInputStream(in2);
+ in.readUTF();
+ in = new DataInputStream(new InflaterInputStream(in2));
+ }
+
+ private FileEntry readFileOld() throws IOException {
+ if(isFinished) {
+ return null;
+ }
+ String s = in.readUTF();
+ if(s.equals(" end")) {
+ isFinished = true;
+ return null;
+ }else if(!s.equals("")) {
+ throw new IOException("invalid epk file");
+ }
+ String path = in.readUTF();
+ byte[] digest = new byte[20];
+ byte[] digest2 = new byte[20];
+ in.read(digest);
+ int len = in.readInt();
+ byte[] file = new byte[len];
+ in.read(file);
+ dg.update(file, 0, len); dg.doFinal(digest2, 0);
+ if(!Arrays.equals(digest, digest2)) throw new IOException("invalid file hash for "+path);
+ if(!"".equals(in.readUTF())) throw new IOException("invalid epk file");
+ return new FileEntry("FILE", path, file);
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/EaglerUUID.java b/src/main/java/net/lax1dude/eaglercraft/sp/EaglerUUID.java
new file mode 100644
index 0000000..b5b1a67
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/EaglerUUID.java
@@ -0,0 +1,189 @@
+package net.lax1dude.eaglercraft.sp;
+
+public class EaglerUUID {
+
+ private final long mostSigBits;
+ private final long leastSigBits;
+
+ private EaglerUUID(byte[] data) {
+ long msb = 0;
+ long lsb = 0;
+ assert data.length == 16 : "data must be 16 bytes in length";
+ for (int i=0; i<8; i++)
+ msb = (msb << 8) | (data[i] & 0xff);
+ for (int i=8; i<16; i++)
+ lsb = (lsb << 8) | (data[i] & 0xff);
+ this.mostSigBits = msb;
+ this.leastSigBits = lsb;
+ }
+
+ public EaglerUUID(long mostSigBits, long leastSigBits) {
+ this.mostSigBits = mostSigBits;
+ this.leastSigBits = leastSigBits;
+ }
+
+ private static final EaglercraftRandom random = new EaglercraftRandom();
+
+ public static EaglerUUID randomUUID() {
+ byte[] randomBytes = new byte[16];
+ random.nextBytes(randomBytes);
+ randomBytes[6] &= 0x0f; /* clear version */
+ randomBytes[6] |= 0x40; /* set to version 4 */
+ randomBytes[8] &= 0x3f; /* clear variant */
+ randomBytes[8] |= 0x80; /* set to IETF variant */
+ return new EaglerUUID(randomBytes);
+ }
+
+ private static final MD5Digest yee = new MD5Digest();
+
+ public static EaglerUUID nameUUIDFromBytes(byte[] name) {
+ yee.update(name, 0, name.length);
+ byte[] md5Bytes = new byte[16];
+ yee.doFinal(md5Bytes, 0);
+ md5Bytes[6] &= 0x0f; /* clear version */
+ md5Bytes[6] |= 0x30; /* set to version 3 */
+ md5Bytes[8] &= 0x3f; /* clear variant */
+ md5Bytes[8] |= 0x80; /* set to IETF variant */
+ return new EaglerUUID(md5Bytes);
+ }
+
+ public static EaglerUUID fromString(String name) {
+ String[] components = name.split("-");
+ if (components.length != 5)
+ throw new IllegalArgumentException("Invalid UUID string: "+name);
+ for (int i=0; i<5; i++)
+ components[i] = "0x"+components[i];
+
+ long mostSigBits = Long.decode(components[0]).longValue();
+ mostSigBits <<= 16;
+ mostSigBits |= Long.decode(components[1]).longValue();
+ mostSigBits <<= 16;
+ mostSigBits |= Long.decode(components[2]).longValue();
+
+ long leastSigBits = Long.decode(components[3]).longValue();
+ leastSigBits <<= 48;
+ leastSigBits |= Long.decode(components[4]).longValue();
+
+ return new EaglerUUID(mostSigBits, leastSigBits);
+ }
+
+ public long getLeastSignificantBits() {
+ return leastSigBits;
+ }
+
+ public long getMostSignificantBits() {
+ return mostSigBits;
+ }
+
+ /**
+ * The version number associated with this {@code UUID}. The version
+ * number describes how this {@code UUID} was generated.
+ *
+ * The version number has the following meaning:
+ *
+ * - 1 Time-based UUID
+ *
- 2 DCE security UUID
+ *
- 3 Name-based UUID
+ *
- 4 Randomly generated UUID
+ *
+ *
+ * @return The version number of this {@code UUID}
+ */
+ public int version() {
+ // Version is bits masked by 0x000000000000F000 in MS long
+ return (int)((mostSigBits >> 12) & 0x0f);
+ }
+
+ /**
+ * The variant number associated with this {@code UUID}. The variant
+ * number describes the layout of the {@code UUID}.
+ *
+ * The variant number has the following meaning:
+ *
+ * - 0 Reserved for NCS backward compatibility
+ *
- 2 IETF RFC 4122
+ * (Leach-Salz), used by this class
+ *
- 6 Reserved, Microsoft Corporation backward compatibility
+ *
- 7 Reserved for future definition
+ *
+ *
+ * @return The variant number of this {@code UUID}
+ */
+ public int variant() {
+ // This field is composed of a varying number of bits.
+ // 0 - - Reserved for NCS backward compatibility
+ // 1 0 - The IETF aka Leach-Salz variant (used by this class)
+ // 1 1 0 Reserved, Microsoft backward compatibility
+ // 1 1 1 Reserved for future definition.
+ return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62)))
+ & (leastSigBits >> 63));
+ }
+
+ public long timestamp() {
+ if (version() != 1) {
+ throw new UnsupportedOperationException("Not a time-based UUID");
+ }
+
+ return (mostSigBits & 0x0FFFL) << 48
+ | ((mostSigBits >> 16) & 0x0FFFFL) << 32
+ | mostSigBits >>> 32;
+ }
+
+ /**
+ * The clock sequence value associated with this UUID.
+ *
+ * The 14 bit clock sequence value is constructed from the clock
+ * sequence field of this UUID. The clock sequence field is used to
+ * guarantee temporal uniqueness in a time-based UUID.
+ *
+ *
The {@code clockSequence} value is only meaningful in a time-based
+ * UUID, which has version type 1. If this UUID is not a time-based UUID
+ * then this method throws UnsupportedOperationException.
+ *
+ * @return The clock sequence of this {@code UUID}
+ *
+ * @throws UnsupportedOperationException
+ * If this UUID is not a version 1 UUID
+ */
+ public int clockSequence() {
+ if (version() != 1) {
+ throw new UnsupportedOperationException("Not a time-based UUID");
+ }
+
+ return (int)((leastSigBits & 0x3FFF000000000000L) >>> 48);
+ }
+
+ public String toString() {
+ return (digits(mostSigBits >> 32, 8) + "-" +
+ digits(mostSigBits >> 16, 4) + "-" +
+ digits(mostSigBits, 4) + "-" +
+ digits(leastSigBits >> 48, 4) + "-" +
+ digits(leastSigBits, 12));
+ }
+
+ private static String digits(long val, int digits) {
+ long hi = 1L << (digits * 4);
+ return Long.toHexString(hi | (val & (hi - 1))).substring(1);
+ }
+
+ public int hashCode() {
+ long hilo = mostSigBits ^ leastSigBits;
+ return ((int)(hilo >> 32)) ^ (int) hilo;
+ }
+
+ public boolean equals(Object obj) {
+ if ((null == obj) || !(obj instanceof EaglerUUID))
+ return false;
+ EaglerUUID id = (EaglerUUID)obj;
+ return (mostSigBits == id.mostSigBits &&
+ leastSigBits == id.leastSigBits);
+ }
+
+ public int compareTo(EaglerUUID val) {
+ return (this.mostSigBits < val.mostSigBits ? -1 :
+ (this.mostSigBits > val.mostSigBits ? 1 :
+ (this.leastSigBits < val.leastSigBits ? -1 :
+ (this.leastSigBits > val.leastSigBits ? 1 :
+ 0))));
+ }
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/EaglercraftRandom.java b/src/main/java/net/lax1dude/eaglercraft/sp/EaglercraftRandom.java
new file mode 100644
index 0000000..52c0ab0
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/EaglercraftRandom.java
@@ -0,0 +1,84 @@
+package net.lax1dude.eaglercraft.sp;
+
+public class EaglercraftRandom {
+
+ private static final long multiplier = 0x5DEECE66DL;
+ private static final long addend = 0xBL;
+ private static final long mask = (1L << 48) - 1;
+
+ private static final double DOUBLE_UNIT = 0x1.0p-53;
+ private long seed = 69;
+
+ public EaglercraftRandom() {
+ this(System.nanoTime());
+ }
+
+ public EaglercraftRandom(long seed) {
+ setSeed(seed);
+ }
+
+ public void setSeed(long yeed) {
+ seed = yeed;
+ }
+
+ protected int next(int bits) {
+ seed = (seed * multiplier + addend) & mask;
+ return (int)(seed >>> (48 - bits));
+ }
+
+ public void nextBytes(byte[] bytes) {
+ for (int i = 0, len = bytes.length; i < len; )
+ for (int rnd = nextInt(),
+ n = Math.min(len - i, Integer.SIZE/Byte.SIZE);
+ n-- > 0; rnd >>= Byte.SIZE)
+ bytes[i++] = (byte)rnd;
+ }
+ public int nextInt() {
+ return next(32);
+ }
+ public int nextInt(int bound) {
+ int r = next(31);
+ int m = bound - 1;
+ if ((bound & m) == 0) // i.e., bound is a power of 2
+ r = (int)((bound * (long)r) >> 31);
+ else {
+ for (int u = r;
+ u - (r = u % bound) + m < 0;
+ u = next(31))
+ ;
+ }
+ return r;
+ }
+ public long nextLong() {
+ return ((long)(next(32)) << 32) + next(32);
+ }
+ public boolean nextBoolean() {
+ return next(1) != 0;
+ }
+ public float nextFloat() {
+ return next(24) / ((float)(1 << 24));
+ }
+ public double nextDouble() {
+ return (((long)(next(26)) << 27) + next(27)) * DOUBLE_UNIT;
+ }
+ private double nextNextGaussian;
+ private boolean haveNextNextGaussian = false;
+ public double nextGaussian() {
+ // See Knuth, ACP, Section 3.4.1 Algorithm C.
+ if (haveNextNextGaussian) {
+ haveNextNextGaussian = false;
+ return nextNextGaussian;
+ } else {
+ double v1, v2, s;
+ do {
+ v1 = 2 * nextDouble() - 1; // between -1 and 1
+ v2 = 2 * nextDouble() - 1; // between -1 and 1
+ s = v1 * v1 + v2 * v2;
+ } while (s >= 1 || s == 0);
+ double multiplier = StrictMath.sqrt(-2 * StrictMath.log(s)/s);
+ nextNextGaussian = v2 * multiplier;
+ haveNextNextGaussian = true;
+ return v1 * multiplier;
+ }
+ }
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/ExpiringSet.java b/src/main/java/net/lax1dude/eaglercraft/sp/ExpiringSet.java
new file mode 100644
index 0000000..64ed0cf
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/ExpiringSet.java
@@ -0,0 +1,74 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+// note that there's a few things not implemented, but I don't care.
+
+public class ExpiringSet extends HashSet {
+ private final long expiration;
+ private final ExpiringEvent event;
+
+ private final Map timestamps = new HashMap<>();
+
+ public ExpiringSet(long expiration) {
+ this.expiration = expiration;
+ this.event = null;
+ }
+
+ public ExpiringSet(long expiration, ExpiringEvent event) {
+ this.expiration = expiration;
+ this.event = event;
+ }
+
+ public interface ExpiringEvent {
+ void onExpiration(T item);
+ }
+
+ public void checkForExpirations() {
+ Iterator iterator = this.timestamps.keySet().iterator();
+ long now = SysUtil.steadyTimeMillis();
+ while (iterator.hasNext()) {
+ T element = iterator.next();
+ if (super.contains(element)) {
+ if (this.timestamps.get(element) + this.expiration < now) {
+ if (this.event != null)
+ this.event.onExpiration(element);
+ iterator.remove();
+ super.remove(element);
+ }
+ } else {
+ iterator.remove();
+ super.remove(element);
+ }
+ }
+ }
+
+ public boolean add(T o) {
+ checkForExpirations();
+ boolean success = super.add(o);
+ if (success)
+ timestamps.put(o, SysUtil.steadyTimeMillis());
+ return success;
+ }
+
+ public boolean remove(Object o) {
+ checkForExpirations();
+ boolean success = super.remove(o);
+ if (success)
+ timestamps.remove(o);
+ return success;
+ }
+
+ public void clear() {
+ this.timestamps.clear();
+ super.clear();
+ }
+
+ public boolean contains(Object o) {
+ checkForExpirations();
+ return super.contains(o);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/GeneralDigest.java b/src/main/java/net/lax1dude/eaglercraft/sp/GeneralDigest.java
new file mode 100644
index 0000000..7229c20
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/GeneralDigest.java
@@ -0,0 +1,124 @@
+package net.lax1dude.eaglercraft.sp;
+
+/**
+ * base implementation of MD4 family style digest as outlined in
+ * "Handbook of Applied Cryptography", pages 344 - 347.
+ */
+public abstract class GeneralDigest {
+ private byte[] xBuf;
+ private int xBufOff;
+
+ private long byteCount;
+
+ /**
+ * Standard constructor
+ */
+ protected GeneralDigest()
+ {
+ xBuf = new byte[4];
+ xBufOff = 0;
+ }
+
+ /**
+ * Copy constructor. We are using copy constructors in place
+ * of the Object.clone() interface as this interface is not
+ * supported by J2ME.
+ */
+ protected GeneralDigest(GeneralDigest t)
+ {
+ xBuf = new byte[t.xBuf.length];
+ System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length);
+
+ xBufOff = t.xBufOff;
+ byteCount = t.byteCount;
+ }
+
+ public void update(
+ byte in)
+ {
+ xBuf[xBufOff++] = in;
+
+ if (xBufOff == xBuf.length)
+ {
+ processWord(xBuf, 0);
+ xBufOff = 0;
+ }
+
+ byteCount++;
+ }
+
+ public void update(
+ byte[] in,
+ int inOff,
+ int len)
+ {
+ //
+ // fill the current word
+ //
+ while ((xBufOff != 0) && (len > 0))
+ {
+ update(in[inOff]);
+
+ inOff++;
+ len--;
+ }
+
+ //
+ // process whole words.
+ //
+ while (len > xBuf.length)
+ {
+ processWord(in, inOff);
+
+ inOff += xBuf.length;
+ len -= xBuf.length;
+ byteCount += xBuf.length;
+ }
+
+ //
+ // load in the remainder.
+ //
+ while (len > 0)
+ {
+ update(in[inOff]);
+
+ inOff++;
+ len--;
+ }
+ }
+
+ public void finish()
+ {
+ long bitLength = (byteCount << 3);
+
+ //
+ // add the pad bytes.
+ //
+ update((byte)128);
+
+ while (xBufOff != 0)
+ {
+ update((byte)0);
+ }
+
+ processLength(bitLength);
+
+ processBlock();
+ }
+
+ public void reset()
+ {
+ byteCount = 0;
+
+ xBufOff = 0;
+ for ( int i = 0; i < xBuf.length; i++ ) {
+ xBuf[i] = 0;
+ }
+ }
+
+ protected abstract void processWord(byte[] in, int inOff);
+
+ protected abstract void processLength(long bitLength);
+
+ protected abstract void processBlock();
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/IntegratedServer.java b/src/main/java/net/lax1dude/eaglercraft/sp/IntegratedServer.java
new file mode 100644
index 0000000..2d807b4
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/IntegratedServer.java
@@ -0,0 +1,555 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.teavm.jso.JSBody;
+import org.teavm.jso.JSFunctor;
+import org.teavm.jso.JSObject;
+import org.teavm.jso.typedarrays.ArrayBuffer;
+
+import net.lax1dude.eaglercraft.sp.ipc.*;
+import net.minecraft.src.AchievementList;
+import net.minecraft.src.AchievementMap;
+import net.minecraft.src.CompressedStreamTools;
+import net.minecraft.src.EnumGameType;
+import net.minecraft.src.ILogAgent;
+import net.minecraft.src.NBTTagCompound;
+import net.minecraft.src.StringTranslate;
+import net.minecraft.src.WorldSettings;
+import net.minecraft.src.WorldType;
+
+public class IntegratedServer {
+
+ private static final LinkedList messageQueue = new LinkedList<>();
+
+ protected static class PKT {
+ protected final String channel;
+ protected final byte[] data;
+ protected PKT(String channel, byte[] data) {
+ this.channel = channel;
+ this.data = data;
+ }
+ }
+
+ private static EAGMinecraftServer currentProcess = null;
+ private static WorldSettings newWorldSettings = null;
+
+ public static EAGMinecraftServer getServer() {
+ return currentProcess;
+ }
+
+ public static final ILogAgent logger = new EAGLogAgent();
+
+ @JSFunctor
+ private static interface WorkerBinaryPacketHandler extends JSObject {
+ public void onMessage(String channel, ArrayBuffer buf);
+ }
+
+ private static class WorkerBinaryPacketHandlerImpl implements WorkerBinaryPacketHandler {
+
+ public void onMessage(String channel, ArrayBuffer buf) {
+ if(channel == null) {
+ System.err.println("Recieved IPC packet with null channel");
+ return;
+ }
+
+ if(buf == null) {
+ System.err.println("Recieved IPC packet with null buffer");
+ return;
+ }
+
+ synchronized(messageQueue) {
+ messageQueue.add(new PKT(channel, TeaVMUtils.wrapByteArrayBuffer(buf)));
+ }
+ }
+
+ }
+
+ private static void tryStopServer() {
+ if(currentProcess != null) {
+ try {
+ currentProcess.stopServer();
+ }catch(Throwable t) {
+ System.err.println("Failed to stop server!");
+ throwExceptionToClient("Failed to stop server!", t);
+ }
+ currentProcess = null;
+ }
+ }
+
+ public static void updateStatusString(String stat, float prog) {
+ sendIPCPacket(new IPCPacket0DProgressUpdate(stat, prog));
+ }
+
+ private static boolean isServerStopped() {
+ return currentProcess == null || !currentProcess.isServerRunning();
+ }
+
+ public static void throwExceptionToClient(String msg, Throwable t) {
+ String str = t.toString();
+ System.err.println("Exception was raised to client: " + str);
+ t.printStackTrace();
+ List arr = new LinkedList<>();
+ for(StackTraceElement e : t.getStackTrace()) {
+ String st = e.toString();
+ arr.add(st);
+ }
+ sendIPCPacket(new IPCPacket15ThrowException(str, arr));
+ }
+
+ public static void sendTaskFailed() {
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacketFFProcessKeepAlive.FAILURE));
+ }
+
+ private static void processAsyncMessageQueue() {
+ ArrayList cur;
+ synchronized(messageQueue) {
+ if(messageQueue.size() <= 0) {
+ return;
+ }
+ cur = new ArrayList(messageQueue);
+ messageQueue.clear();
+ }
+ Iterator itr = cur.iterator();
+ while(itr.hasNext()) {
+ PKT msg = itr.next();
+ if(msg.channel.equals("IPC")) {
+
+ IPCPacketBase packet;
+ try {
+ packet = IPCPacketManager.IPCDeserialize(msg.data);
+ }catch(IOException e) {
+ System.err.print("Failed to deserialize IPC packet: ");
+ e.printStackTrace();
+ continue;
+ }
+
+ int id = packet.id();
+
+ try {
+ switch(id) {
+ case IPCPacket00StartServer.ID: {
+ IPCPacket00StartServer pkt = (IPCPacket00StartServer)packet;
+
+ if(!isServerStopped()) {
+ currentProcess.stopServer();
+ }
+
+ currentProcess = new EAGMinecraftServer(pkt.worldName, pkt.ownerName, newWorldSettings);
+ currentProcess.setBaseServerProperties(pkt.initialDifficulty, newWorldSettings == null ? EnumGameType.SURVIVAL : newWorldSettings.getGameType());
+ currentProcess.startServer();
+
+ String[] worlds = SYS.VFS.getFile("worlds.txt").getAllLines();
+ if(worlds == null || (worlds.length == 1 && worlds[0].trim().length() <= 0)) {
+ worlds = null;
+ }
+ if(worlds == null) {
+ SYS.VFS.getFile("worlds.txt").setAllChars(pkt.worldName);
+ }else {
+ boolean found = false;
+ for(String s : worlds) {
+ if(s.equals(pkt.worldName)) {
+ found = true;
+ break;
+ }
+ }
+ if(!found) {
+ String[] s = new String[worlds.length + 1];
+ s[0] = pkt.worldName;
+ System.arraycopy(worlds, 0, s, 1, worlds.length);
+ SYS.VFS.getFile("worlds.txt").setAllChars(String.join("\n", s));
+ }
+ }
+
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket00StartServer.ID));
+ }
+ break;
+ case IPCPacket01StopServer.ID: {
+ if(!isServerStopped()) {
+ try {
+ currentProcess.stopServer();
+ currentProcess = null;
+ }catch(Throwable t) {
+ throwExceptionToClient("Failed to stop server!", t);
+ }
+ }else {
+ System.err.println("Client tried to stop server while it wasn't running for some reason");
+ }
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket01StopServer.ID));
+ }
+ break;
+ case IPCPacket02InitWorld.ID: {
+ tryStopServer();
+ IPCPacket02InitWorld pkt = (IPCPacket02InitWorld)packet;
+ newWorldSettings = new WorldSettings(pkt.seed, pkt.gamemode == 1 ? EnumGameType.CREATIVE : EnumGameType.SURVIVAL, pkt.structures,
+ pkt.gamemode == 2, pkt.worldType == 1 ? WorldType.FLAT : (pkt.worldType == 2 ? WorldType.LARGE_BIOMES : WorldType.DEFAULT_1_1));
+ newWorldSettings.func_82750_a(pkt.worldArgs);
+ if(pkt.bonusChest) {
+ newWorldSettings.enableBonusChest();
+ }
+ if(pkt.cheats) {
+ newWorldSettings.enableCommands();
+ }
+ }
+ break;
+ case IPCPacket03DeleteWorld.ID: {
+ tryStopServer();
+ IPCPacket03DeleteWorld pkt = (IPCPacket03DeleteWorld)packet;
+ if(SYS.VFS.deleteFiles("worlds/" + pkt.worldName + "/") <= 0) {
+ throwExceptionToClient("Failed to delete world!", new RuntimeException("VFS did not delete directory 'worlds/" + pkt.worldName + "' correctly"));
+ sendTaskFailed();
+ break;
+ }
+ String[] worldsTxt = SYS.VFS.getFile("worlds.txt").getAllLines();
+ if(worldsTxt != null) {
+ LinkedList newWorlds = new LinkedList<>();
+ for(String str : worldsTxt) {
+ if(!str.equalsIgnoreCase(pkt.worldName)) {
+ newWorlds.add(str);
+ }
+ }
+ SYS.VFS.getFile("worlds.txt").setAllChars(String.join("\n", newWorlds));
+ }
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket03DeleteWorld.ID));
+ }
+ break;
+ case IPCPacket04RenameWorld.ID: {
+ tryStopServer();
+ IPCPacket04RenameWorld pkt = (IPCPacket04RenameWorld)packet;
+ if(SYS.VFS.renameFiles("worlds/" + pkt.worldOldName + "/", "worlds/" + pkt.worldNewName + "/", pkt.copy) <= 0) {
+ throwExceptionToClient("Failed to copy/rename server!", new RuntimeException("VFS did not copy/rename directory 'worlds/" + pkt.worldOldName + "' correctly"));
+ sendTaskFailed();
+ break;
+ }else {
+ String[] worldsTxt = SYS.VFS.getFile("worlds.txt").getAllLines();
+ LinkedList newWorlds = new LinkedList<>();
+ if(worldsTxt != null) {
+ for(String str : worldsTxt) {
+ if(pkt.copy || !str.equalsIgnoreCase(pkt.worldOldName)) {
+ newWorlds.add(str);
+ }
+ }
+ }
+ newWorlds.add(pkt.worldNewName);
+ SYS.VFS.getFile("worlds.txt").setAllChars(String.join("\n", newWorlds));
+ VFile worldDat = new VFile("worlds", pkt.worldNewName, "level.dat");
+ if(worldDat.canRead()) {
+ NBTTagCompound worldDatNBT = CompressedStreamTools.decompress(worldDat.getAllBytes());
+ worldDatNBT.getCompoundTag("Data").setString("LevelName", pkt.displayName);
+ worldDat.setAllBytes(CompressedStreamTools.compress(worldDatNBT));
+ }else {
+ throwExceptionToClient("Failed to copy/rename world!", new RuntimeException("Failed to change level.dat world '" + pkt.worldNewName + "' display name to '" + pkt.displayName + "' because level.dat was missing"));
+ sendTaskFailed();
+ break;
+ }
+ }
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket04RenameWorld.ID));
+ }
+ break;
+ case IPCPacket05RequestData.ID: {
+ IPCPacket05RequestData pkt = (IPCPacket05RequestData)packet;
+ if(pkt.request == IPCPacket05RequestData.REQUEST_LEVEL_EAG) {
+ try {
+ sendIPCPacket(new IPCPacket09RequestResponse(WorldConverterEPK.exportWorld(pkt.worldName)));
+ } catch (Throwable t) {
+ String realWorldName = pkt.worldName;
+ int i = realWorldName.lastIndexOf(new String(new char[] { (char)253, (char)233, (char)233 }));
+ if(i != -1) {
+ realWorldName = realWorldName.substring(0, i);
+ }
+ throwExceptionToClient("Failed to export world '" + realWorldName+ "' as EPK", t);
+ sendTaskFailed();
+ }
+ }else if(pkt.request == IPCPacket05RequestData.REQUEST_LEVEL_MCA) {
+ try {
+ sendIPCPacket(new IPCPacket09RequestResponse(WorldConverterMCA.exportWorld(pkt.worldName)));
+ } catch (Throwable t) {
+ throwExceptionToClient("Failed to export world '" + pkt.worldName+ "' as MCA", t);
+ sendTaskFailed();
+ }
+ }
+ }
+ break;
+ case IPCPacket06RenameWorldNBT.ID: {
+ IPCPacket06RenameWorldNBT pkt = (IPCPacket06RenameWorldNBT)packet;
+ if(isServerStopped()) {
+ VFile worldDat = new VFile("worlds", pkt.worldName, "level.dat");
+ if(worldDat.canRead()) {
+ NBTTagCompound worldDatNBT = CompressedStreamTools.decompress(worldDat.getAllBytes());
+ worldDatNBT.getCompoundTag("Data").setString("LevelName", pkt.displayName);
+ worldDat.setAllBytes(CompressedStreamTools.compress(worldDatNBT));
+ }else {
+ throwExceptionToClient("Failed to rename world!", new RuntimeException("Failed to change level.dat world '" + pkt.worldName + "' display name to '" + pkt.displayName + "' because level.dat was missing"));
+ }
+ }else {
+ System.err.println("Client tried to rename a world '" + pkt.worldName + "' to have name '" + pkt.displayName + "' while the server is running");
+ sendTaskFailed();
+ }
+ }
+ break;
+ case IPCPacket07ImportWorld.ID: {
+ IPCPacket07ImportWorld pkt = (IPCPacket07ImportWorld)packet;
+ if(isServerStopped()) {
+ if(pkt.worldFormat == IPCPacket07ImportWorld.WORLD_FORMAT_EAG) {
+ try {
+ WorldConverterEPK.importWorld(pkt.worldData, pkt.worldName);
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket07ImportWorld.ID));
+ }catch(Throwable t) {
+ SYS.VFS.deleteFiles("worlds/" + VFSSaveHandler.worldNameToFolderName(pkt.worldName) + "/");
+ throwExceptionToClient("Failed to import world '" + pkt.worldName + "' as EPK", t);
+ sendTaskFailed();
+ }
+ }else if(pkt.worldFormat == IPCPacket07ImportWorld.WORLD_FORMAT_MCA) {
+ try {
+ WorldConverterMCA.importWorld(pkt.worldData, pkt.worldName);
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket07ImportWorld.ID));
+ }catch(Throwable t) {
+ SYS.VFS.deleteFiles("worlds/" + VFSSaveHandler.worldNameToFolderName(pkt.worldName) + "/");
+ throwExceptionToClient("Failed to import world '" + pkt.worldName + "' as MCA", t);
+ sendTaskFailed();
+ }
+ }else {
+ System.err.println("Client tried to import a world in an unknown format: 0x" + Integer.toHexString(pkt.worldFormat));
+ sendTaskFailed();
+ }
+ }else {
+ System.err.println("Client tried to import a world '" + pkt.worldName + "' while the server is running");
+ sendTaskFailed();
+ }
+ }
+ break;
+ case IPCPacket09RequestResponse.ID:
+
+ break;
+ case IPCPacket0ASetWorldDifficulty.ID: {
+ IPCPacket0ASetWorldDifficulty pkt = (IPCPacket0ASetWorldDifficulty)packet;
+ if(!isServerStopped()) {
+ currentProcess.setDifficultyForAllWorlds(pkt.difficulty);
+ }else {
+ System.err.println("Client tried to set difficulty '" + pkt.difficulty + "' while server was stopped");
+ sendTaskFailed();
+ }
+ }
+ break;
+ case IPCPacket0BPause.ID: {
+ IPCPacket0BPause pkt = (IPCPacket0BPause)packet;
+ if(!isServerStopped()) {
+ if(!pkt.pause && !currentProcess.getPaused()) {
+ currentProcess.saveAllWorlds(true);
+ }else {
+ currentProcess.setPaused(pkt.pause);
+ if(pkt.pause) {
+ currentProcess.saveAllWorlds(true);
+ }
+ }
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket0BPause.ID));
+ }else {
+ System.err.println("Client tried to " + (pkt.pause ? "pause" : "unpause") + " while server was stopped");
+ }
+ }
+ break;
+ case IPCPacket0CPlayerChannel.ID: {
+ IPCPacket0CPlayerChannel pkt = (IPCPacket0CPlayerChannel)packet;
+ if(!isServerStopped()) {
+ if(pkt.open) {
+ if(!currentProcess.getNetworkThread().openChannel(pkt.channel)) {
+ System.err.println("Client tried to open a duplicate channel '" + pkt.channel + "'");
+ }
+ }else {
+ if(!currentProcess.getNetworkThread().closeChannel(pkt.channel)) {
+ System.err.println("Client tried to close a null channel '" + pkt.channel + "'");
+ }
+ }
+ }else {
+ System.err.println("Client tried to " + (pkt.open ? "open" : "close") + " channel '" + pkt.channel + "' while server was stopped");
+ }
+ }
+ break;
+ case IPCPacket0EListWorlds.ID: {
+ if(isServerStopped()) {
+ String[] worlds = SYS.VFS.getFile("worlds.txt").getAllLines();
+ if(worlds == null || (worlds.length == 1 && worlds[0].trim().length() <= 0)) {
+ worlds = null;
+ }
+ if(worlds == null) {
+ sendIPCPacket(new IPCPacket16NBTList(IPCPacket16NBTList.WORLD_LIST, new LinkedList()));
+ break;
+ }
+ LinkedList updatedList = new LinkedList<>();
+ LinkedList sendListNBT = new LinkedList<>();
+ boolean rewrite = false;
+ for(String w : worlds) {
+ byte[] dat = (new VFile("worlds", w, "level.dat")).getAllBytes();
+ if(dat != null) {
+ NBTTagCompound worldDatNBT;
+ try {
+ worldDatNBT = CompressedStreamTools.decompress(dat);
+ worldDatNBT.setString("folderName", w);
+ sendListNBT.add(worldDatNBT);
+ updatedList.add(w);
+ continue;
+ }catch(IOException e) {
+ // shit fuck
+ }
+
+ }
+ rewrite = true;
+ System.err.println("World level.dat for '" + w + "' was not found, attempting to delete 'worlds/" + w + "/*'");
+ if(SYS.VFS.deleteFiles("worlds/" + w) <= 0) {
+ System.err.println("No files were deleted in 'worlds/" + w + "/*', this may be corruption but '" + w + "' will still be removed from worlds.txt");
+ }
+ }
+ if(rewrite) {
+ SYS.VFS.getFile("worlds.txt").setAllChars(String.join("\n", updatedList));
+ }
+ sendIPCPacket(new IPCPacket16NBTList(IPCPacket16NBTList.WORLD_LIST, sendListNBT));
+ }else {
+ System.err.println("Client tried to list worlds while server was running");
+ sendTaskFailed();
+ }
+ }
+ break;
+ case IPCPacket0FListFiles.ID:
+
+ break;
+ case IPCPacket10FileRead.ID:
+
+ break;
+ case IPCPacket12FileWrite.ID:
+
+ break;
+ case IPCPacket13FileCopyMove.ID:
+
+ break;
+ case IPCPacket14StringList.ID: {
+ IPCPacket14StringList pkt = (IPCPacket14StringList)packet;
+ switch(pkt.opCode) {
+ case IPCPacket14StringList.LOCALE:
+ StringTranslate.init(pkt.stringList);
+ break;
+ case IPCPacket14StringList.STAT_GUID:
+ AchievementMap.init(pkt.stringList);
+ AchievementList.init();
+ break;
+ default:
+ System.err.println("Strange string list 0x" + Integer.toHexString(pkt.opCode) + " with length " + pkt.stringList.size() + " recieved");
+ break;
+ }
+ }
+ break;
+ case IPCPacket17ConfigureLAN.ID: {
+ IPCPacket17ConfigureLAN pkt = (IPCPacket17ConfigureLAN)packet;
+ //currentProcess.getConfigurationManager().configureLAN(pkt.gamemode, pkt.cheats, pkt.iceServers); // FIX THIS SHIT
+ }
+ break;
+ case IPCPacket18ClearPlayers.ID: {
+ SYS.VFS.deleteFiles("worlds/" + ((IPCPacket18ClearPlayers)packet).worldName + "/player");
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket18ClearPlayers.ID));
+ }
+ break;
+ default:
+ System.err.println("IPC packet type 0x" + Integer.toHexString(id) + " class '" + packet.getClass().getSimpleName() + "' was not handled");
+ sendTaskFailed();
+ break;
+ }
+ }catch(Throwable t) {
+ String str = "IPC packet 0x" + Integer.toHexString(id) + " class '" + packet.getClass().getSimpleName() + "' was not processed correctly";
+ System.err.println(str);
+ throwExceptionToClient(str, t);
+ sendTaskFailed();
+ }
+
+ continue;
+ }
+ }
+ long watchDog = SysUtil.steadyTimeMillis();
+ itr = cur.iterator();
+ int overflow = 0;
+ while(itr.hasNext()) {
+ PKT msg = itr.next();
+ if(!msg.channel.equals("IPC")) {
+ if(SysUtil.steadyTimeMillis() - watchDog > 500l) {
+ ++overflow;
+ continue;
+ }
+ if(!msg.channel.startsWith("NET|") || currentProcess == null) {
+ //System.err.println("Unknown ICP channel: '" + msg.channel + "' passed " + msg.data.length + " bytes");
+ continue;
+ }
+ String u = msg.channel.substring(4);
+ currentProcess.getNetworkThread().recievePacket(u, msg.data);
+ }
+ }
+ if(overflow > 0) {
+ System.err.println("Async ICP queue is overloaded, server dropped " + overflow + " player packets");
+ }
+ }
+
+ @JSBody(params = { "ch", "dat" }, script = "postMessage({ ch: ch, dat : dat });")
+ private static native void sendWorkerPacket(String channel, ArrayBuffer arr);
+
+ public static void sendIPCPacket(IPCPacketBase pkt) {
+ byte[] serialized;
+
+ try {
+ serialized = IPCPacketManager.IPCSerialize(pkt);
+ } catch (IOException e) {
+ System.err.println("Could not serialize IPC packet 0x" + Integer.toHexString(pkt.id()) + " class '" + pkt.getClass().getSimpleName() + "'");
+ e.printStackTrace();
+ return;
+ }
+
+ sendWorkerPacket("IPC", TeaVMUtils.unwrapArrayBuffer(serialized));
+ }
+
+ public static void sendPlayerPacket(String channel, byte[] buf) {
+ //System.out.println("[Server][SEND][" + channel + "]: " + buf.length);
+ sendWorkerPacket("NET|" + channel, TeaVMUtils.unwrapArrayBuffer(buf));
+ }
+
+ private static boolean isRunning = false;
+
+ public static void halt() {
+ isRunning = false;
+ }
+
+ private static void mainLoop() {
+ processAsyncMessageQueue();
+
+ if(currentProcess != null) {
+ currentProcess.mainLoop();
+ if(currentProcess.isServerStopped()) {
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(IPCPacket01StopServer.ID));
+ currentProcess = null;
+ }
+ }else {
+ SysUtil.sleep(50);
+ }
+ }
+
+ @JSBody(params = { "wb" }, script = "onmessage = function(o) { wb(o.data.ch, o.data.dat); };")
+ private static native void registerPacketHandler(WorkerBinaryPacketHandler wb);
+
+ public static void main(String[] args) {
+
+ registerPacketHandler(new WorkerBinaryPacketHandlerImpl());
+
+ isRunning = true;
+
+ sendIPCPacket(new IPCPacketFFProcessKeepAlive(0xFF));
+
+ while(isRunning) {
+
+ mainLoop();
+
+ SysUtil.immediateContinue();
+ }
+
+ // yee
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/MD5Digest.java b/src/main/java/net/lax1dude/eaglercraft/sp/MD5Digest.java
new file mode 100644
index 0000000..8f007c3
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/MD5Digest.java
@@ -0,0 +1,244 @@
+package net.lax1dude.eaglercraft.sp;
+
+/**
+ * implementation of MD5 as outlined in "Handbook of Applied Cryptography",
+ * pages 346 - 347.
+ */
+public class MD5Digest extends GeneralDigest {
+ private static final int DIGEST_LENGTH = 16;
+
+ private int H1, H2, H3, H4; // IV's
+
+ private int[] X = new int[16];
+ private int xOff;
+
+ public String getAlgorithmName() {
+ return "MD5";
+ }
+
+ public int getDigestSize() {
+ return DIGEST_LENGTH;
+ }
+
+ protected void processWord(byte[] in, int inOff) {
+ X[xOff++] = littleEndianToInt(in, inOff);
+
+ if (xOff == 16) {
+ processBlock();
+ }
+ }
+
+ private int littleEndianToInt(byte[] bs, int off) {
+ int n = bs[off] & 0xff;
+ n |= (bs[++off] & 0xff) << 8;
+ n |= (bs[++off] & 0xff) << 16;
+ n |= bs[++off] << 24;
+ return n;
+ }
+
+ protected void processLength(long bitLength) {
+ if (xOff > 14) {
+ processBlock();
+ }
+
+ X[14] = (int) (bitLength & 0xffffffff);
+ X[15] = (int) (bitLength >>> 32);
+ }
+
+ public int doFinal(byte[] out, int outOff) {
+ finish();
+
+ intToLittleEndian(H1, out, outOff);
+ intToLittleEndian(H2, out, outOff + 4);
+ intToLittleEndian(H3, out, outOff + 8);
+ intToLittleEndian(H4, out, outOff + 12);
+
+ reset();
+
+ return DIGEST_LENGTH;
+ }
+
+ private void intToLittleEndian(int n, byte[] bs, int off) {
+ bs[off] = (byte) (n);
+ bs[++off] = (byte) (n >>> 8);
+ bs[++off] = (byte) (n >>> 16);
+ bs[++off] = (byte) (n >>> 24);
+ }
+
+ /**
+ * reset the chaining variables to the IV values.
+ */
+ public void reset() {
+ super.reset();
+
+ H1 = 0x67452301;
+ H2 = 0xefcdab89;
+ H3 = 0x98badcfe;
+ H4 = 0x10325476;
+
+ xOff = 0;
+
+ for (int i = 0; i != X.length; i++) {
+ X[i] = 0;
+ }
+ }
+
+ //
+ // round 1 left rotates
+ //
+ private static final int S11 = 7;
+ private static final int S12 = 12;
+ private static final int S13 = 17;
+ private static final int S14 = 22;
+
+ //
+ // round 2 left rotates
+ //
+ private static final int S21 = 5;
+ private static final int S22 = 9;
+ private static final int S23 = 14;
+ private static final int S24 = 20;
+
+ //
+ // round 3 left rotates
+ //
+ private static final int S31 = 4;
+ private static final int S32 = 11;
+ private static final int S33 = 16;
+ private static final int S34 = 23;
+
+ //
+ // round 4 left rotates
+ //
+ private static final int S41 = 6;
+ private static final int S42 = 10;
+ private static final int S43 = 15;
+ private static final int S44 = 21;
+
+ /*
+ * rotate int x left n bits.
+ */
+ private int rotateLeft(int x, int n) {
+ return (x << n) | (x >>> (32 - n));
+ }
+
+ /*
+ * F, G, H and I are the basic MD5 functions.
+ */
+ private int F(int u, int v, int w) {
+ return (u & v) | (~u & w);
+ }
+
+ private int G(int u, int v, int w) {
+ return (u & w) | (v & ~w);
+ }
+
+ private int H(int u, int v, int w) {
+ return u ^ v ^ w;
+ }
+
+ private int K(int u, int v, int w) {
+ return v ^ (u | ~w);
+ }
+
+ protected void processBlock() {
+ int a = H1;
+ int b = H2;
+ int c = H3;
+ int d = H4;
+
+ //
+ // Round 1 - F cycle, 16 times.
+ //
+ a = rotateLeft(a + F(b, c, d) + X[0] + 0xd76aa478, S11) + b;
+ d = rotateLeft(d + F(a, b, c) + X[1] + 0xe8c7b756, S12) + a;
+ c = rotateLeft(c + F(d, a, b) + X[2] + 0x242070db, S13) + d;
+ b = rotateLeft(b + F(c, d, a) + X[3] + 0xc1bdceee, S14) + c;
+ a = rotateLeft(a + F(b, c, d) + X[4] + 0xf57c0faf, S11) + b;
+ d = rotateLeft(d + F(a, b, c) + X[5] + 0x4787c62a, S12) + a;
+ c = rotateLeft(c + F(d, a, b) + X[6] + 0xa8304613, S13) + d;
+ b = rotateLeft(b + F(c, d, a) + X[7] + 0xfd469501, S14) + c;
+ a = rotateLeft(a + F(b, c, d) + X[8] + 0x698098d8, S11) + b;
+ d = rotateLeft(d + F(a, b, c) + X[9] + 0x8b44f7af, S12) + a;
+ c = rotateLeft(c + F(d, a, b) + X[10] + 0xffff5bb1, S13) + d;
+ b = rotateLeft(b + F(c, d, a) + X[11] + 0x895cd7be, S14) + c;
+ a = rotateLeft(a + F(b, c, d) + X[12] + 0x6b901122, S11) + b;
+ d = rotateLeft(d + F(a, b, c) + X[13] + 0xfd987193, S12) + a;
+ c = rotateLeft(c + F(d, a, b) + X[14] + 0xa679438e, S13) + d;
+ b = rotateLeft(b + F(c, d, a) + X[15] + 0x49b40821, S14) + c;
+
+ //
+ // Round 2 - G cycle, 16 times.
+ //
+ a = rotateLeft(a + G(b, c, d) + X[1] + 0xf61e2562, S21) + b;
+ d = rotateLeft(d + G(a, b, c) + X[6] + 0xc040b340, S22) + a;
+ c = rotateLeft(c + G(d, a, b) + X[11] + 0x265e5a51, S23) + d;
+ b = rotateLeft(b + G(c, d, a) + X[0] + 0xe9b6c7aa, S24) + c;
+ a = rotateLeft(a + G(b, c, d) + X[5] + 0xd62f105d, S21) + b;
+ d = rotateLeft(d + G(a, b, c) + X[10] + 0x02441453, S22) + a;
+ c = rotateLeft(c + G(d, a, b) + X[15] + 0xd8a1e681, S23) + d;
+ b = rotateLeft(b + G(c, d, a) + X[4] + 0xe7d3fbc8, S24) + c;
+ a = rotateLeft(a + G(b, c, d) + X[9] + 0x21e1cde6, S21) + b;
+ d = rotateLeft(d + G(a, b, c) + X[14] + 0xc33707d6, S22) + a;
+ c = rotateLeft(c + G(d, a, b) + X[3] + 0xf4d50d87, S23) + d;
+ b = rotateLeft(b + G(c, d, a) + X[8] + 0x455a14ed, S24) + c;
+ a = rotateLeft(a + G(b, c, d) + X[13] + 0xa9e3e905, S21) + b;
+ d = rotateLeft(d + G(a, b, c) + X[2] + 0xfcefa3f8, S22) + a;
+ c = rotateLeft(c + G(d, a, b) + X[7] + 0x676f02d9, S23) + d;
+ b = rotateLeft(b + G(c, d, a) + X[12] + 0x8d2a4c8a, S24) + c;
+
+ //
+ // Round 3 - H cycle, 16 times.
+ //
+ a = rotateLeft(a + H(b, c, d) + X[5] + 0xfffa3942, S31) + b;
+ d = rotateLeft(d + H(a, b, c) + X[8] + 0x8771f681, S32) + a;
+ c = rotateLeft(c + H(d, a, b) + X[11] + 0x6d9d6122, S33) + d;
+ b = rotateLeft(b + H(c, d, a) + X[14] + 0xfde5380c, S34) + c;
+ a = rotateLeft(a + H(b, c, d) + X[1] + 0xa4beea44, S31) + b;
+ d = rotateLeft(d + H(a, b, c) + X[4] + 0x4bdecfa9, S32) + a;
+ c = rotateLeft(c + H(d, a, b) + X[7] + 0xf6bb4b60, S33) + d;
+ b = rotateLeft(b + H(c, d, a) + X[10] + 0xbebfbc70, S34) + c;
+ a = rotateLeft(a + H(b, c, d) + X[13] + 0x289b7ec6, S31) + b;
+ d = rotateLeft(d + H(a, b, c) + X[0] + 0xeaa127fa, S32) + a;
+ c = rotateLeft(c + H(d, a, b) + X[3] + 0xd4ef3085, S33) + d;
+ b = rotateLeft(b + H(c, d, a) + X[6] + 0x04881d05, S34) + c;
+ a = rotateLeft(a + H(b, c, d) + X[9] + 0xd9d4d039, S31) + b;
+ d = rotateLeft(d + H(a, b, c) + X[12] + 0xe6db99e5, S32) + a;
+ c = rotateLeft(c + H(d, a, b) + X[15] + 0x1fa27cf8, S33) + d;
+ b = rotateLeft(b + H(c, d, a) + X[2] + 0xc4ac5665, S34) + c;
+
+ //
+ // Round 4 - K cycle, 16 times.
+ //
+ a = rotateLeft(a + K(b, c, d) + X[0] + 0xf4292244, S41) + b;
+ d = rotateLeft(d + K(a, b, c) + X[7] + 0x432aff97, S42) + a;
+ c = rotateLeft(c + K(d, a, b) + X[14] + 0xab9423a7, S43) + d;
+ b = rotateLeft(b + K(c, d, a) + X[5] + 0xfc93a039, S44) + c;
+ a = rotateLeft(a + K(b, c, d) + X[12] + 0x655b59c3, S41) + b;
+ d = rotateLeft(d + K(a, b, c) + X[3] + 0x8f0ccc92, S42) + a;
+ c = rotateLeft(c + K(d, a, b) + X[10] + 0xffeff47d, S43) + d;
+ b = rotateLeft(b + K(c, d, a) + X[1] + 0x85845dd1, S44) + c;
+ a = rotateLeft(a + K(b, c, d) + X[8] + 0x6fa87e4f, S41) + b;
+ d = rotateLeft(d + K(a, b, c) + X[15] + 0xfe2ce6e0, S42) + a;
+ c = rotateLeft(c + K(d, a, b) + X[6] + 0xa3014314, S43) + d;
+ b = rotateLeft(b + K(c, d, a) + X[13] + 0x4e0811a1, S44) + c;
+ a = rotateLeft(a + K(b, c, d) + X[4] + 0xf7537e82, S41) + b;
+ d = rotateLeft(d + K(a, b, c) + X[11] + 0xbd3af235, S42) + a;
+ c = rotateLeft(c + K(d, a, b) + X[2] + 0x2ad7d2bb, S43) + d;
+ b = rotateLeft(b + K(c, d, a) + X[9] + 0xeb86d391, S44) + c;
+
+ H1 += a;
+ H2 += b;
+ H3 += c;
+ H4 += d;
+
+ //
+ // reset the offset and clean out the word buffer.
+ //
+ xOff = 0;
+ for (int i = 0; i != X.length; i++) {
+ X[i] = 0;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/MessageChannel.java b/src/main/java/net/lax1dude/eaglercraft/sp/MessageChannel.java
new file mode 100644
index 0000000..2f024cc
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/MessageChannel.java
@@ -0,0 +1,36 @@
+package net.lax1dude.eaglercraft.sp;
+
+import org.teavm.jso.JSBody;
+import org.teavm.jso.JSClass;
+import org.teavm.jso.JSObject;
+import org.teavm.jso.JSProperty;
+import org.teavm.jso.workers.MessagePort;
+
+/**
+ * Copyright (c) 2024 lax1dude. All Rights Reserved.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+@JSClass
+public class MessageChannel implements JSObject {
+
+ @JSBody(params = { }, script = "return (typeof MessageChannel !== \"undefined\");")
+ public static native boolean supported();
+
+ @JSProperty
+ public native MessagePort getPort1();
+
+ @JSProperty
+ public native MessagePort getPort2();
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/NoCatchParse.java b/src/main/java/net/lax1dude/eaglercraft/sp/NoCatchParse.java
new file mode 100644
index 0000000..70bcdeb
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/NoCatchParse.java
@@ -0,0 +1,412 @@
+package net.lax1dude.eaglercraft.sp;
+
+public class NoCatchParse {
+
+ public static final int INT_EXCEPTION = Integer.MIN_VALUE;
+ public static final float FLOAT_EXCEPTION = Float.NaN;
+ public static final double DOUBLE_EXCEPTION = Double.NaN;
+
+ public static int parseInt(String s) {
+ return parseInt(s, 10, false, INT_EXCEPTION);
+ }
+
+ public static int parseInt(String s, int radix) {
+ return parseInt(s, radix, false, INT_EXCEPTION);
+ }
+
+ public static int parseInt(String s, int radix, boolean log) {
+ return parseInt(s, radix, log, INT_EXCEPTION);
+ }
+
+ public static int parseInt(String s, int radix, boolean log, int exceptionResult) {
+ if (s == null) {
+ if (log) {
+ System.err.println("parseInt: string was null");
+ }
+ return exceptionResult;
+ }
+
+ if (s.isEmpty()) {
+ if (log) {
+ System.err.println("parseInt: string was empty");
+ }
+ return exceptionResult;
+ }
+
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ if (log) {
+ System.err.println("parseInt: invalid radix '" + radix + "'");
+ }
+ return exceptionResult;
+ }
+
+ tryFail: {
+ int result = 0;
+ boolean negative = false;
+ int i = 0, len = s.length();
+ int limit = -Integer.MAX_VALUE;
+ int multmin;
+ int digit;
+
+ if (len > 0) {
+ char firstChar = s.charAt(0);
+ if (firstChar < '0') { // Possible leading "+" or "-"
+ if (firstChar == '-') {
+ negative = true;
+ limit = Integer.MIN_VALUE;
+ } else if (firstChar != '+')
+ break tryFail;
+
+ if (len == 1)
+ break tryFail;
+ i++;
+ }
+ multmin = limit / radix;
+ while (i < len) {
+ // Accumulating negatively avoids surprises near MAX_VALUE
+ digit = Character.digit(s.charAt(i++), radix);
+ if (digit < 0 || result < multmin) {
+
+ break tryFail;
+ }
+ result *= radix;
+ if (result < limit + digit) {
+ break tryFail;
+ }
+ result -= digit;
+ }
+ } else {
+ break tryFail;
+ }
+ int ret = negative ? result : -result;
+ if (ret == exceptionResult) {
+ System.err.println(
+ "parseInt: number '" + s + "' was parsed successfully but it is equal to exceptionResult");
+ }
+ return ret;
+ }
+ if (log) {
+ System.err.println("parseInt: cannot parse '" + s + "'");
+ }
+ return exceptionResult;
+ }
+
+ public static double parseDouble(String s) {
+ return parseDouble(s, false, DOUBLE_EXCEPTION);
+ }
+
+ public static double parseDouble(String s, boolean log) {
+ return parseDouble(s, log, DOUBLE_EXCEPTION);
+ }
+
+ public static double parseDouble(String s, boolean log, double exceptionResult) {
+ if (s == null) {
+ if (log) {
+ System.err.println("parseDouble: string was null");
+ }
+ return exceptionResult;
+ }
+
+ if (s.isEmpty()) {
+ if (log) {
+ System.err.println("parseDouble: string was empty");
+ }
+ return exceptionResult;
+ }
+
+ tryFail: {
+ int start = 0;
+ int end = s.length();
+ while (s.charAt(start) <= ' ') {
+ if (++start == end) {
+ break tryFail;
+ }
+ }
+ while (s.charAt(end - 1) <= ' ') {
+ --end;
+ }
+
+ boolean negative = false;
+ int index = start;
+ if (s.charAt(index) == '-') {
+ ++index;
+ negative = true;
+ } else if (s.charAt(index) == '+') {
+ ++index;
+ }
+ if (index == end) {
+ break tryFail;
+ }
+ char c = s.charAt(index);
+
+ long mantissa = 0;
+ int exp = 0;
+ boolean hasOneDigit = false;
+ if (c != '.') {
+ hasOneDigit = true;
+ if (c < '0' || c > '9') {
+ break tryFail;
+ }
+ while (index < end && s.charAt(index) == '0') {
+ ++index;
+ }
+ while (index < end) {
+ c = s.charAt(index);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ if (mantissa < Long.MAX_VALUE / 10 - 9) {
+ mantissa = mantissa * 10 + (c - '0');
+ } else {
+ ++exp;
+ }
+ ++index;
+ }
+ }
+ if (index < end && s.charAt(index) == '.') {
+ ++index;
+ while (index < end) {
+ c = s.charAt(index);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ if (mantissa < Long.MAX_VALUE / 10 - 9) {
+ mantissa = mantissa * 10 + (c - '0');
+ --exp;
+ }
+ ++index;
+ hasOneDigit = true;
+ }
+ if (!hasOneDigit) {
+ break tryFail;
+ }
+ }
+ if (index < end) {
+ c = s.charAt(index);
+ if (c != 'e' && c != 'E') {
+ break tryFail;
+ }
+ ++index;
+ boolean negativeExp = false;
+ if (index == end) {
+ break tryFail;
+ }
+ if (s.charAt(index) == '-') {
+ ++index;
+ negativeExp = true;
+ } else if (s.charAt(index) == '+') {
+ ++index;
+ }
+ int numExp = 0;
+ hasOneDigit = false;
+ while (index < end) {
+ c = s.charAt(index);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ numExp = 10 * numExp + (c - '0');
+ hasOneDigit = true;
+ ++index;
+ }
+ if (!hasOneDigit) {
+ break tryFail;
+ }
+ if (negativeExp) {
+ numExp = -numExp;
+ }
+ exp += numExp;
+ }
+ if (exp > 308 || exp == 308 && mantissa > 17976931348623157L) {
+ return !negative ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
+ }
+ if (negative) {
+ mantissa = -mantissa;
+ }
+ return mantissa * doubleDecimalExponent(exp);
+ }
+ if (log) {
+ System.err.println("parseDouble: cannot parse '" + s + "'");
+ }
+ return exceptionResult;
+ }
+
+ public static double doubleDecimalExponent(int n) {
+ double d;
+ if (n < 0) {
+ d = 0.1;
+ n = -n;
+ } else {
+ d = 10;
+ }
+ double result = 1;
+ while (n != 0) {
+ if (n % 2 != 0) {
+ result *= d;
+ }
+ d *= d;
+ n /= 2;
+ }
+ return result;
+ }
+
+ public static float parseFloat(String s) {
+ return parseFloat(s, false, FLOAT_EXCEPTION);
+ }
+
+ public static float parseFloat(String s, boolean log) {
+ return parseFloat(s, log, FLOAT_EXCEPTION);
+ }
+
+ public static float parseFloat(String s, boolean log, float exceptionResult) {
+ if (s == null) {
+ if (log) {
+ System.err.println("parseFloat: string was null");
+ }
+ return exceptionResult;
+ }
+
+ if (s.isEmpty()) {
+ if (log) {
+ System.err.println("parseFloat: string was empty");
+ }
+ return exceptionResult;
+ }
+
+ tryFail: {
+ int start = 0;
+ int end = s.length();
+ while (s.charAt(start) <= ' ') {
+ if (++start == end) {
+ break tryFail;
+ }
+ }
+ while (s.charAt(end - 1) <= ' ') {
+ --end;
+ }
+
+ boolean negative = false;
+ int index = start;
+ if (s.charAt(index) == '-') {
+ ++index;
+ negative = true;
+ } else if (s.charAt(index) == '+') {
+ ++index;
+ }
+ if (index == end) {
+ break tryFail;
+ }
+ char c = s.charAt(index);
+
+ int mantissa = 0;
+ int exp = 0;
+
+ boolean hasOneDigit = false;
+ if (c != '.') {
+ hasOneDigit = true;
+ if (c < '0' || c > '9') {
+ break tryFail;
+ }
+
+ while (index < end && s.charAt(index) == '0') {
+ ++index;
+ }
+ while (index < end) {
+ c = s.charAt(index);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ if (mantissa < (Integer.MAX_VALUE / 10) - 9) {
+ mantissa = mantissa * 10 + (c - '0');
+ } else {
+ ++exp;
+ }
+ ++index;
+ }
+ }
+
+ if (index < end && s.charAt(index) == '.') {
+ ++index;
+ while (index < end) {
+ c = s.charAt(index);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ if (mantissa < (Integer.MAX_VALUE / 10) - 9) {
+ mantissa = mantissa * 10 + (c - '0');
+ --exp;
+ }
+ ++index;
+ hasOneDigit = true;
+ }
+ if (!hasOneDigit) {
+ break tryFail;
+ }
+ }
+ if (index < end) {
+ c = s.charAt(index);
+ if (c != 'e' && c != 'E') {
+ break tryFail;
+ }
+ ++index;
+ boolean negativeExp = false;
+ if (index == end) {
+ break tryFail;
+ }
+ if (s.charAt(index) == '-') {
+ ++index;
+ negativeExp = true;
+ } else if (s.charAt(index) == '+') {
+ ++index;
+ }
+ int numExp = 0;
+ hasOneDigit = false;
+ while (index < end) {
+ c = s.charAt(index);
+ if (c < '0' || c > '9') {
+ break;
+ }
+ numExp = 10 * numExp + (c - '0');
+ hasOneDigit = true;
+ ++index;
+ }
+ if (!hasOneDigit) {
+ break tryFail;
+ }
+ if (negativeExp) {
+ numExp = -numExp;
+ }
+ exp += numExp;
+ }
+ if (exp > 38 || exp == 38 && mantissa > 34028234) {
+ return !negative ? Float.POSITIVE_INFINITY : Float.NEGATIVE_INFINITY;
+ }
+ if (negative) {
+ mantissa = -mantissa;
+ }
+ return mantissa * floatDecimalExponent(exp);
+ }
+ if (log) {
+ System.err.println("parseFloat: cannot parse '" + s + "'");
+ }
+ return exceptionResult;
+ }
+
+ private static float floatDecimalExponent(int n) {
+ double d;
+ if (n < 0) {
+ d = 0.1;
+ n = -n;
+ } else {
+ d = 10;
+ }
+ double result = 1;
+ while (n != 0) {
+ if (n % 2 != 0) {
+ result *= d;
+ }
+ d *= d;
+ n /= 2;
+ }
+ return (float) result;
+ }
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/RandomAccessMemoryFile.java b/src/main/java/net/lax1dude/eaglercraft/sp/RandomAccessMemoryFile.java
new file mode 100644
index 0000000..17f38f3
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/RandomAccessMemoryFile.java
@@ -0,0 +1,310 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.EOFException;
+import java.io.IOException;
+
+/**
+ * Copyright (c) 2023-2024 lax1dude. All Rights Reserved.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+public class RandomAccessMemoryFile implements DataInput, DataOutput {
+
+ private byte[] buffer;
+ private int length;
+ private int pos;
+
+ public RandomAccessMemoryFile(byte[] initialBuffer, int initialLength) {
+ this.buffer = initialBuffer;
+ this.length = initialLength;
+ this.pos = 0;
+ }
+
+ private void grow(int newMaxSize) {
+ if (length < newMaxSize) {
+ if (buffer.length < newMaxSize) {
+ byte[] newBuffer = new byte[newMaxSize | 0x7FFFF];
+ System.arraycopy(buffer, 0, newBuffer, 0, length);
+ buffer = newBuffer;
+ }
+ length = newMaxSize;
+ }
+ }
+
+ public byte[] getByteArray() {
+ byte[] b = new byte[length];
+ System.arraycopy(buffer, 0, b, 0, length);
+ return b;
+ }
+
+ public int read() throws IOException {
+ return (pos < length) ? (buffer[pos++] & 0xff) : -1;
+ }
+
+ private int readBytes(byte b[], int off, int len) throws IOException {
+ if (pos >= length) {
+ return -1;
+ }
+
+ int avail = length - pos;
+ if (len > avail) {
+ len = avail;
+ }
+ if (len <= 0) {
+ return 0;
+ }
+ System.arraycopy(buffer, pos, b, off, len);
+ pos += len;
+ return len;
+ }
+
+ public int read(byte b[], int off, int len) throws IOException {
+ return readBytes(b, off, len);
+ }
+
+ public int read(byte b[]) throws IOException {
+ return readBytes(b, 0, b.length);
+ }
+
+ public final void readFully(byte b[]) throws IOException {
+ readFully(b, 0, b.length);
+ }
+
+ public final void readFully(byte b[], int off, int len) throws IOException {
+ int n = 0;
+ do {
+ int count = this.read(b, off + n, len - n);
+ if (count < 0)
+ throw new EOFException();
+ n += count;
+ } while (n < len);
+ }
+
+ public int skipBytes(int n) throws IOException {
+ int newpos;
+
+ if (n <= 0) {
+ return 0;
+ }
+ newpos = pos + n;
+ if (newpos > length) {
+ newpos = length;
+ }
+ seek(newpos);
+
+ return (int) (newpos - pos);
+ }
+
+ public void write(int b) throws IOException {
+ grow(pos + 1);
+ buffer[pos] = (byte) b;
+ pos += 1;
+ }
+
+ private void writeBytes(byte b[], int off, int len) throws IOException {
+ grow(pos + len);
+ System.arraycopy(b, off, buffer, pos, len);
+ pos += len;
+ }
+
+ public void write(byte b[]) throws IOException {
+ writeBytes(b, 0, b.length);
+ }
+
+ public void write(byte b[], int off, int len) throws IOException {
+ writeBytes(b, off, len);
+ }
+
+ public void seek(int pos) {
+ this.pos = pos;
+ }
+
+ public int getLength() {
+ return length;
+ }
+
+ public void setLength(int newLength) {
+ grow(newLength);
+ }
+
+ public final boolean readBoolean() throws IOException {
+ int ch = this.read();
+ if (ch < 0)
+ throw new EOFException();
+ return (ch != 0);
+ }
+
+ public final byte readByte() throws IOException {
+ int ch = this.read();
+ if (ch < 0)
+ throw new EOFException();
+ return (byte) (ch);
+ }
+
+ public final int readUnsignedByte() throws IOException {
+ int ch = this.read();
+ if (ch < 0)
+ throw new EOFException();
+ return ch;
+ }
+
+ public final short readShort() throws IOException {
+ int ch1 = this.read();
+ int ch2 = this.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (short) ((ch1 << 8) + (ch2 << 0));
+ }
+
+ public final int readUnsignedShort() throws IOException {
+ int ch1 = this.read();
+ int ch2 = this.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (ch1 << 8) + (ch2 << 0);
+ }
+
+ public final char readChar() throws IOException {
+ int ch1 = this.read();
+ int ch2 = this.read();
+ if ((ch1 | ch2) < 0)
+ throw new EOFException();
+ return (char) ((ch1 << 8) + (ch2 << 0));
+ }
+
+ public final int readInt() throws IOException {
+ int ch1 = this.read();
+ int ch2 = this.read();
+ int ch3 = this.read();
+ int ch4 = this.read();
+ if ((ch1 | ch2 | ch3 | ch4) < 0)
+ throw new EOFException();
+ return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
+ }
+
+ public final long readLong() throws IOException {
+ return ((long) (readInt()) << 32) + (readInt() & 0xFFFFFFFFL);
+ }
+
+ public final float readFloat() throws IOException {
+ return Float.intBitsToFloat(readInt());
+ }
+
+ public final double readDouble() throws IOException {
+ return Double.longBitsToDouble(readLong());
+ }
+
+ public final String readLine() throws IOException {
+ StringBuilder input = new StringBuilder();
+ int c = -1;
+ boolean eol = false;
+
+ while (!eol) {
+ switch (c = read()) {
+ case -1:
+ case '\n':
+ eol = true;
+ break;
+ case '\r':
+ eol = true;
+ int cur = pos;
+ if ((read()) != '\n') {
+ seek(cur);
+ }
+ break;
+ default:
+ input.append((char) c);
+ break;
+ }
+ }
+
+ if ((c == -1) && (input.length() == 0)) {
+ return null;
+ }
+ return input.toString();
+ }
+
+ public final String readUTF() throws IOException {
+ throw new IOException("TODO");
+ }
+
+ public final void writeBoolean(boolean v) throws IOException {
+ write(v ? 1 : 0);
+ }
+
+ public final void writeByte(int v) throws IOException {
+ write(v);
+ }
+
+ public final void writeShort(int v) throws IOException {
+ write((v >>> 8) & 0xFF);
+ write((v >>> 0) & 0xFF);
+ }
+
+ public final void writeChar(int v) throws IOException {
+ write((v >>> 8) & 0xFF);
+ write((v >>> 0) & 0xFF);
+ }
+
+ public final void writeInt(int v) throws IOException {
+ write((v >>> 24) & 0xFF);
+ write((v >>> 16) & 0xFF);
+ write((v >>> 8) & 0xFF);
+ write((v >>> 0) & 0xFF);
+ }
+
+ public final void writeLong(long v) throws IOException {
+ write((int) (v >>> 56) & 0xFF);
+ write((int) (v >>> 48) & 0xFF);
+ write((int) (v >>> 40) & 0xFF);
+ write((int) (v >>> 32) & 0xFF);
+ write((int) (v >>> 24) & 0xFF);
+ write((int) (v >>> 16) & 0xFF);
+ write((int) (v >>> 8) & 0xFF);
+ write((int) (v >>> 0) & 0xFF);
+ }
+
+ public final void writeFloat(float v) throws IOException {
+ writeInt(Float.floatToIntBits(v));
+ }
+
+ public final void writeDouble(double v) throws IOException {
+ writeLong(Double.doubleToLongBits(v));
+ }
+
+ public final void writeBytes(String s) throws IOException {
+ int len = s.length();
+ byte[] b = new byte[len];
+ s.getBytes(0, len, b, 0);
+ writeBytes(b, 0, len);
+ }
+
+ public final void writeChars(String s) throws IOException {
+ int clen = s.length();
+ int blen = 2 * clen;
+ byte[] b = new byte[blen];
+ char[] c = new char[clen];
+ s.getChars(0, clen, c, 0);
+ for (int i = 0, j = 0; i < clen; i++) {
+ b[j++] = (byte) (c[i] >>> 8);
+ b[j++] = (byte) (c[i] >>> 0);
+ }
+ writeBytes(b, 0, blen);
+ }
+
+ public final void writeUTF(String str) throws IOException {
+ throw new IOException("TODO");
+ }
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/SHA1Digest.java b/src/main/java/net/lax1dude/eaglercraft/sp/SHA1Digest.java
new file mode 100644
index 0000000..bffcbc0
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/SHA1Digest.java
@@ -0,0 +1,213 @@
+package net.lax1dude.eaglercraft.sp;
+
+/**
+ * implementation of SHA-1 as outlined in "Handbook of Applied Cryptography",
+ * pages 346 - 349.
+ *
+ * It is interesting to ponder why the, apart from the extra IV, the other
+ * difference here from MD5 is the "endienness" of the word processing!
+ */
+public class SHA1Digest extends GeneralDigest {
+ private static final int DIGEST_LENGTH = 20;
+
+ private int H1, H2, H3, H4, H5;
+
+ private int[] X = new int[80];
+ private int xOff;
+
+ /**
+ * Standard constructor
+ */
+ public SHA1Digest() {
+ reset();
+ }
+
+ /**
+ * Copy constructor. This will copy the state of the provided message digest.
+ */
+ public SHA1Digest(SHA1Digest t) {
+ super(t);
+
+ H1 = t.H1;
+ H2 = t.H2;
+ H3 = t.H3;
+ H4 = t.H4;
+ H5 = t.H5;
+
+ System.arraycopy(t.X, 0, X, 0, t.X.length);
+ xOff = t.xOff;
+ }
+
+ public String getAlgorithmName() {
+ return "SHA-1";
+ }
+
+ public int getDigestSize() {
+ return DIGEST_LENGTH;
+ }
+
+ protected void processWord(byte[] in, int inOff) {
+ X[xOff++] = ((in[inOff] & 0xff) << 24) | ((in[inOff + 1] & 0xff) << 16) | ((in[inOff + 2] & 0xff) << 8)
+ | ((in[inOff + 3] & 0xff));
+
+ if (xOff == 16) {
+ processBlock();
+ }
+ }
+
+ private void unpackWord(int word, byte[] out, int outOff) {
+ out[outOff] = (byte) (word >>> 24);
+ out[outOff + 1] = (byte) (word >>> 16);
+ out[outOff + 2] = (byte) (word >>> 8);
+ out[outOff + 3] = (byte) word;
+ }
+
+ protected void processLength(long bitLength) {
+ if (xOff > 14) {
+ processBlock();
+ }
+
+ X[14] = (int) (bitLength >>> 32);
+ X[15] = (int) (bitLength & 0xffffffff);
+ }
+
+ public int doFinal(byte[] out, int outOff) {
+ finish();
+
+ unpackWord(H1, out, outOff);
+ unpackWord(H2, out, outOff + 4);
+ unpackWord(H3, out, outOff + 8);
+ unpackWord(H4, out, outOff + 12);
+ unpackWord(H5, out, outOff + 16);
+
+ reset();
+
+ return DIGEST_LENGTH;
+ }
+
+ /**
+ * reset the chaining variables
+ */
+ public void reset() {
+ super.reset();
+
+ H1 = 0x67452301;
+ H2 = 0xefcdab89;
+ H3 = 0x98badcfe;
+ H4 = 0x10325476;
+ H5 = 0xc3d2e1f0;
+
+ xOff = 0;
+ for (int i = 0; i != X.length; i++) {
+ X[i] = 0;
+ }
+ }
+
+ //
+ // Additive constants
+ //
+ private static final int Y1 = 0x5a827999;
+ private static final int Y2 = 0x6ed9eba1;
+ private static final int Y3 = 0x8f1bbcdc;
+ private static final int Y4 = 0xca62c1d6;
+
+ private int f(int u, int v, int w) {
+ return ((u & v) | ((~u) & w));
+ }
+
+ private int h(int u, int v, int w) {
+ return (u ^ v ^ w);
+ }
+
+ private int g(int u, int v, int w) {
+ return ((u & v) | (u & w) | (v & w));
+ }
+
+ private int rotateLeft(int x, int n) {
+ return (x << n) | (x >>> (32 - n));
+ }
+
+ protected void processBlock() {
+ //
+ // expand 16 word block into 80 word block.
+ //
+ for (int i = 16; i <= 79; i++) {
+ X[i] = rotateLeft((X[i - 3] ^ X[i - 8] ^ X[i - 14] ^ X[i - 16]), 1);
+ }
+
+ //
+ // set up working variables.
+ //
+ int A = H1;
+ int B = H2;
+ int C = H3;
+ int D = H4;
+ int E = H5;
+
+ //
+ // round 1
+ //
+ for (int j = 0; j <= 19; j++) {
+ int t = rotateLeft(A, 5) + f(B, C, D) + E + X[j] + Y1;
+
+ E = D;
+ D = C;
+ C = rotateLeft(B, 30);
+ B = A;
+ A = t;
+ }
+
+ //
+ // round 2
+ //
+ for (int j = 20; j <= 39; j++) {
+ int t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + Y2;
+
+ E = D;
+ D = C;
+ C = rotateLeft(B, 30);
+ B = A;
+ A = t;
+ }
+
+ //
+ // round 3
+ //
+ for (int j = 40; j <= 59; j++) {
+ int t = rotateLeft(A, 5) + g(B, C, D) + E + X[j] + Y3;
+
+ E = D;
+ D = C;
+ C = rotateLeft(B, 30);
+ B = A;
+ A = t;
+ }
+
+ //
+ // round 4
+ //
+ for (int j = 60; j <= 79; j++) {
+ int t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + Y4;
+
+ E = D;
+ D = C;
+ C = rotateLeft(B, 30);
+ B = A;
+ A = t;
+ }
+
+ H1 += A;
+ H2 += B;
+ H3 += C;
+ H4 += D;
+ H5 += E;
+
+ //
+ // reset the offset and clean out the word buffer.
+ //
+ xOff = 0;
+ for (int i = 0; i != X.length; i++) {
+ X[i] = 0;
+ }
+ }
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/SYS.java b/src/main/java/net/lax1dude/eaglercraft/sp/SYS.java
new file mode 100644
index 0000000..5cded6b
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/SYS.java
@@ -0,0 +1,28 @@
+package net.lax1dude.eaglercraft.sp;
+
+import org.teavm.jso.JSBody;
+
+import net.lax1dude.eaglercraft.sp.VirtualFilesystem.VFSHandle;
+
+public class SYS {
+
+ public static final VirtualFilesystem VFS;
+
+ @JSBody(params = { }, script = "return eaglercraftServerOpts.worldDatabaseName;")
+ private static native String getWorldDatabaseName();
+
+ static {
+
+ VFSHandle vh = VirtualFilesystem.openVFS("_net_lax1dude_eaglercraft_sp_VirtualFilesystem_1_5_2_" + getWorldDatabaseName());
+
+ if(vh.vfs == null) {
+ System.err.println("Could not init filesystem!");
+ IntegratedServer.throwExceptionToClient("Could not init filesystem!", new RuntimeException("VFSHandle.vfs was null"));
+ }
+
+ VFS = vh.vfs;
+
+ }
+
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/SkinsPlugin.java b/src/main/java/net/lax1dude/eaglercraft/sp/SkinsPlugin.java
new file mode 100644
index 0000000..b211308
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/SkinsPlugin.java
@@ -0,0 +1,108 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+
+import net.minecraft.src.EntityPlayerMP;
+import net.minecraft.src.Packet250CustomPayload;
+
+public class SkinsPlugin {
+
+ private static final HashMap skinCollection = new HashMap<>();
+ private static final HashMap capeCollection = new HashMap<>();
+ private static final HashMap lastSkinLayerUpdate = new HashMap<>();
+
+ private static final int[] SKIN_DATA_SIZE = new int[] { 64*32*4, 64*64*4, -9, -9, 1, 64*64*4, -9 }; // 128 pixel skins crash clients
+ private static final int[] CAPE_DATA_SIZE = new int[] { 32*32*4, -9, 1 };
+
+ public static boolean handleMessage(EntityPlayerMP player, Packet250CustomPayload payload) {
+ if(payload.data.length > 0) {
+ String user = player.username;
+ byte[] msg = payload.data;
+ try {
+ if("EAG|MySkin".equals(payload.channel)) {
+ if(!skinCollection.containsKey(user)) {
+ int t = (int)msg[0] & 0xFF;
+ if(t < SKIN_DATA_SIZE.length && msg.length == (SKIN_DATA_SIZE[t] + 1)) {
+ skinCollection.put(user, msg);
+ }
+ }
+ return true;
+ }
+ if("EAG|MyCape".equals(payload.channel)) {
+ if(!capeCollection.containsKey(user)) {
+ int t = (int)msg[0] & 0xFF;
+ if(t < CAPE_DATA_SIZE.length && msg.length == (CAPE_DATA_SIZE[t] + 2)) {
+ capeCollection.put(user, msg);
+ }
+ }
+ return true;
+ }
+ if("EAG|FetchSkin".equals(payload.channel)) {
+ if(msg.length > 2) {
+ String fetch = new String(msg, 2, msg.length - 2, StandardCharsets.UTF_8);
+ byte[] data;
+ if((data = skinCollection.get(fetch)) != null) {
+ byte[] conc = new byte[data.length + 2];
+ conc[0] = msg[0]; conc[1] = msg[1]; //synchronization cookie
+ System.arraycopy(data, 0, conc, 2, data.length);
+ if((data = capeCollection.get(fetch)) != null) {
+ byte[] conc2 = new byte[conc.length + data.length];
+ System.arraycopy(conc, 0, conc2, 0, conc.length);
+ System.arraycopy(data, 0, conc2, conc.length, data.length);
+ conc = conc2;
+ }
+ player.playerNetServerHandler.sendPacket(new Packet250CustomPayload("EAG|UserSkin", conc));
+ }
+ }
+ return true;
+ }
+ if("EAG|SkinLayers".equals(payload.channel)) {
+ long millis = SysUtil.steadyTimeMillis();
+ Long lsu = lastSkinLayerUpdate.get(user);
+ if(lsu != null && millis - lsu < 700L) { // DoS protection
+ return true;
+ }
+ lastSkinLayerUpdate.put(user, millis);
+ byte[] data;
+ if((data = capeCollection.get(user)) != null) {
+ data[1] = msg[0];
+ }else {
+ data = new byte[] { (byte)2, msg[0], (byte)0 };
+ capeCollection.put(user, data);
+ }
+ ByteArrayOutputStream bao = new ByteArrayOutputStream();
+ DataOutputStream dd = new DataOutputStream(bao);
+ dd.write(msg[0]);
+ dd.writeUTF(user);
+ byte[] bpacket = bao.toByteArray();
+ for(Object o : player.mcServer.getConfigurationManager().playerEntityList) {
+ EntityPlayerMP pl = (EntityPlayerMP) o;
+ if(!pl.username.equals(user)) {
+ pl.playerNetServerHandler.sendPacket(new Packet250CustomPayload("EAG|SkinLayers", bpacket));
+ }
+ }
+ return true;
+ }
+ }catch(Throwable t) {
+ // hacker
+ }
+ }
+ return false;
+ }
+
+ public static void handleDisconnect(EntityPlayerMP player) {
+ skinCollection.remove(player.username);
+ capeCollection.remove(player.username);
+ lastSkinLayerUpdate.remove(player.username);
+ }
+
+ public static void reset() {
+ skinCollection.clear();
+ capeCollection.clear();
+ lastSkinLayerUpdate.clear();
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/SysUtil.java b/src/main/java/net/lax1dude/eaglercraft/sp/SysUtil.java
new file mode 100644
index 0000000..09c55bb
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/SysUtil.java
@@ -0,0 +1,152 @@
+package net.lax1dude.eaglercraft.sp;
+
+import org.teavm.interop.Async;
+import org.teavm.interop.AsyncCallback;
+import org.teavm.jso.JSBody;
+import org.teavm.jso.JSObject;
+import org.teavm.jso.browser.Window;
+import org.teavm.jso.core.JSString;
+import org.teavm.jso.dom.events.EventListener;
+import org.teavm.jso.dom.events.MessageEvent;
+import org.teavm.platform.Platform;
+import org.teavm.platform.PlatformRunnable;
+
+public class SysUtil {
+
+ private static final JSObject steadyTimeFunc = getSteadyTimeFunc();
+
+ @JSBody(params = { }, script = "return ((typeof performance !== \"undefined\") && (typeof performance.now === \"function\"))"
+ + "? performance.now.bind(performance)"
+ + ": (function(epochStart){ return function() { return Date.now() - epochStart; }; })(Date.now());")
+ private static native JSObject getSteadyTimeFunc();
+
+ @JSBody(params = { "steadyTimeFunc" }, script = "return steadyTimeFunc();")
+ private static native double steadyTimeMillis0(JSObject steadyTimeFunc);
+
+ public static long steadyTimeMillis() {
+ return (long)steadyTimeMillis0(steadyTimeFunc);
+ }
+
+ public static long nanoTime() {
+ return (long)(steadyTimeMillis0(steadyTimeFunc) * 1000000.0);
+ }
+
+ @Async
+ public static native void sleep(int millis);
+
+ private static void sleep(int millis, final AsyncCallback callback) {
+ Platform.schedule(new DumbSleepHandler(callback), millis);
+ }
+
+ private static class DumbSleepHandler implements PlatformRunnable {
+ private final AsyncCallback callback;
+ private DumbSleepHandler(AsyncCallback callback) {
+ this.callback = callback;
+ }
+ @Override
+ public void run() {
+ callback.complete(null);
+ }
+ }
+
+ private static boolean hasCheckedImmediateContinue = false;
+ private static MessageChannel immediateContinueChannel = null;
+ private static Runnable currentContinueHack = null;
+ private static final JSString emptyJSString = JSString.valueOf("");
+
+ public static void immediateContinue() {
+ if(!hasCheckedImmediateContinue) {
+ hasCheckedImmediateContinue = true;
+ checkImmediateContinueSupport();
+ }
+ if(immediateContinueChannel != null) {
+ immediateContinueTeaVM();
+ }else {
+ sleep(0);
+ }
+ }
+
+ @Async
+ private static native void immediateContinueTeaVM();
+
+ private static void immediateContinueTeaVM(final AsyncCallback cb) {
+ if(currentContinueHack != null) {
+ cb.error(new IllegalStateException("Worker thread is already waiting for an immediate continue callback!"));
+ return;
+ }
+ currentContinueHack = () -> {
+ cb.complete(null);
+ };
+ try {
+ immediateContinueChannel.getPort2().postMessage(emptyJSString);
+ }catch(Throwable t) {
+ System.err.println("Caught error posting immediate continue, using setTimeout instead");
+ Window.setTimeout(() -> cb.complete(null), 0);
+ }
+ }
+
+ private static void checkImmediateContinueSupport() {
+ try {
+ immediateContinueChannel = null;
+ if(!MessageChannel.supported()) {
+ System.err.println("Fast immediate continue will be disabled for server context due to MessageChannel being unsupported");
+ return;
+ }
+ immediateContinueChannel = new MessageChannel();
+ immediateContinueChannel.getPort1().addEventListener("message", new EventListener() {
+ @Override
+ public void handleEvent(MessageEvent evt) {
+ Runnable toRun = currentContinueHack;
+ currentContinueHack = null;
+ if(toRun != null) {
+ toRun.run();
+ }
+ }
+ });
+ immediateContinueChannel.getPort1().start();
+ immediateContinueChannel.getPort2().start();
+ final boolean[] checkMe = new boolean[1];
+ checkMe[0] = false;
+ currentContinueHack = () -> {
+ checkMe[0] = true;
+ };
+ immediateContinueChannel.getPort2().postMessage(emptyJSString);
+ if(checkMe[0]) {
+ currentContinueHack = null;
+ if(immediateContinueChannel != null) {
+ safeShutdownChannel(immediateContinueChannel);
+ }
+ immediateContinueChannel = null;
+ System.err.println("Fast immediate continue will be disabled for server context due to actually continuing immediately");
+ return;
+ }
+ sleep(10);
+ currentContinueHack = null;
+ if(!checkMe[0]) {
+ if(immediateContinueChannel != null) {
+ safeShutdownChannel(immediateContinueChannel);
+ }
+ immediateContinueChannel = null;
+ System.err.println("Fast immediate continue will be disabled for server context due to startup check failing");
+ }
+ }catch(Throwable t) {
+ System.err.println("Fast immediate continue will be disabled for server context due to exceptions");
+ if(immediateContinueChannel != null) {
+ safeShutdownChannel(immediateContinueChannel);
+ }
+ immediateContinueChannel = null;
+ }
+ }
+
+ private static void safeShutdownChannel(MessageChannel chan) {
+ try {
+ chan.getPort1().close();
+ }catch(Throwable tt) {
+ }
+ try {
+ chan.getPort2().close();
+ }catch(Throwable tt) {
+ }
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/TeaVMUtils.java b/src/main/java/net/lax1dude/eaglercraft/sp/TeaVMUtils.java
new file mode 100644
index 0000000..9f1a3d1
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/TeaVMUtils.java
@@ -0,0 +1,93 @@
+package net.lax1dude.eaglercraft.sp;
+
+import org.teavm.backend.javascript.spi.GeneratedBy;
+import org.teavm.backend.javascript.spi.InjectedBy;
+import org.teavm.jso.typedarrays.ArrayBuffer;
+import org.teavm.jso.typedarrays.ArrayBufferView;
+import org.teavm.jso.typedarrays.Float32Array;
+import org.teavm.jso.typedarrays.Int16Array;
+import org.teavm.jso.typedarrays.Int32Array;
+import org.teavm.jso.typedarrays.Int8Array;
+import org.teavm.jso.typedarrays.Uint8Array;
+
+public class TeaVMUtils {
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
+ public static native Int8Array unwrapByteArray(byte[] buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
+ public static native ArrayBuffer unwrapArrayBuffer(byte[] buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
+ public static native ArrayBufferView unwrapArrayBufferView(byte[] buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
+ public static native byte[] wrapByteArray(Int8Array buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
+ public static native byte[] wrapByteArrayBuffer(ArrayBuffer buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
+ public static native byte[] wrapByteArrayBufferView(ArrayBufferView buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapUnsignedTypedArray.class)
+ public static native Uint8Array unwrapUnsignedByteArray(byte[] buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
+ public static native byte[] wrapUnsignedByteArray(Uint8Array buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
+ public static native Int32Array unwrapIntArray(int[] buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
+ public static native ArrayBuffer unwrapArrayBuffer(int[] buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
+ public static native ArrayBufferView unwrapArrayBufferView(int[] buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
+ public static native int[] wrapIntArray(Int32Array buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
+ public static native int[] wrapIntArrayBuffer(ArrayBuffer buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
+ public static native int[] wrapIntArrayBufferView(ArrayBufferView buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
+ public static native Float32Array unwrapFloatArray(float[] buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
+ public static native ArrayBuffer unwrapArrayBuffer(float[] buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
+ public static native ArrayBufferView unwrapArrayBufferView(float[] buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
+ public static native float[] wrapFloatArray(Float32Array buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
+ public static native float[] wrapFloatArrayBuffer(ArrayBuffer buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
+ public static native float[] wrapFloatArrayBufferView(ArrayBufferView buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
+ public static native Int16Array unwrapShortArray(short[] buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapArrayBuffer.class)
+ public static native ArrayBuffer unwrapArrayBuffer(short[] buf);
+
+ @InjectedBy(TeaVMUtilsUnwrapGenerator.UnwrapTypedArray.class)
+ public static native ArrayBufferView unwrapArrayBufferView(short[] buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapTypedArray.class)
+ public static native short[] wrapShortArray(Int16Array buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBuffer.class)
+ public static native short[] wrapShortArrayBuffer(ArrayBuffer buf);
+
+ @GeneratedBy(TeaVMUtilsUnwrapGenerator.WrapArrayBufferView.class)
+ public static native short[] wrapShortArrayBuffer(ArrayBufferView buf);
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/TeaVMUtilsUnwrapGenerator.java b/src/main/java/net/lax1dude/eaglercraft/sp/TeaVMUtilsUnwrapGenerator.java
new file mode 100644
index 0000000..6358d93
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/TeaVMUtilsUnwrapGenerator.java
@@ -0,0 +1,158 @@
+package net.lax1dude.eaglercraft.sp;
+
+import org.teavm.backend.javascript.codegen.SourceWriter;
+import org.teavm.backend.javascript.spi.Generator;
+import org.teavm.backend.javascript.spi.GeneratorContext;
+import org.teavm.backend.javascript.spi.Injector;
+import org.teavm.backend.javascript.spi.InjectorContext;
+import org.teavm.model.MethodReference;
+
+public class TeaVMUtilsUnwrapGenerator {
+
+ // WARNING: This code uses internal TeaVM APIs that may not have
+ // been intended for end users of the compiler to program with
+
+ public static class UnwrapArrayBuffer implements Injector {
+
+ @Override
+ public void generate(InjectorContext context, MethodReference methodRef) {
+ context.writeExpr(context.getArgument(0));
+ context.getWriter().append(".data.buffer");
+ }
+
+ }
+
+ public static class UnwrapTypedArray implements Injector {
+
+ @Override
+ public void generate(InjectorContext context, MethodReference methodRef) {
+ context.writeExpr(context.getArgument(0));
+ context.getWriter().append(".data");
+ }
+
+ }
+
+ public static class WrapArrayBuffer implements Generator {
+
+ @Override
+ public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) {
+ String parName = context.getParameterName(1);
+ switch (methodRef.getName()) {
+ case "wrapByteArrayBuffer":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_bytecls").append(',').ws();
+ writer.append("new Int8Array(").append(parName).append("))").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ case "wrapIntArrayBuffer":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_intcls").append(',').ws();
+ writer.append("new Int32Array(").append(parName).append("))").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ case "wrapFloatArrayBuffer":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_floatcls").append(',').ws();
+ writer.append("new Float32Array(").append(parName).append("))").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ case "wrapShortArrayBuffer":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_shortcls").append(',').ws();
+ writer.append("new Int16Array(").append(parName).append("))").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ default:
+ break;
+ }
+ }
+
+ }
+
+ public static class WrapArrayBufferView implements Generator {
+
+ @Override
+ public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) {
+ String parName = context.getParameterName(1);
+ switch (methodRef.getName()) {
+ case "wrapByteArrayBufferView":
+ case "wrapUnsignedByteArray":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_bytecls").append(',').ws();
+ writer.append("new Int8Array(").append(parName).append(".buffer))").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ case "wrapIntArrayBufferView":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_intcls").append(',').ws();
+ writer.append("new Int32Array(").append(parName).append(".buffer))").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ case "wrapFloatArrayBufferView":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_floatcls").append(',').ws();
+ writer.append("new Float32Array(").append(parName).append(".buffer))").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ case "wrapShortArrayBufferView":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_shortcls").append(',').ws();
+ writer.append("new Int16Array(").append(parName).append(".buffer))").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ default:
+ break;
+ }
+ }
+
+ }
+
+ public static class WrapTypedArray implements Generator {
+
+ @Override
+ public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) {
+ String parName = context.getParameterName(1);
+ switch (methodRef.getName()) {
+ case "wrapByteArray":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_shortcls").append(',').ws();
+ writer.append(parName).append(")").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ case "wrapIntArray":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_intcls").append(',').ws();
+ writer.append(parName).append(")").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ case "wrapFloatArray":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_floatcls").append(',').ws();
+ writer.append(parName).append(")").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ case "wrapShortArray":
+ writer.append("return ").append(parName).ws().append('?').ws();
+ writer.appendFunction("$rt_wrapArray").append('(').appendFunction("$rt_shortcls").append(',').ws();
+ writer.append(parName).append(")").ws();
+ writer.append(':').ws().append("null;").softNewLine();
+ break;
+ default:
+ break;
+ }
+ }
+
+ }
+
+ public static class UnwrapUnsignedTypedArray implements Injector {
+
+ @Override
+ public void generate(InjectorContext context, MethodReference methodRef) {
+ context.getWriter().append("new Uint8Array(");
+ context.writeExpr(context.getArgument(0));
+ context.getWriter().append(".data.buffer)");
+ }
+
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/VFSChunkLoader.java b/src/main/java/net/lax1dude/eaglercraft/sp/VFSChunkLoader.java
new file mode 100644
index 0000000..d52f920
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/VFSChunkLoader.java
@@ -0,0 +1,307 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+
+import net.minecraft.src.Chunk;
+import net.minecraft.src.ChunkCoordIntPair;
+import net.minecraft.src.CompressedStreamTools;
+import net.minecraft.src.Entity;
+import net.minecraft.src.EntityList;
+import net.minecraft.src.ExtendedBlockStorage;
+import net.minecraft.src.IChunkLoader;
+import net.minecraft.src.MinecraftException;
+import net.minecraft.src.NBTTagCompound;
+import net.minecraft.src.NBTTagList;
+import net.minecraft.src.NextTickListEntry;
+import net.minecraft.src.NibbleArray;
+import net.minecraft.src.TileEntity;
+import net.minecraft.src.World;
+
+public class VFSChunkLoader implements IChunkLoader {
+
+ public final VFile chunkDirectory;
+
+ private static final String hex = "0123456789ABCDEF";
+
+ public static String getChunkPath(int x, int z) {
+ int unsignedX = x + 1900000;
+ int unsignedZ = z + 1900000;
+
+ char[] path = new char[12];
+ for(int i = 5; i >= 0; --i) {
+ path[i] = hex.charAt((unsignedX >> (i * 4)) & 0xF);
+ path[i + 6] = hex.charAt((unsignedZ >> (i * 4)) & 0xF);
+ }
+
+ return new String(path);
+ }
+
+ public static ChunkCoordIntPair getChunkCoords(String filename) {
+ String strX = filename.substring(0, 6);
+ String strZ = filename.substring(6);
+
+ int retX = 0;
+ int retZ = 0;
+
+ for(int i = 0; i < 6; ++i) {
+ retX |= hex.indexOf(strX.charAt(i)) << (i << 2);
+ retZ |= hex.indexOf(strZ.charAt(i)) << (i << 2);
+ }
+
+ return new ChunkCoordIntPair(retX - 1900000, retZ - 1900000);
+ }
+
+ public VFSChunkLoader(VFile chunkDirectory) {
+ this.chunkDirectory = chunkDirectory;
+ }
+
+ @Override
+ public Chunk loadChunk(World var1, int var2, int var3) throws IOException {
+ VFile file = new VFile(chunkDirectory, getChunkPath(var2, var3) + ".dat");
+ byte[] bytes = file.getAllBytes();
+
+ if(bytes == null) {
+ return null;
+ }
+
+ try {
+ NBTTagCompound nbt = CompressedStreamTools.decompress(bytes);
+ nbt = nbt.getCompoundTag("Level");
+ return readChunkFromNBT(var1, nbt, var2, var3);
+ }catch(Throwable t) {
+ file.delete();
+ System.err.println("Corrupted chunk has been deleted: [" + var2 + ", " + var3 + "]");
+ t.printStackTrace();
+ return null;
+ }
+ }
+
+ @Override
+ public void saveChunk(World var1, Chunk var2) throws MinecraftException, IOException {
+
+ NBTTagCompound chunkFile = new NBTTagCompound();
+ this.writeChunkToNBT(var2, var1, chunkFile);
+
+ byte[] save;
+
+ try {
+ NBTTagCompound chunkFileSave = new NBTTagCompound();
+ chunkFileSave.setCompoundTag("Level", chunkFile);
+ save = CompressedStreamTools.compressChunk(chunkFileSave);
+ }catch(IOException e) {
+ System.err.println("Corrupted chunk could not be serialized: [" + var2.xPosition + ", " + var2.zPosition + "]");
+ return;
+ }
+
+ VFile file = new VFile(chunkDirectory, getChunkPath(var2.xPosition, var2.zPosition) + ".dat");
+
+ if(!file.setAllBytes(save)) {
+ System.err.println("Corrupted chunk could not be written: [" + var2.xPosition + ", " + var2.zPosition + "] to file \"" + file.toString() + "\")");
+ }
+
+ }
+
+ @Override
+ public void saveExtraChunkData(World var1, Chunk var2) {
+ // ?
+ }
+
+ @Override
+ public void chunkTick() {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void saveExtraData() {
+ // unused
+ }
+
+ private Chunk readChunkFromNBT(World par1World, NBTTagCompound par2NBTTagCompound, int x, int z) {
+ int var3 = x; //par2NBTTagCompound.getInteger("xPos");
+ int var4 = z; //par2NBTTagCompound.getInteger("zPos");
+ Chunk var5 = new Chunk(par1World, var3, var4);
+ var5.heightMap = par2NBTTagCompound.getIntArray("HeightMap");
+ var5.isTerrainPopulated = par2NBTTagCompound.getBoolean("TerrainPopulated");
+ NBTTagList var6 = par2NBTTagCompound.getTagList("Sections");
+ byte var7 = 16;
+ ExtendedBlockStorage[] var8 = new ExtendedBlockStorage[var7];
+ boolean var9 = !par1World.provider.hasNoSky;
+
+ for (int var10 = 0; var10 < var6.tagCount(); ++var10) {
+ NBTTagCompound var11 = (NBTTagCompound) var6.tagAt(var10);
+ byte var12 = var11.getByte("Y");
+ ExtendedBlockStorage var13 = new ExtendedBlockStorage(var12 << 4, var9);
+ var13.setBlockLSBArray(var11.getByteArray("Blocks"));
+
+ if (var11.hasKey("Add")) {
+ var13.setBlockMSBArray(new NibbleArray(var11.getByteArray("Add"), 4));
+ }
+
+ var13.setBlockMetadataArray(new NibbleArray(var11.getByteArray("Data"), 4));
+ var13.setBlocklightArray(new NibbleArray(var11.getByteArray("BlockLight"), 4));
+
+ if (var9) {
+ var13.setSkylightArray(new NibbleArray(var11.getByteArray("SkyLight"), 4));
+ }
+
+ var13.removeInvalidBlocks();
+ var8[var12] = var13;
+ }
+
+ var5.setStorageArrays(var8);
+
+ if (par2NBTTagCompound.hasKey("Biomes")) {
+ var5.setBiomeArray(par2NBTTagCompound.getByteArray("Biomes"));
+ }
+
+ NBTTagList var17 = par2NBTTagCompound.getTagList("Entities");
+
+ if (var17 != null) {
+ for (int var18 = 0; var18 < var17.tagCount(); ++var18) {
+ NBTTagCompound var20 = (NBTTagCompound) var17.tagAt(var18);
+ Entity var22 = EntityList.createEntityFromNBT(var20, par1World);
+ var5.hasEntities = true;
+
+ if (var22 != null) {
+ var5.addEntity(var22);
+ Entity var14 = var22;
+
+ for (NBTTagCompound var15 = var20; var15.hasKey("Riding"); var15 = var15.getCompoundTag("Riding")) {
+ Entity var16 = EntityList.createEntityFromNBT(var15.getCompoundTag("Riding"), par1World);
+
+ if (var16 != null) {
+ var5.addEntity(var16);
+ var14.mountEntity(var16);
+ }
+
+ var14 = var16;
+ }
+ }
+ }
+ }
+
+ NBTTagList var19 = par2NBTTagCompound.getTagList("TileEntities");
+
+ if (var19 != null) {
+ for (int var21 = 0; var21 < var19.tagCount(); ++var21) {
+ NBTTagCompound var24 = (NBTTagCompound) var19.tagAt(var21);
+ TileEntity var26 = TileEntity.createAndLoadEntity(var24);
+
+ if (var26 != null) {
+ var5.addTileEntity(var26);
+ }
+ }
+ }
+
+ if (par2NBTTagCompound.hasKey("TileTicks")) {
+ NBTTagList var23 = par2NBTTagCompound.getTagList("TileTicks");
+
+ if (var23 != null) {
+ for (int var25 = 0; var25 < var23.tagCount(); ++var25) {
+ NBTTagCompound var27 = (NBTTagCompound) var23.tagAt(var25);
+ par1World.scheduleBlockUpdateFromLoad(var27.getInteger("x"), var27.getInteger("y"),
+ var27.getInteger("z"), var27.getInteger("i"), var27.getInteger("t"), var27.getInteger("p"));
+ }
+ }
+ }
+
+ return var5;
+ }
+
+ private void writeChunkToNBT(Chunk par1Chunk, World par2World, NBTTagCompound par3NBTTagCompound) {
+ par3NBTTagCompound.setInteger("xPos", par1Chunk.xPosition);
+ par3NBTTagCompound.setInteger("zPos", par1Chunk.zPosition);
+ par3NBTTagCompound.setLong("LastUpdate", par2World.getTotalWorldTime());
+ par3NBTTagCompound.setIntArray("HeightMap", par1Chunk.heightMap);
+ par3NBTTagCompound.setBoolean("TerrainPopulated", par1Chunk.isTerrainPopulated);
+ ExtendedBlockStorage[] var4 = par1Chunk.getBlockStorageArray();
+ NBTTagList var5 = new NBTTagList("Sections");
+ boolean var6 = !par2World.provider.hasNoSky;
+ ExtendedBlockStorage[] var7 = var4;
+ int var8 = var4.length;
+ NBTTagCompound var11;
+
+ for (int var9 = 0; var9 < var8; ++var9) {
+ ExtendedBlockStorage var10 = var7[var9];
+
+ if (var10 != null) {
+ var11 = new NBTTagCompound();
+ var11.setByte("Y", (byte) (var10.getYLocation() >> 4 & 255));
+ var11.setByteArray("Blocks", var10.getBlockLSBArray());
+
+ if (var10.getBlockMSBArray() != null) {
+ var11.setByteArray("Add", var10.getBlockMSBArray().data);
+ }
+
+ var11.setByteArray("Data", var10.getMetadataArray().data);
+ var11.setByteArray("BlockLight", var10.getBlocklightArray().data);
+
+ if (var6) {
+ var11.setByteArray("SkyLight", var10.getSkylightArray().data);
+ } else {
+ var11.setByteArray("SkyLight", new byte[var10.getBlocklightArray().data.length]);
+ }
+
+ var5.appendTag(var11);
+ }
+ }
+
+ par3NBTTagCompound.setTag("Sections", var5);
+ par3NBTTagCompound.setByteArray("Biomes", par1Chunk.getBiomeArray());
+ par1Chunk.hasEntities = false;
+ NBTTagList var16 = new NBTTagList();
+ Iterator var18;
+
+ for (var8 = 0; var8 < par1Chunk.entityLists.length; ++var8) {
+ var18 = par1Chunk.entityLists[var8].iterator();
+
+ while (var18.hasNext()) {
+ Entity var20 = (Entity) var18.next();
+ var11 = new NBTTagCompound();
+
+ if (var20.addEntityID(var11)) {
+ par1Chunk.hasEntities = true;
+ var16.appendTag(var11);
+ }
+ }
+ }
+
+ par3NBTTagCompound.setTag("Entities", var16);
+ NBTTagList var17 = new NBTTagList();
+ var18 = par1Chunk.chunkTileEntityMap.values().iterator();
+
+ while (var18.hasNext()) {
+ TileEntity var21 = (TileEntity) var18.next();
+ var11 = new NBTTagCompound();
+ var21.writeToNBT(var11);
+ var17.appendTag(var11);
+ }
+
+ par3NBTTagCompound.setTag("TileEntities", var17);
+ List var19 = par2World.getPendingBlockUpdates(par1Chunk, false);
+
+ if (var19 != null) {
+ long var22 = par2World.getTotalWorldTime();
+ NBTTagList var12 = new NBTTagList();
+ Iterator var13 = var19.iterator();
+
+ while (var13.hasNext()) {
+ NextTickListEntry var14 = (NextTickListEntry) var13.next();
+ NBTTagCompound var15 = new NBTTagCompound();
+ var15.setInteger("i", var14.blockID);
+ var15.setInteger("x", var14.xCoord);
+ var15.setInteger("y", var14.yCoord);
+ var15.setInteger("z", var14.zCoord);
+ var15.setInteger("t", (int) (var14.scheduledTime - var22));
+ var15.setInteger("p", var14.field_82754_f);
+ var12.appendTag(var15);
+ }
+
+ par3NBTTagCompound.setTag("TileTicks", var12);
+ }
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/VFSIterator.java b/src/main/java/net/lax1dude/eaglercraft/sp/VFSIterator.java
new file mode 100644
index 0000000..a8d12c7
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/VFSIterator.java
@@ -0,0 +1,17 @@
+package net.lax1dude.eaglercraft.sp;
+
+public interface VFSIterator {
+
+ public static class BreakLoop extends RuntimeException {
+ public BreakLoop() {
+ super("iterator loop break request");
+ }
+ }
+
+ public default void end() {
+ throw new BreakLoop();
+ }
+
+ public void next(VIteratorFile entry);
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/VFSSaveFormat.java b/src/main/java/net/lax1dude/eaglercraft/sp/VFSSaveFormat.java
new file mode 100644
index 0000000..6a1515d
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/VFSSaveFormat.java
@@ -0,0 +1,39 @@
+package net.lax1dude.eaglercraft.sp;
+
+import net.minecraft.src.IProgressUpdate;
+import net.minecraft.src.ISaveFormat;
+import net.minecraft.src.ISaveHandler;
+
+public class VFSSaveFormat implements ISaveFormat {
+
+ private VFSSaveHandler folder;
+
+ public VFSSaveFormat(VFSSaveHandler dir) {
+ folder = dir;
+ }
+
+ @Override
+ public ISaveHandler getSaveLoader(String var1, boolean var2) {
+ return folder;
+ }
+
+ @Override
+ public void flushCache() {
+ }
+
+ @Override
+ public boolean deleteWorldDirectory(String var1) {
+ return true;
+ }
+
+ @Override
+ public boolean isOldMapFormat(String var1) {
+ return false;
+ }
+
+ @Override
+ public boolean convertMapFormat(String var1, IProgressUpdate var2) {
+ return false;
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/VFSSaveHandler.java b/src/main/java/net/lax1dude/eaglercraft/sp/VFSSaveHandler.java
new file mode 100644
index 0000000..a4c8927
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/VFSSaveHandler.java
@@ -0,0 +1,172 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import net.minecraft.src.CompressedStreamTools;
+import net.minecraft.src.EntityPlayer;
+import net.minecraft.src.IChunkLoader;
+import net.minecraft.src.IPlayerFileData;
+import net.minecraft.src.ISaveHandler;
+import net.minecraft.src.MinecraftException;
+import net.minecraft.src.NBTTagCompound;
+import net.minecraft.src.WorldInfo;
+import net.minecraft.src.WorldProvider;
+
+public class VFSSaveHandler implements ISaveHandler, IPlayerFileData {
+
+ public final VFile worldDirectory;
+
+ private final HashMap chunkLoaders = new HashMap<>();
+
+ public VFSSaveHandler(VFile worldDirectory) {
+ this.worldDirectory = worldDirectory;
+ }
+
+ @Override
+ public WorldInfo loadWorldInfo() {
+
+ byte[] level_dat_bin = (new VFile(worldDirectory, "level.dat")).getAllBytes();
+
+ if(level_dat_bin == null) {
+ return null;
+ }
+
+ try {
+ NBTTagCompound level_dat = CompressedStreamTools.decompress(level_dat_bin);
+ return new WorldInfo(level_dat.getCompoundTag("Data"));
+ }catch(Throwable t) {
+ System.err.println("Could not parse level.dat!");
+ t.printStackTrace();
+ }
+
+ return null;
+ }
+
+ @Override
+ public void checkSessionLock() throws MinecraftException {
+ // no
+ }
+
+ @Override
+ public IChunkLoader getChunkLoader(WorldProvider var1) {
+ VFSChunkLoader loader = chunkLoaders.get(var1.dimensionId);
+
+ if(loader == null) {
+ loader = new VFSChunkLoader(new VFile(worldDirectory, "level" + var1.dimensionId));
+ chunkLoaders.put(var1.dimensionId, loader);
+ }
+
+ return loader;
+ }
+
+ @Override
+ public void saveWorldInfoWithPlayer(WorldInfo var1, NBTTagCompound var2) {
+ NBTTagCompound var3 = var2 != null ? var1.cloneNBTCompound(var2) : var1.getNBTTagCompound();
+ NBTTagCompound var4 = new NBTTagCompound();
+ var4.setTag("Data", var3);
+
+ VFile level_dat = new VFile(worldDirectory, "level.dat");
+
+ byte[] compressed;
+
+ try {
+ compressed = CompressedStreamTools.compress(var4);
+ }catch(IOException e) {
+ System.err.println("Could not serialize \"" + level_dat + "\"");
+ e.printStackTrace();
+ return;
+ }
+
+ if(!level_dat.setAllBytes(compressed)) {
+ System.err.println("Could not save \"" + level_dat + "\" to filesystem");
+ }
+ }
+
+ @Override
+ public void saveWorldInfo(WorldInfo var1) {
+ saveWorldInfoWithPlayer(var1, null);
+ }
+
+ @Override
+ public IPlayerFileData getPlayerNBTManager() {
+ return this;
+ }
+
+ @Override
+ public void flush() {
+
+ }
+
+ @Override
+ public VFile getMapFileFromName(String var1) {
+ return new VFile(worldDirectory, "data", var1 + ".dat");
+ }
+
+ @Override
+ public String getWorldDirectoryName() {
+ return worldDirectory.toString();
+ }
+
+ @Override
+ public void writePlayerData(EntityPlayer var1) {
+ NBTTagCompound var2 = new NBTTagCompound();
+ var1.writeToNBT(var2);
+
+ byte[] bin;
+
+ try {
+ bin = CompressedStreamTools.compress(var2);
+ }catch(Throwable t) {
+ System.err.println("Could not serialize player data for \"" + var1.username + "\"");
+ t.printStackTrace();
+ return;
+ }
+
+ VFile playerData = new VFile(worldDirectory, "player", var1.username.toLowerCase() + ".dat");
+
+ if(!playerData.setAllBytes(bin)) {
+ System.err.println("Could not write player data for \"" + var1.username + "\" to file \"" + playerData.toString() + "\"");
+ }
+ }
+
+ @Override
+ public NBTTagCompound readPlayerData(EntityPlayer var1) {
+ VFile playerData = new VFile(worldDirectory, "player", var1.username.toLowerCase() + ".dat");
+
+ NBTTagCompound ret = null;
+
+ byte[] playerBin = playerData.getAllBytes();
+ if(playerBin != null) {
+ try {
+ ret = CompressedStreamTools.decompress(playerBin);
+ var1.readFromNBT(ret);
+ }catch(IOException e) {
+ System.err.println("Could not deserialize player data for \"" + var1.username + "\"");
+ e.printStackTrace();
+ }
+ }
+
+ return ret;
+ }
+
+ @Override
+ public String[] getAvailablePlayerDat() {
+ return null;
+ }
+
+ public static String worldNameToFolderName(String par1Str) {
+ par1Str = par1Str.replaceAll("[\\./\"]", "_");
+
+ boolean shit = true;
+ while(shit) {
+ shit = (new VFile("worlds", par1Str, "level.dat")).exists();
+ if(shit) {
+ par1Str = par1Str + "_";
+ }
+ }
+
+ return par1Str;
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/VFSTestClass.java b/src/main/java/net/lax1dude/eaglercraft/sp/VFSTestClass.java
new file mode 100644
index 0000000..e354489
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/VFSTestClass.java
@@ -0,0 +1,117 @@
+package net.lax1dude.eaglercraft.sp;
+
+public class VFSTestClass {
+
+ public static void test(VirtualFilesystem vfs) {
+ /*
+ System.out.println("'test1' exists: " + vfs.getFile("test1").exists());
+ System.out.println("'test1' chars: " + vfs.getFile("test1").getAllChars());
+ System.out.println("'test2' chars: " + vfs.getFile("test2").getAllChars());
+ System.out.println("'test3' chars: " + vfs.getFile("test3").getAllChars());
+ System.out.println("'test2' exists: " + vfs.getFile("test2").exists());
+ System.out.println("'test3' exists: " + vfs.getFile("test3").exists());
+
+ System.out.println("'test1' set chars 'test string 1': " + vfs.getFile("test1").setAllChars("test string 1"));
+ System.out.println("'test2' set chars 'test string 2': " + vfs.getFile("test2").setAllChars("test string 2"));
+ System.out.println("'test3' set chars 'test string 3': " + vfs.getFile("test3").setAllChars("test string 3"));
+
+ System.out.println("'test1' exists: " + vfs.getFile("test1").exists());
+ System.out.println("'test2' exists: " + vfs.getFile("test2").exists());
+ System.out.println("'test3' exists: " + vfs.getFile("test3").exists());
+
+ System.out.println("'test1' chars: " + vfs.getFile("test1").getAllChars());
+ System.out.println("'test2' chars: " + vfs.getFile("test2").getAllChars());
+ System.out.println("'test3' chars: " + vfs.getFile("test3").getAllChars());
+
+ System.out.println("'test3' delete: " + vfs.getFile("test3").delete());
+ System.out.println("'test3' exists: " + vfs.getFile("test3").exists());
+
+ System.out.println("'test2' delete: " + vfs.getFile("test2").delete());
+ System.out.println("'test2' chars: " + vfs.getFile("test2").getAllChars());
+
+ System.out.println("'test4' exists: " + vfs.getFile("test4").exists());
+ System.out.println("'test1' to 'test4' rename: " + vfs.getFile("test1").rename("test4"));
+ System.out.println("'test4' exists: " + vfs.getFile("test4").exists());
+ System.out.println("'test4' chars: " + vfs.getFile("test4").getAllChars());
+ System.out.println("'test1' exists: " + vfs.getFile("test1").exists());
+ System.out.println("'test4' to 'test1' rename: " + vfs.getFile("test4").rename("test1"));
+ System.out.println("'test4' exists: " + vfs.getFile("test4").exists());
+ System.out.println("'test4' chars: " + vfs.getFile("test4").getAllChars());
+ System.out.println("'test1' exists: " + vfs.getFile("test1").exists());
+ System.out.println("'test1' chars: " + vfs.getFile("test1").getAllChars());
+
+ System.out.println("'test1' cache get chars: " + vfs.getFile("test1", true).getAllChars());
+ System.out.println("'test1' cache exists: " + vfs.getFile("test1", true).exists());
+ System.out.println("'test1' cache delete: " + vfs.getFile("test1", true).delete());
+ System.out.println("'test1' cache exists: " + vfs.getFile("test1", true).exists());
+ System.out.println("'test1' cache get chars: " + vfs.getFile("test1", true).getAllChars());
+
+ System.out.println("'test1' cache set chars 'test cache string 1': " + vfs.getFile("test1", true).setAllChars("test cache string 1"));
+ System.out.println("'test2' cache set chars 'test cache string 2': " + vfs.getFile("test2", true).setAllChars("test cache string 2"));
+ System.out.println("'test3' cache set chars 'test cache string 3': " + vfs.getFile("test3", true).setAllChars("test cache string 3"));
+
+ System.out.println("'test1' cache chars: " + vfs.getFile("test1").getAllChars());
+ System.out.println("'test2' cache chars: " + vfs.getFile("test2").getAllChars());
+ System.out.println("'test3' cache chars: " + vfs.getFile("test3").getAllChars());
+
+ System.out.println("'test1' cache copy chars: " + VirtualFilesystem.utf8(vfs.getFile("test1").getAllBytes(true)));
+ System.out.println("'test2' cache copy chars: " + VirtualFilesystem.utf8(vfs.getFile("test2").getAllBytes(true)));
+ System.out.println("'test3' cache copy chars: " + VirtualFilesystem.utf8(vfs.getFile("test3").getAllBytes(true)));
+ */
+
+ VFile f = new VFile("test1");
+ System.out.println(f);
+
+ f = new VFile("/test1");
+ System.out.println(f);
+
+ f = new VFile("/test2/");
+ System.out.println(f);
+
+ f = new VFile("test2/");
+ System.out.println(f);
+
+ f = new VFile("test2/teste");
+ System.out.println(f);
+
+ f = new VFile("\\test2\\teste");
+ System.out.println(f);
+
+ f = new VFile("\\test2\\teste\\..\\eag");
+ System.out.println(f);
+
+ f = new VFile("test2", "teste", "eag");
+ System.out.println(f);
+
+ f = new VFile(f, "../", "test2", "teste", "eag");
+ System.out.println(f);
+
+ f = new VFile(f, "../../", "test2", ".", "eag");
+ System.out.println(f);
+
+ f = new VFile("you/eag", f);
+ System.out.println(f);
+
+ f = new VFile(" you/ eag ", f);
+ System.out.println(f);
+
+ f = new VFile("\\yee\\", f);
+ System.out.println(f);
+
+ f = new VFile("\\yee\\", "yeeler", f, new VFile("yee"));
+ System.out.println(f);
+
+ f = new VFile(f, new VFile("yee2"));
+ System.out.println(f);
+
+ f = new VFile("yee/deevler/", new VFile("yee2"));
+ System.out.println(f);
+
+ f = new VFile("yee/../../../../", new VFile("yee2"));
+ System.out.println(f);
+
+ f = new VFile("yee/../../deevler../../", new VFile("yee2"));
+ System.out.println(f);
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/VFile.java b/src/main/java/net/lax1dude/eaglercraft/sp/VFile.java
new file mode 100644
index 0000000..f07daa5
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/VFile.java
@@ -0,0 +1,227 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class VFile {
+
+ public static final String pathSeperator = "/";
+ public static final String[] altPathSeperator = new String[] { "\\" };
+
+ public static String normalizePath(String p) {
+ for(int i = 0; i < altPathSeperator.length; ++i) {
+ p = p.replace(altPathSeperator[i], pathSeperator);
+ }
+ if(p.startsWith(pathSeperator)) {
+ p = p.substring(1);
+ }
+ if(p.endsWith(pathSeperator)) {
+ p = p.substring(0, p.length() - pathSeperator.length());
+ }
+ return p;
+ }
+
+ public static String[] splitPath(String p) {
+ String[] pth = normalizePath(p).split(pathSeperator);
+ for(int i = 0; i < pth.length; ++i) {
+ pth[i] = pth[i].trim();
+ }
+ return pth;
+ }
+
+ protected String path;
+
+ public static String createPath(Object... p) {
+ ArrayList r = new ArrayList<>();
+ for(int i = 0; i < p.length; ++i) {
+ if(p[i] == null) {
+ continue;
+ }
+ String gg = p[i].toString();
+ if(gg == null) {
+ continue;
+ }
+ String[] parts = splitPath(gg);
+ for(int j = 0; j < parts.length; ++j) {
+ if(parts[j] == null || parts[j].equals(".")) {
+ continue;
+ }else if(parts[j].equals("..") && r.size() > 0) {
+ int k = r.size() - 1;
+ if(!r.get(k).equals("..")) {
+ r.remove(k);
+ }else {
+ r.add("..");
+ }
+ }else {
+ r.add(parts[j]);
+ }
+ }
+ }
+ if(r.size() > 0) {
+ StringBuilder s = new StringBuilder();
+ for(int i = 0; i < r.size(); ++i) {
+ if(i > 0) {
+ s.append(pathSeperator);
+ }
+ s.append(r.get(i));
+ }
+ return s.toString();
+ }else {
+ return null;
+ }
+ }
+
+ public VFile(Object... p) {
+ this.path = createPath(p);
+ }
+
+ public InputStream getInputStream() {
+ return isRelative() ? null : SYS.VFS.getFile(path).getInputStream();
+ }
+
+ public OutputStream getOutputStream() {
+ return isRelative() ? null : SYS.VFS.getFile(path).getOutputStream();
+ }
+
+ public String toString() {
+ return path;
+ }
+
+ public boolean isRelative() {
+ return path == null || path.contains("..");
+ }
+
+ public boolean canRead() {
+ return !isRelative() && SYS.VFS.fileExists(path);
+ }
+
+ public String getPath() {
+ return path.equals("unnamed") ? null : path;
+ }
+
+ public String getName() {
+ if(path == null) {
+ return null;
+ }
+ int i = path.lastIndexOf(pathSeperator);
+ return i == -1 ? path : path.substring(i + 1);
+ }
+
+ public boolean canWrite() {
+ return !isRelative();
+ }
+
+ public String getParent() {
+ if(path == null) {
+ return null;
+ }
+ int i = path.indexOf(pathSeperator);
+ return i == -1 ? ".." : path.substring(0, i);
+ }
+
+ public int hashCode() {
+ return path == null ? 0 : path.hashCode();
+ }
+
+ public boolean equals(Object o) {
+ return path != null && o != null && (o instanceof VFile) && path.equals(((VFile)o).path);
+ }
+
+ public boolean exists() {
+ return !isRelative() && SYS.VFS.fileExists(path);
+ }
+
+ public boolean delete() {
+ return !isRelative() && SYS.VFS.deleteFile(path);
+ }
+
+ public boolean renameTo(String p, boolean copy) {
+ if(!isRelative() && SYS.VFS.renameFile(path, p, copy)) {
+ path = p;
+ return true;
+ }
+ return false;
+ }
+
+ public int length() {
+ return isRelative() ? -1 : SYS.VFS.getFile(path).getSize();
+ }
+
+ public void getBytes(int fileOffset, byte[] array, int offset, int length) {
+ if(isRelative()) {
+ throw new ArrayIndexOutOfBoundsException("File is relative");
+ }
+ SYS.VFS.getFile(path).getBytes(fileOffset, array, offset, length);
+ }
+
+ public void setCacheEnabled() {
+ if(isRelative()) {
+ throw new RuntimeException("File is relative");
+ }
+ SYS.VFS.getFile(path).setCacheEnabled();
+ }
+
+ public byte[] getAllBytes() {
+ if(isRelative()) {
+ return null;
+ }
+ return SYS.VFS.getFile(path).getAllBytes();
+ }
+
+ public String getAllChars() {
+ if(isRelative()) {
+ return null;
+ }
+ return SYS.VFS.getFile(path).getAllChars();
+ }
+
+ public String[] getAllLines() {
+ if(isRelative()) {
+ return null;
+ }
+ return SYS.VFS.getFile(path).getAllLines();
+ }
+
+ public byte[] getAllBytes(boolean copy) {
+ if(isRelative()) {
+ return null;
+ }
+ return SYS.VFS.getFile(path).getAllBytes(copy);
+ }
+
+ public boolean setAllChars(String bytes) {
+ if(isRelative()) {
+ return false;
+ }
+ return SYS.VFS.getFile(path).setAllChars(bytes);
+ }
+
+ public boolean setAllBytes(byte[] bytes) {
+ if(isRelative()) {
+ return false;
+ }
+ return SYS.VFS.getFile(path).setAllBytes(bytes);
+ }
+
+ public boolean setAllBytes(byte[] bytes, boolean copy) {
+ if(isRelative()) {
+ return false;
+ }
+ return SYS.VFS.getFile(path).setAllBytes(bytes, copy);
+ }
+
+ public List list() {
+ if(isRelative()) {
+ return Arrays.asList(path);
+ }
+ return SYS.VFS.listFiles(path);
+ }
+
+ public int deleteAll() {
+ return isRelative() ? 0 : SYS.VFS.deleteFiles(path);
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/VIteratorFile.java b/src/main/java/net/lax1dude/eaglercraft/sp/VIteratorFile.java
new file mode 100644
index 0000000..5ba3d8b
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/VIteratorFile.java
@@ -0,0 +1,289 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.teavm.interop.Async;
+import org.teavm.interop.AsyncCallback;
+import org.teavm.jso.JSBody;
+import org.teavm.jso.JSObject;
+import org.teavm.jso.dom.events.Event;
+import org.teavm.jso.dom.events.EventListener;
+import org.teavm.jso.indexeddb.IDBCursor;
+import org.teavm.jso.indexeddb.IDBRequest;
+import org.teavm.jso.typedarrays.ArrayBuffer;
+import org.teavm.jso.typedarrays.Uint8Array;
+
+/**
+ * Do not use an instance of this class outside of the VFSIterator.next() method
+ */
+public class VIteratorFile extends VFile {
+
+ static final VIteratorFile instance = new VIteratorFile();
+
+ private VIteratorFile() {
+ super("");
+ this.idx = -1;
+ this.cur = null;
+ this.vfs = null;
+ }
+
+ private static class VirtualIteratorOutputStream extends ByteArrayOutputStream {
+
+ private final VIteratorFile itr;
+
+ protected VirtualIteratorOutputStream(VIteratorFile itr) {
+ this.itr = itr;
+ }
+
+ public void close() throws IOException {
+ if(!itr.setAllBytes(super.toByteArray(), false)) {
+ throw new IOException("Could not close stream and write to \"" + itr.path + "\" on VFS \"" + itr.vfs.database + "\" (the file was probably deleted)");
+ }
+ }
+
+ }
+
+ private int idx;
+ private IDBCursor cur;
+ private VirtualFilesystem vfs;
+ private boolean wasDeleted;
+
+ @JSBody(params = { "k" }, script = "return ((typeof k) === \"string\") ? k : (((typeof k) === \"undefined\") ? null : (((typeof k[0]) === \"string\") ? k[0] : null));")
+ private static native String readKey(JSObject k);
+
+ static VIteratorFile create(int idx, VirtualFilesystem vfs, IDBCursor cur) {
+ String k = readKey(cur.getKey());
+ if(k == null) {
+ return null;
+ }
+ instance.update(idx, k, vfs, cur);
+ return instance;
+ }
+
+ public VFile makeVFile() {
+ return new VFile(path);
+ }
+
+ private void update(int idx, String path, VirtualFilesystem vfs, IDBCursor cur) {
+ this.idx = idx;
+ this.path = path;
+ this.vfs = vfs;
+ this.cur = cur;
+ this.wasDeleted = false;
+ }
+
+ public InputStream getInputStream() {
+ return !wasDeleted ? new ByteArrayInputStream(getAllBytes()) : null;
+ }
+
+ public OutputStream getOutputStream() {
+ return !wasDeleted ? new VirtualIteratorOutputStream(this) : null;
+ }
+
+ public String toString() {
+ return path;
+ }
+
+ public boolean isRelative() {
+ return false;
+ }
+
+ public boolean canRead() {
+ return !wasDeleted;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getName() {
+ if(path == null) {
+ return null;
+ }
+ int i = path.indexOf(pathSeperator);
+ return i == -1 ? path : path.substring(i + 1);
+ }
+
+ public boolean canWrite() {
+ return !wasDeleted;
+ }
+
+ public String getParent() {
+ if(path == null) {
+ return null;
+ }
+ int i = path.indexOf(pathSeperator);
+ return i == -1 ? ".." : path.substring(0, i);
+ }
+
+ public int hashCode() {
+ return path == null ? 0 : path.hashCode();
+ }
+
+ public boolean equals(Object o) {
+ return path != null && o != null && (o instanceof VFile) && path.equals(((VFile)o).path);
+ }
+
+ public boolean exists() {
+ return !wasDeleted;
+ }
+
+ public boolean delete() {
+ return wasDeleted = AsyncHandlers.awaitRequest(cur.delete()).bool;
+ }
+
+ public boolean renameTo(String p) {
+ byte[] data = getAllBytes();
+ String op = path;
+ path = p;
+ if(!setAllBytes(data)) {
+ path = op;
+ return false;
+ }
+ path = op;
+ if(!delete()) {
+ return false;
+ }
+ path = p;
+ return true;
+ }
+
+ public int length() {
+ JSObject obj = cur.getValue();
+
+ if(obj == null) {
+ throw new RuntimeException("Value of entry is missing");
+ }
+
+ ArrayBuffer arr = readRow(obj);
+
+ if(arr == null) {
+ throw new RuntimeException("Value of the fucking value of the entry is missing");
+ }
+
+ return arr.getByteLength();
+ }
+
+ public void getBytes(int fileOffset, byte[] array, int offset, int length) {
+ JSObject obj = cur.getValue();
+
+ if(obj == null) {
+ throw new ArrayIndexOutOfBoundsException("Value of entry is missing");
+ }
+
+ ArrayBuffer arr = readRow(obj);
+
+ if(arr == null) {
+ throw new ArrayIndexOutOfBoundsException("Value of the fucking value of the entry is missing");
+ }
+
+ Uint8Array a = new Uint8Array(arr);
+
+ if(a.getLength() < fileOffset + length) {
+ throw new ArrayIndexOutOfBoundsException("file '" + path + "' size was "+a.getLength()+" but user tried to read index "+(fileOffset + length - 1));
+ }
+
+ for(int i = 0; i < length; ++i) {
+ array[i + offset] = (byte)a.get(i + fileOffset);
+ }
+ }
+
+ public void setCacheEnabled() {
+ // no
+ }
+
+ @JSBody(params = { "obj" }, script = "return (typeof obj === 'undefined') ? null : ((typeof obj.data === 'undefined') ? null : obj.data);")
+ private static native ArrayBuffer readRow(JSObject obj);
+
+ public byte[] getAllBytes() {
+ JSObject obj = cur.getValue();
+
+ if(obj == null) {
+ return null;
+ }
+
+ ArrayBuffer arr = readRow(obj);
+
+ if(arr == null) {
+ return null;
+ }
+
+ Uint8Array a = new Uint8Array(arr);
+ int ii = a.getByteLength();
+
+ byte[] array = new byte[ii];
+ for(int i = 0; i < ii; ++i) {
+ array[i] = (byte)a.get(i);
+ }
+
+ return array;
+ }
+
+ public String getAllChars() {
+ return VirtualFilesystem.utf8(getAllBytes());
+ }
+
+ public String[] getAllLines() {
+ return VirtualFilesystem.lines(VirtualFilesystem.utf8(getAllBytes()));
+ }
+
+ public byte[] getAllBytes(boolean copy) {
+ return getAllBytes();
+ }
+
+ public boolean setAllChars(String bytes) {
+ return setAllBytes(VirtualFilesystem.utf8(bytes));
+ }
+
+ public List list() {
+ throw new RuntimeException("Cannot perform list all in VFS callback");
+ }
+
+ public int deleteAll() {
+ throw new RuntimeException("Cannot perform delete all in VFS callback");
+ }
+
+ @JSBody(params = { "pat", "dat" }, script = "return { path: pat, data: dat };")
+ private static native JSObject writeRow(String name, ArrayBuffer data);
+
+ public boolean setAllBytes(byte[] bytes) {
+ ArrayBuffer a = new ArrayBuffer(bytes.length);
+ Uint8Array ar = new Uint8Array(a);
+ ar.set(bytes);
+ JSObject obj = writeRow(path, a);
+ BooleanResult r = AsyncHandlers.awaitRequest(cur.update(obj));
+ return r.bool;
+ }
+
+ public boolean setAllBytes(byte[] bytes, boolean copy) {
+ return setAllBytes(bytes);
+ }
+
+ public static class AsyncHandlers {
+
+ @Async
+ public static native BooleanResult awaitRequest(IDBRequest r);
+
+ private static void awaitRequest(IDBRequest r, final AsyncCallback cb) {
+ r.addEventListener("success", new EventListener() {
+ @Override
+ public void handleEvent(Event evt) {
+ cb.complete(BooleanResult._new(true));
+ }
+ });
+ r.addEventListener("error", new EventListener() {
+ @Override
+ public void handleEvent(Event evt) {
+ cb.complete(BooleanResult._new(false));
+ }
+ });
+ }
+
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/VirtualFilesystem.java b/src/main/java/net/lax1dude/eaglercraft/sp/VirtualFilesystem.java
new file mode 100644
index 0000000..c441824
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/VirtualFilesystem.java
@@ -0,0 +1,688 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import org.teavm.interop.Async;
+import org.teavm.interop.AsyncCallback;
+import org.teavm.jso.JSBody;
+import org.teavm.jso.JSObject;
+import org.teavm.jso.dom.events.EventListener;
+import org.teavm.jso.indexeddb.EventHandler;
+import org.teavm.jso.indexeddb.IDBCountRequest;
+import org.teavm.jso.indexeddb.IDBCursor;
+import org.teavm.jso.indexeddb.IDBCursorRequest;
+import org.teavm.jso.indexeddb.IDBDatabase;
+import org.teavm.jso.indexeddb.IDBFactory;
+import org.teavm.jso.indexeddb.IDBGetRequest;
+import org.teavm.jso.indexeddb.IDBObjectStoreParameters;
+import org.teavm.jso.indexeddb.IDBOpenDBRequest;
+import org.teavm.jso.indexeddb.IDBRequest;
+import org.teavm.jso.indexeddb.IDBTransaction;
+import org.teavm.jso.indexeddb.IDBVersionChangeEvent;
+import org.teavm.jso.typedarrays.ArrayBuffer;
+import org.teavm.jso.typedarrays.Int8Array;
+import org.teavm.jso.typedarrays.Uint8Array;
+
+public class VirtualFilesystem {
+
+ protected static class VirtualOutputStream extends ByteArrayOutputStream {
+ private final VFSFile file;
+
+ protected VirtualOutputStream(VFSFile file) {
+ this.file = file;
+ }
+
+ public void close() throws IOException {
+ if(!file.setAllBytes(super.toByteArray(), false)) {
+ throw new IOException("Could not close stream and write to \"" + file.filePath + "\" on VFS \"" + file.virtualFilesystem.database + "\" (the file was probably deleted)");
+ }
+ }
+ }
+
+ public static class VFSFile {
+
+ public final VirtualFilesystem virtualFilesystem;
+ protected boolean cacheEnabled;
+ protected String filePath;
+ protected int fileSize = -1;
+ protected boolean hasBeenDeleted = false;
+ protected boolean hasBeenAccessed = false;
+ protected boolean exists = false;
+
+ protected byte[] cache = null;
+ protected long cacheHit;
+
+ protected VFSFile(VirtualFilesystem vfs, String filePath, boolean cacheEnabled) {
+ this.virtualFilesystem = vfs;
+ this.filePath = filePath;
+ this.cacheHit = SysUtil.steadyTimeMillis();
+ if(cacheEnabled) {
+ setCacheEnabled();
+ }
+ }
+
+ public boolean equals(Object o) {
+ return (o instanceof VFSFile) && ((VFSFile)o).filePath.equals(filePath);
+ }
+
+ public int hashCode() {
+ return filePath.hashCode();
+ }
+
+ public String getPath() {
+ return filePath;
+ }
+
+ public int getSize() {
+ cacheHit = SysUtil.steadyTimeMillis();
+ if(fileSize < 0) {
+ if(cacheEnabled) {
+ byte[] b = getAllBytes(false);
+ if(b != null) {
+ fileSize = b.length;
+ }
+ }else {
+ ArrayBuffer dat = AsyncHandlers.readWholeFile(virtualFilesystem.indexeddb, filePath);
+ if(dat != null) {
+ fileSize = dat.getByteLength();
+ }
+ }
+ }
+ return fileSize;
+ }
+
+ public InputStream getInputStream() {
+ byte[] dat = getAllBytes(false);
+ if(dat == null) {
+ return null;
+ }
+ return new ByteArrayInputStream(dat);
+ }
+
+ public OutputStream getOutputStream() {
+ return new VirtualOutputStream(this);
+ }
+
+ public void getBytes(int fileOffset, byte[] array, int offset, int length) {
+ if(hasBeenDeleted) {
+ throw new ArrayIndexOutOfBoundsException("file '" + filePath + "' has been deleted");
+ }else if(hasBeenAccessed && !exists) {
+ throw new ArrayIndexOutOfBoundsException("file '" + filePath + "' does not exist");
+ }
+ cacheHit = SysUtil.steadyTimeMillis();
+ if(cacheEnabled && cache != null) {
+ System.arraycopy(cache, fileOffset, array, offset, length);
+ }else {
+ ArrayBuffer aa = AsyncHandlers.readWholeFile(virtualFilesystem.indexeddb, filePath);
+ hasBeenAccessed = true;
+ if(aa != null) {
+ exists = true;
+ }else {
+ exists = false;
+ throw new ArrayIndexOutOfBoundsException("file '" + filePath + "' does not exist");
+ }
+ this.fileSize = aa.getByteLength();
+ if(cacheEnabled) {
+ cache = TeaVMUtils.wrapByteArrayBuffer(aa);
+ }
+ if(fileSize < fileOffset + length) {
+ throw new ArrayIndexOutOfBoundsException("file '" + filePath + "' size was "+fileSize+" but user tried to read index "+(fileOffset + length - 1));
+ }
+ TeaVMUtils.unwrapByteArray(array).set(new Int8Array(aa, fileOffset, length), offset);
+ }
+ }
+
+ public void setCacheEnabled() {
+ if(!cacheEnabled && !hasBeenDeleted && !(hasBeenAccessed && !exists)) {
+ cacheHit = SysUtil.steadyTimeMillis();
+ cache = getAllBytes(false);
+ cacheEnabled = true;
+ }
+ }
+
+ public byte[] getAllBytes() {
+ return getAllBytes(false);
+ }
+
+ public String getAllChars() {
+ return utf8(getAllBytes(false));
+ }
+
+ public String[] getAllLines() {
+ return lines(getAllChars());
+ }
+
+ public byte[] getAllBytes(boolean copy) {
+ if(hasBeenDeleted || (hasBeenAccessed && !exists)) {
+ return null;
+ }
+ cacheHit = SysUtil.steadyTimeMillis();
+ if(cacheEnabled && cache != null) {
+ byte[] b = cache;
+ if(copy) {
+ b = new byte[cache.length];
+ System.arraycopy(cache, 0, b, 0, cache.length);
+ }
+ return b;
+ }else {
+ hasBeenAccessed = true;
+ ArrayBuffer b = AsyncHandlers.readWholeFile(virtualFilesystem.indexeddb, filePath);
+ if(b != null) {
+ exists = true;
+ }else {
+ exists = false;
+ return null;
+ }
+ this.fileSize = b.getByteLength();
+ if(cacheEnabled) {
+ if(copy) {
+ cache = new byte[fileSize];
+ TeaVMUtils.unwrapByteArray(cache).set(new Int8Array(b));
+ }else {
+ cache = TeaVMUtils.wrapByteArrayBuffer(b);
+ }
+ }
+ return TeaVMUtils.wrapByteArrayBuffer(b);
+ }
+ }
+
+ public boolean setAllChars(String bytes) {
+ return setAllBytes(utf8(bytes), true);
+ }
+
+ public boolean setAllBytes(byte[] bytes) {
+ return setAllBytes(bytes, true);
+ }
+
+ public boolean setAllBytes(byte[] bytes, boolean copy) {
+ if(hasBeenDeleted || bytes == null) {
+ return false;
+ }
+ cacheHit = SysUtil.steadyTimeMillis();
+ this.fileSize = bytes.length;
+ if(cacheEnabled) {
+ byte[] copz = bytes;
+ if(copy) {
+ copz = new byte[bytes.length];
+ System.arraycopy(bytes, 0, copz, 0, bytes.length);
+ }
+ cache = copz;
+ return sync();
+ }else {
+ boolean s = AsyncHandlers.writeWholeFile(virtualFilesystem.indexeddb, filePath,
+ TeaVMUtils.unwrapArrayBuffer(bytes)).bool;
+ hasBeenAccessed = true;
+ exists = exists || s;
+ return s;
+ }
+ }
+
+ public boolean sync() {
+ if(cacheEnabled && cache != null && !hasBeenDeleted) {
+ cacheHit = SysUtil.steadyTimeMillis();
+ boolean tryWrite = AsyncHandlers.writeWholeFile(virtualFilesystem.indexeddb, filePath,
+ TeaVMUtils.unwrapArrayBuffer(cache)).bool;
+ hasBeenAccessed = true;
+ exists = exists || tryWrite;
+ return tryWrite;
+ }
+ return false;
+ }
+
+ public boolean delete() {
+ if(!hasBeenDeleted && !(hasBeenAccessed && !exists)) {
+ cacheHit = SysUtil.steadyTimeMillis();
+ if(!AsyncHandlers.deleteFile(virtualFilesystem.indexeddb, filePath).bool) {
+ hasBeenAccessed = true;
+ return false;
+ }
+ virtualFilesystem.fileMap.remove(filePath);
+ hasBeenDeleted = true;
+ hasBeenAccessed = true;
+ exists = false;
+ return true;
+ }
+ return false;
+ }
+
+ public boolean rename(String newName, boolean copy) {
+ if(!hasBeenDeleted && !(hasBeenAccessed && !exists)) {
+ cacheHit = SysUtil.steadyTimeMillis();
+ ArrayBuffer arr = AsyncHandlers.readWholeFile(virtualFilesystem.indexeddb, filePath);
+ hasBeenAccessed = true;
+ if(arr != null) {
+ exists = true;
+ if(!AsyncHandlers.writeWholeFile(virtualFilesystem.indexeddb, newName, arr).bool) {
+ return false;
+ }
+ if(!copy && !AsyncHandlers.deleteFile(virtualFilesystem.indexeddb, filePath).bool) {
+ return false;
+ }
+ }else {
+ exists = false;
+ }
+ if(!copy) {
+ virtualFilesystem.fileMap.remove(filePath);
+ filePath = newName;
+ virtualFilesystem.fileMap.put(newName, this);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public boolean exists() {
+ if(hasBeenDeleted) {
+ return false;
+ }
+ cacheHit = SysUtil.steadyTimeMillis();
+ if(hasBeenAccessed) {
+ return exists;
+ }
+ exists = AsyncHandlers.fileExists(virtualFilesystem.indexeddb, filePath).bool;
+ hasBeenAccessed = true;
+ return exists;
+ }
+
+ }
+
+ private final HashMap fileMap = new HashMap<>();
+
+ public final String database;
+ private final IDBDatabase indexeddb;
+
+ public static class VFSHandle {
+
+ public final boolean failedInit;
+ public final boolean failedLocked;
+ public final String failedError;
+ public final VirtualFilesystem vfs;
+
+ public VFSHandle(boolean init, boolean locked, String error, VirtualFilesystem db) {
+ failedInit = init;
+ failedLocked = locked;
+ failedError = error;
+ vfs = db;
+ }
+
+ public String toString() {
+ if(failedInit) {
+ return "IDBFactory threw an exception, IndexedDB is most likely not supported in this browser." + (failedError == null ? "" : "\n\n" + failedError);
+ }
+ if(failedLocked) {
+ return "The filesystem requested is already in use on a different tab.";
+ }
+ if(failedError != null) {
+ return "The IDBFactory.open() request failed, reason: " + failedError;
+ }
+ return "Virtual Filesystem Object: " + vfs.database;
+ }
+
+ }
+
+ public static VFSHandle openVFS(String db) {
+ DatabaseOpen evt = AsyncHandlers.openDB(db);
+ if(evt.failedInit) {
+ return new VFSHandle(true, false, evt.failedError, null);
+ }
+ if(evt.failedLocked) {
+ return new VFSHandle(false, true, null, null);
+ }
+ if(evt.failedError != null) {
+ return new VFSHandle(false, false, evt.failedError, null);
+ }
+ return new VFSHandle(false, false, null, new VirtualFilesystem(db, evt.database));
+ }
+
+ private VirtualFilesystem(String db, IDBDatabase idb) {
+ database = db;
+ indexeddb = idb;
+ }
+
+ public void close() {
+ indexeddb.close();
+ }
+
+ public VFSFile getFile(String path) {
+ return getFile(path, false);
+ }
+
+ public VFSFile getFile(String path, boolean cache) {
+ VFSFile f = fileMap.get(path);
+ if(f == null) {
+ fileMap.put(path, f = new VFSFile(this, path, cache));
+ }else {
+ if(cache) {
+ f.setCacheEnabled();
+ }
+ }
+ return f;
+ }
+
+ public boolean renameFile(String oldName, String newName, boolean copy) {
+ return getFile(oldName).rename(newName, copy);
+ }
+
+ public boolean deleteFile(String path) {
+ return getFile(path).delete();
+ }
+
+ public boolean fileExists(String path) {
+ return getFile(path).exists();
+ }
+
+ public List listFiles(String prefix) {
+ final ArrayList list = new ArrayList<>();
+ AsyncHandlers.iterateFiles(indexeddb, this, prefix, false, (v) -> {
+ list.add(v.getPath());
+ });
+ return list;
+ }
+
+ public List listVFiles(String prefix) {
+ final ArrayList list = new ArrayList<>();
+ AsyncHandlers.iterateFiles(indexeddb, this, prefix, false, (v) -> {
+ list.add(new VFile(v.getPath()));
+ });
+ return list;
+ }
+
+ public int deleteFiles(String prefix) {
+ return AsyncHandlers.deleteFiles(indexeddb, prefix);
+ }
+
+ public int iterateFiles(String prefix, boolean rw, VFSIterator itr) {
+ return AsyncHandlers.iterateFiles(indexeddb, this, prefix, rw, itr);
+ }
+
+ public int renameFiles(String oldPrefix, String newPrefix, boolean copy) {
+ List filesToCopy = listFiles(oldPrefix);
+ int i = 0;
+ for(String str : filesToCopy) {
+ String f = VFile.createPath(newPrefix, str.substring(oldPrefix.length()));
+ if(!renameFile(str, f, copy)) {
+ System.err.println("Could not " + (copy ? "copy" : "rename") + " file \"" + str + "\" to \"" + f + "\" for some reason");
+ }else {
+ ++i;
+ }
+ }
+ return i;
+ }
+
+ public void flushCache(long age) {
+ long curr = SysUtil.steadyTimeMillis();
+ Iterator files = fileMap.values().iterator();
+ while(files.hasNext()) {
+ if(curr - files.next().cacheHit > age) {
+ files.remove();
+ }
+ }
+ }
+
+ protected static class DatabaseOpen {
+
+ protected final boolean failedInit;
+ protected final boolean failedLocked;
+ protected final String failedError;
+
+ protected final IDBDatabase database;
+
+ protected DatabaseOpen(boolean init, boolean locked, String error, IDBDatabase db) {
+ failedInit = init;
+ failedLocked = locked;
+ failedError = error;
+ database = db;
+ }
+
+ }
+
+ @JSBody(script = "return ((typeof indexedDB) !== 'undefined') ? indexedDB : null;")
+ protected static native IDBFactory createIDBFactory();
+
+ protected static class AsyncHandlers {
+
+ @Async
+ protected static native DatabaseOpen openDB(String name);
+
+ private static void openDB(String name, final AsyncCallback cb) {
+ IDBFactory i = createIDBFactory();
+ if(i == null) {
+ cb.complete(new DatabaseOpen(false, false, "window.indexedDB was null or undefined", null));
+ return;
+ }
+ final IDBOpenDBRequest f = i.open(name, 1);
+ f.setOnBlocked(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(new DatabaseOpen(false, true, null, null));
+ }
+ });
+ f.setOnSuccess(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(new DatabaseOpen(false, false, null, f.getResult()));
+ }
+ });
+ f.setOnError(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(new DatabaseOpen(false, false, "open error", null));
+ }
+ });
+ f.setOnUpgradeNeeded(new EventListener() {
+ @Override
+ public void handleEvent(IDBVersionChangeEvent evt) {
+ f.getResult().createObjectStore("filesystem", IDBObjectStoreParameters.create().keyPath("path"));
+ }
+ });
+ }
+
+ @Async
+ protected static native BooleanResult deleteFile(IDBDatabase db, String name);
+
+ private static void deleteFile(IDBDatabase db, String name, final AsyncCallback cb) {
+ IDBTransaction tx = db.transaction("filesystem", "readwrite");
+ final IDBRequest r = tx.objectStore("filesystem").delete(makeTheFuckingKeyWork(name));
+
+ r.setOnSuccess(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(BooleanResult._new(true));
+ }
+ });
+ r.setOnError(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(BooleanResult._new(false));
+ }
+ });
+ }
+
+ @JSBody(params = { "obj" }, script = "return (typeof obj === 'undefined') ? null : ((typeof obj.data === 'undefined') ? null : obj.data);")
+ protected static native ArrayBuffer readRow(JSObject obj);
+
+ @JSBody(params = { "obj" }, script = "return [obj];")
+ private static native JSObject makeTheFuckingKeyWork(String k);
+
+ @Async
+ protected static native ArrayBuffer readWholeFile(IDBDatabase db, String name);
+
+ private static void readWholeFile(IDBDatabase db, String name, final AsyncCallback cb) {
+ IDBTransaction tx = db.transaction("filesystem", "readonly");
+ final IDBGetRequest r = tx.objectStore("filesystem").get(makeTheFuckingKeyWork(name));
+ r.setOnSuccess(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(readRow(r.getResult()));
+ }
+ });
+ r.setOnError(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(null);
+ }
+ });
+
+ }
+
+ @JSBody(params = { "k" }, script = "return ((typeof k) === \"string\") ? k : (((typeof k) === \"undefined\") ? null : (((typeof k[0]) === \"string\") ? k[0] : null));")
+ private static native String readKey(JSObject k);
+
+ @JSBody(params = { "k" }, script = "return ((typeof k) === \"undefined\") ? null : (((typeof k.path) === \"undefined\") ? null : (((typeof k.path) === \"string\") ? k[0] : null));")
+ private static native String readRowKey(JSObject r);
+
+ @Async
+ protected static native Integer iterateFiles(IDBDatabase db, final VirtualFilesystem vfs, final String prefix, boolean rw, final VFSIterator itr);
+
+ private static void iterateFiles(IDBDatabase db, final VirtualFilesystem vfs, final String prefix, boolean rw, final VFSIterator itr, final AsyncCallback cb) {
+ IDBTransaction tx = db.transaction("filesystem", rw ? "readwrite" : "readonly");
+ final IDBCursorRequest r = tx.objectStore("filesystem").openCursor();
+ final int[] res = new int[1];
+ r.setOnSuccess(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ IDBCursor c = r.getResult();
+ if(c == null || c.getKey() == null || c.getValue() == null) {
+ cb.complete(res[0]);
+ return;
+ }
+ String k = readKey(c.getKey());
+ if(k != null) {
+ if(k.startsWith(prefix)) {
+ int ci = res[0]++;
+ try {
+ itr.next(VIteratorFile.create(ci, vfs, c));
+ }catch(VFSIterator.BreakLoop ex) {
+ cb.complete(res[0]);
+ return;
+ }
+ }
+ }
+ c.doContinue();
+ }
+ });
+ r.setOnError(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(res[0] > 0 ? res[0] : -1);
+ }
+ });
+ }
+
+ @Async
+ protected static native Integer deleteFiles(IDBDatabase db, final String prefix);
+
+ private static void deleteFiles(IDBDatabase db, final String prefix, final AsyncCallback cb) {
+ IDBTransaction tx = db.transaction("filesystem", "readwrite");
+ final IDBCursorRequest r = tx.objectStore("filesystem").openCursor();
+ final int[] res = new int[1];
+ r.setOnSuccess(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ IDBCursor c = r.getResult();
+ if(c == null || c.getKey() == null || c.getValue() == null) {
+ cb.complete(res[0]);
+ return;
+ }
+ String k = readKey(c.getKey());
+ if(k != null) {
+ if(k.startsWith(prefix)) {
+ c.delete();
+ ++res[0];
+ }
+ }
+ c.doContinue();
+ }
+ });
+ r.setOnError(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(res[0] > 0 ? res[0] : -1);
+ }
+ });
+ }
+
+ @Async
+ protected static native BooleanResult fileExists(IDBDatabase db, String name);
+
+ private static void fileExists(IDBDatabase db, String name, final AsyncCallback cb) {
+ IDBTransaction tx = db.transaction("filesystem", "readonly");
+ final IDBCountRequest r = tx.objectStore("filesystem").count(makeTheFuckingKeyWork(name));
+ r.setOnSuccess(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(BooleanResult._new(r.getResult() > 0));
+ }
+ });
+ r.setOnError(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(BooleanResult._new(false));
+ }
+ });
+ }
+
+ @JSBody(params = { "pat", "dat" }, script = "return { path: pat, data: dat };")
+ protected static native JSObject writeRow(String name, ArrayBuffer data);
+
+ @Async
+ protected static native BooleanResult writeWholeFile(IDBDatabase db, String name, ArrayBuffer data);
+
+ private static void writeWholeFile(IDBDatabase db, String name, ArrayBuffer data, final AsyncCallback cb) {
+ IDBTransaction tx = db.transaction("filesystem", "readwrite");
+ final IDBRequest r = tx.objectStore("filesystem").put(writeRow(name, data));
+
+ r.setOnSuccess(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(BooleanResult._new(true));
+ }
+ });
+ r.setOnError(new EventHandler() {
+ @Override
+ public void handleEvent() {
+ cb.complete(BooleanResult._new(false));
+ }
+ });
+ }
+
+ }
+
+ public static byte[] utf8(String str) {
+ if(str == null) return null;
+ return str.getBytes(Charset.forName("UTF-8"));
+ }
+
+ public static String utf8(byte[] str) {
+ if(str == null) return null;
+ return new String(str, Charset.forName("UTF-8"));
+ }
+
+ public static String CRLFtoLF(String str) {
+ if(str == null) return null;
+ str = str.indexOf('\r') != -1 ? str.replace("\r", "") : str;
+ str = str.trim();
+ if(str.endsWith("\n")) {
+ str = str.substring(0, str.length() - 1);
+ }
+ if(str.startsWith("\n")) {
+ str = str.substring(1);
+ }
+ return str;
+ }
+
+ public static String[] lines(String str) {
+ if(str == null) return null;
+ return CRLFtoLF(str).split("\n");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/WorkerListenThread.java b/src/main/java/net/lax1dude/eaglercraft/sp/WorkerListenThread.java
new file mode 100644
index 0000000..ea49888
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/WorkerListenThread.java
@@ -0,0 +1,96 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.src.NetHandler;
+
+public class WorkerListenThread {
+ /** Reference to the MinecraftServer object. */
+ private final MinecraftServer mcServer;
+ private final HashSet connections = new HashSet<>();
+ private final HashMap channels = new HashMap<>();
+
+ /** Whether the network listener object is listening. */
+ public volatile boolean isListening = false;
+
+ public WorkerListenThread(MinecraftServer par1MinecraftServer) {
+ this.mcServer = par1MinecraftServer;
+ this.isListening = true;
+ }
+
+ /**
+ * adds this connection to the list of currently connected players
+ */
+ public void addPlayer(NetHandler par1NetServerHandler) {
+ System.out.println("[Server][ADDPLAYER][" + par1NetServerHandler.getClass().getSimpleName() + "]");
+ this.connections.add(par1NetServerHandler);
+ }
+
+ public void stopListening() {
+ this.isListening = false;
+ List names = new ArrayList<>(channels.keySet());
+ for(int i = 0, l = names.size(); i < l; ++i) {
+ closeChannel(names.get(i));
+ }
+ }
+
+ public boolean openChannel(String player) {
+ System.out.println("[Server][OPENCHANNEL][" + player + "]");
+ return channels.put(player, new WorkerNetworkManager(player, mcServer, this)) == null;
+ }
+
+ public void recievePacket(String player, byte[] data) {
+ WorkerNetworkManager channel = channels.get(player);
+ if(channel == null) {
+ return;
+ }
+ channel.addToRecieveQueue(data);
+ }
+
+ public boolean closeChannel(String player) {
+ System.out.println("[Server][CLOSECHANNEL][" + player + "]");
+ WorkerNetworkManager channel = channels.get(player);
+ if(channel == null) {
+ return false;
+ }
+ channels.remove(player);
+ channel.networkShutdown(null, null, null);
+ return true;
+ }
+
+ private void deleteDeadConnections() {
+ Iterator itr = this.connections.iterator();
+ while (itr.hasNext()) {
+ NetHandler handler = itr.next();
+ if (handler.shouldBeRemoved()) {
+ itr.remove();
+ //System.out.println("[Client][REMOVEDEAD]");
+ }
+ }
+ }
+
+ /**
+ * Handles all incoming connections and packets
+ */
+ public void handleNetworkListenThread() {
+
+ deleteDeadConnections();
+
+ List conns = new ArrayList<>(this.connections);
+ for (NetHandler var2 : conns) {
+ var2.handlePackets();
+ }
+
+ deleteDeadConnections();
+
+ }
+
+ public MinecraftServer getServer() {
+ return this.mcServer;
+ }
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/WorkerNetworkManager.java b/src/main/java/net/lax1dude/eaglercraft/sp/WorkerNetworkManager.java
new file mode 100644
index 0000000..a668b2a
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/WorkerNetworkManager.java
@@ -0,0 +1,156 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.LinkedList;
+import net.lax1dude.eaglercraft.sp.ipc.IPCPacket0CPlayerChannel;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.src.INetworkManager;
+import net.minecraft.src.NetHandler;
+import net.minecraft.src.NetLoginHandler;
+import net.minecraft.src.NetServerHandler;
+import net.minecraft.src.Packet;
+
+public class WorkerNetworkManager implements INetworkManager {
+
+ private NetHandler theNetHandler;
+ private MinecraftServer minecraftServer;
+ private String ipcChannel;
+ private boolean isAlive;
+ private WorkerListenThread listenThread;
+
+ private LinkedList frags = new LinkedList<>();
+
+ public WorkerNetworkManager(String ipcChannel, MinecraftServer srv, WorkerListenThread th) {
+ this.ipcChannel = ipcChannel;
+ this.theNetHandler = new NetLoginHandler(srv, this);
+ th.addPlayer(theNetHandler);
+ this.minecraftServer = srv;
+ this.isAlive = true;
+ this.listenThread = th;
+ }
+
+ @Override
+ public void setNetHandler(NetHandler var1) {
+ theNetHandler = var1;
+ listenThread.addPlayer(theNetHandler);
+ }
+
+ @Override
+ public void addToSendQueue(Packet var1) {
+ if(!isAlive) {
+ return;
+ }
+ try {
+ ByteArrayOutputStream bao = new ByteArrayOutputStream(var1.getPacketSize() + 1);
+ Packet.writePacket(var1, new DataOutputStream(bao));
+ IntegratedServer.sendPlayerPacket(ipcChannel, bao.toByteArray());
+ }catch(IOException e) {
+ System.err.println("Failed to serialize minecraft packet '" + var1.getPacketId() + "' for IPC channel 'NET|" + ipcChannel + "'");
+ e.printStackTrace();
+ return;
+ }
+ }
+
+ public void addToRecieveQueue(byte[] fragment) {
+ //System.out.println("[Server][READ][QUEUE][" + ipcChannel + "]: " + fragment.length);
+ if(!isAlive) {
+ return;
+ }
+ frags.add(fragment);
+ }
+
+ @Override
+ public void wakeThreads() {
+ // no
+ }
+
+ @Override
+ public void processReadPackets() {
+ while(frags.size() > 0) {
+ byte[] pktBytes = frags.remove(0);
+ try {
+ ByteArrayInputStream bai = new ByteArrayInputStream(pktBytes);
+ int pktId = bai.read();
+
+ if(pktId == -1) {
+ System.err.println("Recieved invalid '-1' packet");
+ continue;
+ }
+
+ Packet pkt = Packet.getNewPacket(minecraftServer.getLogAgent(), pktId);
+
+ if(pkt == null) {
+ System.err.println("Recieved invalid '" + pktId + "' packet");
+ continue;
+ }
+
+ pkt.readPacketData(new DataInputStream(bai));
+
+ //System.out.println("[Server][" + ipcChannel + "]: packet '" + pkt.getClass().getSimpleName() + "' recieved");
+
+ try {
+ pkt.processPacket(theNetHandler);
+ }catch(Throwable t) {
+ System.err.println("Could not process minecraft packet 0x" + Integer.toHexString(pkt.getPacketId()) + " class '" + pkt.getClass().getSimpleName() + "' on channel 'NET|" + ipcChannel + "'");
+ t.printStackTrace();
+ }
+
+ }catch(IOException ex) {
+ System.err.println("Could not deserialize a " + pktBytes.length + " byte long minecraft packet of type '" + (pktBytes.length <= 0 ? -1 : (int)(pktBytes[0] & 0xFF)) + "' on channel 'NET|" + ipcChannel + "'");
+ }
+ }
+
+ }
+
+ @Override
+ public void serverShutdown() {
+ if(isAlive) {
+ listenThread.closeChannel(ipcChannel);
+ IntegratedServer.sendIPCPacket(new IPCPacket0CPlayerChannel(ipcChannel, false));
+ }
+ if(theNetHandler != null && (theNetHandler instanceof NetServerHandler)) {
+ ((NetServerHandler)theNetHandler).kickPlayerFromServer(null);
+ }
+ isAlive = false;
+ }
+
+ @Override
+ public int packetSize() { // why is this a thing
+ return 0;
+ }
+
+ @Override
+ public void networkShutdown(String var1, Object... var2) {
+ if(isAlive) {
+ listenThread.closeChannel(ipcChannel);
+ IntegratedServer.sendIPCPacket(new IPCPacket0CPlayerChannel(ipcChannel, false));
+ }
+ if(theNetHandler != null && (theNetHandler instanceof NetServerHandler)) {
+ ((NetServerHandler)theNetHandler).kickPlayerFromServer(null);
+ }
+ isAlive = false;
+ }
+
+ @Override
+ public void closeConnections() {
+
+ }
+
+ @Override
+ public String getServerURI() {
+ return "None? I dont fucking know what a URI is";
+ }
+
+ public boolean equals(Object o) {
+ return (o instanceof WorkerNetworkManager) && ((WorkerNetworkManager)o).ipcChannel.equals(ipcChannel);
+ }
+
+ public int hashCode() {
+ return ipcChannel.hashCode();
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/WorldConverterEPK.java b/src/main/java/net/lax1dude/eaglercraft/sp/WorldConverterEPK.java
new file mode 100644
index 0000000..3164e2d
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/WorldConverterEPK.java
@@ -0,0 +1,80 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.IOException;
+
+import net.minecraft.src.CompressedStreamTools;
+import net.minecraft.src.NBTTagCompound;
+
+public class WorldConverterEPK {
+
+ public static void importWorld(byte[] archiveContents, String newName) throws IOException {
+ String folder = VFSSaveHandler.worldNameToFolderName(newName);
+ VFile dir = new VFile("worlds", folder);
+ EPKDecompiler dc = new EPKDecompiler(archiveContents);
+ EPKDecompiler.FileEntry f = null;
+ int lastProgUpdate = 0;
+ int prog = 0;
+ boolean hasReadType = dc.isOld();
+ while((f = dc.readFile()) != null) {
+ byte[] b = f.data;
+ if(!hasReadType) {
+ if(f.type.equals("HEAD") && f.name.equals("file-type") && EPKDecompiler.readASCII(f.data).equals("epk/world152")) {
+ hasReadType = true;
+ continue;
+ }else {
+ throw new IOException("file does not contain a singleplayer 1.5.2 world!");
+ }
+ }
+ if(f.type.equals("FILE")) {
+ if(f.name.equals("level.dat")) {
+ NBTTagCompound worldDatNBT = CompressedStreamTools.decompress(b);
+ worldDatNBT.getCompoundTag("Data").setString("LevelName", newName);
+ worldDatNBT.getCompoundTag("Data").setLong("LastPlayed", System.currentTimeMillis());
+ b = CompressedStreamTools.compress(worldDatNBT);
+ }
+ VFile ff = new VFile(dir, f.name);
+ ff.setAllBytes(b);
+ prog += b.length;
+ if(prog - lastProgUpdate > 10000) {
+ lastProgUpdate = prog;
+ IntegratedServer.updateStatusString("selectWorld.progress.importing.0", prog);
+ }
+ }
+ }
+ String[] worldsTxt = SYS.VFS.getFile("worlds.txt").getAllLines();
+ if(worldsTxt == null || worldsTxt.length <= 0) {
+ worldsTxt = new String[] { folder };
+ }else {
+ String[] tmp = worldsTxt;
+ worldsTxt = new String[worldsTxt.length + 1];
+ System.arraycopy(tmp, 0, worldsTxt, 0, tmp.length);
+ worldsTxt[worldsTxt.length - 1] = folder;
+ }
+ SYS.VFS.getFile("worlds.txt").setAllChars(String.join("\n", worldsTxt));
+ }
+
+ public static byte[] exportWorld(String worldName) {
+ String realWorldName = worldName;
+ String worldOwner = "UNKNOWN";
+ int j = realWorldName.lastIndexOf(new String(new char[] { (char)253, (char)233, (char)233 }));
+ if(j != -1) {
+ worldOwner = realWorldName.substring(j + 3);
+ realWorldName = realWorldName.substring(0, j);
+ }
+ final int[] bytesWritten = new int[1];
+ final int[] lastUpdate = new int[1];
+ String pfx = "worlds/" + realWorldName + "/";
+ EPK2Compiler c = new EPK2Compiler(realWorldName, worldOwner, "epk/world152");
+ SYS.VFS.iterateFiles(pfx, false, (i) -> {
+ byte[] b = i.getAllBytes();
+ c.append(i.path.substring(pfx.length()), b);
+ bytesWritten[0] += b.length;
+ if (bytesWritten[0] - lastUpdate[0] > 10000) {
+ lastUpdate[0] = bytesWritten[0];
+ IntegratedServer.updateStatusString("selectWorld.progress.exporting.1", bytesWritten[0]);
+ }
+ });
+ return c.complete();
+ }
+
+}
diff --git a/src/main/java/net/lax1dude/eaglercraft/sp/WorldConverterMCA.java b/src/main/java/net/lax1dude/eaglercraft/sp/WorldConverterMCA.java
new file mode 100644
index 0000000..efea349
--- /dev/null
+++ b/src/main/java/net/lax1dude/eaglercraft/sp/WorldConverterMCA.java
@@ -0,0 +1,255 @@
+package net.lax1dude.eaglercraft.sp;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import net.minecraft.src.CompressedStreamTools;
+import net.minecraft.src.NBTTagCompound;
+import net.minecraft.src.RegionFile;
+
+public class WorldConverterMCA {
+
+ public static void importWorld(byte[] archiveContents, String newName) throws IOException {
+ String folderName = newName.replaceAll("[\\./\"]", "_");
+ VFile worldDir = new VFile("worlds", folderName);
+ while((new VFile(worldDir, "level.dat")).exists() || (new VFile(worldDir, "level.dat_old")).exists()) {
+ folderName += "_";
+ worldDir = new VFile("worlds", folderName);
+ }
+ List fileNames = new ArrayList<>();
+ try(ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(archiveContents))) {
+ ZipEntry folderNameFile = null;
+ while((folderNameFile = zis.getNextEntry()) != null) {
+ if (folderNameFile.getName().contains("__MACOSX/")) continue;
+ if (folderNameFile.isDirectory()) continue;
+ String lowerName = folderNameFile.getName().toLowerCase();
+ if (!(lowerName.endsWith(".dat") || lowerName.endsWith(".dat_old") || lowerName.endsWith(".mca") || lowerName.endsWith(".mcr"))) continue;
+ fileNames.add(folderNameFile.getName().toCharArray());
+ }
+ }
+ final int[] i = new int[] { 0 };
+ while(fileNames.get(0).length > i[0] && fileNames.stream().allMatch(w -> w[i[0]] == fileNames.get(0)[i[0]])) i[0]++;
+ int folderPrefixOffset = i[0];
+ try(ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(archiveContents))) {
+ ZipEntry f = null;
+ int lastProgUpdate = 0;
+ int prog = 0;
+ while ((f = zis.getNextEntry()) != null) {
+ if (f.getName().contains("__MACOSX/")) continue;
+ if (f.isDirectory()) continue;
+ String lowerName = f.getName().toLowerCase();
+ if (!(lowerName.endsWith(".dat") || lowerName.endsWith(".dat_old") || lowerName.endsWith(".mca") || lowerName.endsWith(".mcr") || lowerName.endsWith(".bmp"))) continue;
+ byte[] b;
+ int sz = (int)f.getSize();
+ if(sz >= 0) {
+ b = new byte[sz];
+ int j = 0, k;
+ while(j < b.length && (k = zis.read(b, j, b.length - j)) != -1) {
+ j += k;
+ }
+ }else {
+ b = inputStreamToBytesNoClose(zis);
+ }
+ String fileName = f.getName().substring(folderPrefixOffset);
+ if (fileName.equals("level.dat") || fileName.equals("level.dat_old")) {
+ NBTTagCompound worldDatNBT = CompressedStreamTools.readCompressed(new ByteArrayInputStream(b));
+ worldDatNBT.getCompoundTag("Data").setString("LevelName", newName);
+ worldDatNBT.getCompoundTag("Data").setLong("LastPlayed", System.currentTimeMillis());
+ ByteArrayOutputStream bo = new ByteArrayOutputStream();
+ CompressedStreamTools.writeCompressed(worldDatNBT, bo);
+ b = bo.toByteArray();
+ VFile ff = new VFile(worldDir, fileName);
+ ff.setAllBytes(b);
+ prog += b.length;
+ } else if ((fileName.endsWith(".mcr") || fileName.endsWith(".mca")) && (fileName.startsWith("region/") || fileName.startsWith("DIM1/region/") || fileName.startsWith("DIM-1/region/"))) {
+ VFile chunkFolder = new VFile(worldDir, fileName.startsWith("DIM1") ? "level1" : (fileName.startsWith("DIM-1") ? "level-1" : "level0"));
+ RegionFile mca = new RegionFile(new RandomAccessMemoryFile(b, b.length));
+ for(int j = 0; j < 32; ++j) {
+ for(int k = 0; k < 32; ++k) {
+ if(mca.isChunkSaved(j, k)) {
+ NBTTagCompound chunkNBT;
+ NBTTagCompound chunkLevel;
+ try {
+ chunkNBT = CompressedStreamTools.read(mca.getChunkDataInputStream(j, k));
+ if(!chunkNBT.hasKey("Level")) {
+ throw new IOException("Chunk is missing level data!");
+ }
+ chunkLevel = chunkNBT.getCompoundTag("Level");
+ }catch(Throwable t) {
+ System.err.println("Could not read chunk: " + j + ", " + k);
+ t.printStackTrace();
+ continue;
+ }
+ int chunkX = chunkLevel.getInteger("xPos");
+ int chunkZ = chunkLevel.getInteger("zPos");
+ VFile chunkOut = new VFile(chunkFolder, VFSChunkLoader.getChunkPath(chunkX, chunkZ) + ".dat");
+ if(chunkOut.exists()) {
+ System.err.println("Chunk already exists: " + chunkOut.getPath());
+ continue;
+ }
+ ByteArrayOutputStream bao = new ByteArrayOutputStream();
+ CompressedStreamTools.writeCompressed(chunkNBT, bao);
+ b = bao.toByteArray();
+ chunkOut.setAllBytes(b);
+ prog += b.length;
+ if (prog - lastProgUpdate > 25000) {
+ lastProgUpdate = prog;
+ IntegratedServer.updateStatusString("selectWorld.progress.importing.1", prog);
+ }
+ }
+ }
+ }
+ } else if (fileName.startsWith("data/") || fileName.startsWith("players/")) {
+ VFile ff = new VFile(worldDir, fileName);
+ ff.setAllBytes(b);
+ prog += b.length;
+ }
+ }
+ }
+ String[] worldsTxt = SYS.VFS.getFile("worlds.txt").getAllLines();
+ if(worldsTxt == null || worldsTxt.length <= 0 || (worldsTxt.length == 1 && worldsTxt[0].trim().length() <= 0)) {
+ worldsTxt = new String[] { folderName };
+ }else {
+ String[] tmp = worldsTxt;
+ worldsTxt = new String[worldsTxt.length + 1];
+ System.arraycopy(tmp, 0, worldsTxt, 0, tmp.length);
+ worldsTxt[worldsTxt.length - 1] = folderName;
+ }
+ SYS.VFS.getFile("worlds.txt").setAllChars(String.join("\n", worldsTxt));
+ }
+
+ public static byte[] exportWorld(String folderName) throws IOException {
+ ByteArrayOutputStream bao = new ByteArrayOutputStream();
+ VFile worldFolder;
+ try(ZipOutputStream zos = new ZipOutputStream(bao)) {
+ zos.setComment("contains backup of world '" + folderName + "'");
+ worldFolder = new VFile("worlds", folderName);
+ VFile vf = new VFile(worldFolder, "level.dat");
+ byte[] b;
+ int lastProgUpdate = 0;
+ int prog = 0;
+ boolean safe = false;
+ if(vf.exists()) {
+ zos.putNextEntry(new ZipEntry(folderName + "/level.dat"));
+ b = vf.getAllBytes();
+ zos.write(b);
+ prog += b.length;
+ safe = true;
+ }
+ vf = new VFile(worldFolder, "level.dat_old");
+ if(vf.exists()) {
+ zos.putNextEntry(new ZipEntry(folderName + "/level.dat_old"));
+ b = vf.getAllBytes();
+ zos.write(b);
+ prog += b.length;
+ safe = true;
+ }
+ if (prog - lastProgUpdate > 25000) {
+ lastProgUpdate = prog;
+ IntegratedServer.updateStatusString("selectWorld.progress.exporting.2", prog);
+ }
+ String[] srcFolderNames = new String[] { "level0", "level-1", "level1" };
+ String[] dstFolderNames = new String[] { "/region/", "/DIM-1/region/", "/DIM1/region/" };
+ List fileList;
+ for(int i = 0; i < 3; ++i) {
+ vf = new VFile(worldFolder, srcFolderNames[i]);
+ fileList = SYS.VFS.listVFiles(vf.getPath());
+ String regionFolder = folderName + dstFolderNames[i];
+ Map regionFiles = new HashMap<>();
+ for(int k = 0, l = fileList.size(); k < l; ++k) {
+ VFile chunkFile = fileList.get(k);
+ NBTTagCompound chunkNBT;
+ NBTTagCompound chunkLevel;
+ try {
+ b = chunkFile.getAllBytes();
+ chunkNBT = CompressedStreamTools.readCompressed(new ByteArrayInputStream(b));
+ if(!chunkNBT.hasKey("Level")) {
+ throw new IOException("Chunk is missing level data!");
+ }
+ chunkLevel = chunkNBT.getCompoundTag("Level");
+ }catch(IOException t) {
+ System.err.println("Could not read chunk: " + chunkFile.getPath());
+ t.printStackTrace();
+ continue;
+ }
+ int chunkX = chunkLevel.getInteger("xPos");
+ int chunkZ = chunkLevel.getInteger("zPos");
+ String regionFileName = "r." + (chunkX >> 5) + "." + (chunkZ >> 5) + ".mca";
+ RegionFile rf = regionFiles.get(regionFileName);
+ if(rf == null) {
+ rf = new RegionFile(new RandomAccessMemoryFile(new byte[65536], 0));
+ regionFiles.put(regionFileName, rf);
+ }
+ try(DataOutputStream dos = rf.getChunkDataOutputStream(chunkX & 31, chunkZ & 31)) {
+ CompressedStreamTools.write(chunkNBT, dos);
+ }catch(IOException t) {
+ System.err.println("Could not write chunk to " + regionFileName + ": " + chunkFile.getPath());
+ t.printStackTrace();
+ continue;
+ }
+ prog += b.length;
+ if (prog - lastProgUpdate > 25000) {
+ lastProgUpdate = prog;
+ IntegratedServer.updateStatusString("selectWorld.progress.exporting.2", prog);
+ }
+ }
+ if(regionFiles.isEmpty()) {
+ System.err.println("No region files were generated");
+ continue;
+ }
+ for(Entry etr : regionFiles.entrySet()) {
+ String regionPath = regionFolder + etr.getKey();
+ zos.putNextEntry(new ZipEntry(regionPath));
+ zos.write(etr.getValue().getFile().getByteArray());
+ }
+ }
+ fileList = SYS.VFS.listVFiles((new VFile(worldFolder, "data")).getPath());
+ for(int k = 0, l = fileList.size(); k < l; ++k) {
+ VFile dataFile = fileList.get(k);
+ zos.putNextEntry(new ZipEntry(folderName + "/data/" + dataFile.getName()));
+ b = dataFile.getAllBytes();
+ zos.write(b);
+ prog += b.length;
+ if (prog - lastProgUpdate > 25000) {
+ lastProgUpdate = prog;
+ IntegratedServer.updateStatusString("selectWorld.progress.exporting.2", prog);
+ }
+ }
+ fileList = SYS.VFS.listVFiles((new VFile(worldFolder, "players")).getPath());
+ for(int k = 0, l = fileList.size(); k < l; ++k) {
+ VFile dataFile = fileList.get(k);
+ zos.putNextEntry(new ZipEntry(folderName + "/players/" + dataFile.getName()));
+ b = dataFile.getAllBytes();
+ zos.write(b);
+ prog += b.length;
+ if (prog - lastProgUpdate > 25000) {
+ lastProgUpdate = prog;
+ IntegratedServer.updateStatusString("selectWorld.progress.exporting.2", prog);
+ }
+ }
+ }
+ return bao.toByteArray();
+ }
+
+ private static byte[] inputStreamToBytesNoClose(InputStream is) throws IOException {
+ ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
+ byte[] buf = new byte[1024];
+ int i;
+ while ((i = is.read(buf)) != -1) {
+ os.write(buf, 0, i);
+ }
+ return os.toByteArray();
+ }
+
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 2c1393b..d889788 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -1,26 +1,30 @@
package net.minecraft.server;
-import java.io.File;
import java.io.IOException;
-import java.net.Proxy;
-import java.security.KeyPair;
-import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Date;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.List;
+
+import net.lax1dude.eaglercraft.sp.IntegratedServer;
+import net.lax1dude.eaglercraft.sp.SYS;
+import net.lax1dude.eaglercraft.sp.SysUtil;
+import net.lax1dude.eaglercraft.sp.VFSSaveHandler;
+import net.lax1dude.eaglercraft.sp.VFile;
+import net.lax1dude.eaglercraft.sp.WorkerListenThread;
+import net.lax1dude.eaglercraft.sp.ipc.IPCPacket0DProgressUpdate;
+import net.lax1dude.eaglercraft.sp.ipc.IPCPacket14StringList;
import net.minecraft.src.AxisAlignedBB;
import net.minecraft.src.ChunkCoordinates;
import net.minecraft.src.CommandBase;
-import net.minecraft.src.ConvertingProgressUpdate;
import net.minecraft.src.DispenserBehaviors;
import net.minecraft.src.EntityPlayer;
+import net.minecraft.src.EntityPlayerMP;
import net.minecraft.src.EnumGameType;
import net.minecraft.src.ICommandManager;
import net.minecraft.src.ICommandSender;
import net.minecraft.src.ILogAgent;
import net.minecraft.src.IProgressUpdate;
-import net.minecraft.src.ISaveFormat;
import net.minecraft.src.ISaveHandler;
import net.minecraft.src.IUpdatePlayerListBox;
import net.minecraft.src.MinecraftException;
@@ -28,1118 +32,948 @@ import net.minecraft.src.Packet;
import net.minecraft.src.Packet4UpdateTime;
import net.minecraft.src.ServerCommandManager;
import net.minecraft.src.ServerConfigurationManager;
+import net.minecraft.src.StringTranslate;
+import net.minecraft.src.StringUtils;
import net.minecraft.src.World;
import net.minecraft.src.WorldInfo;
import net.minecraft.src.WorldManager;
import net.minecraft.src.WorldServer;
import net.minecraft.src.WorldServerMulti;
import net.minecraft.src.WorldSettings;
-import net.minecraft.src.WorldType;
-
-public abstract class MinecraftServer implements ICommandSender, Runnable
-{
- /** Instance of Minecraft Server. */
- private static MinecraftServer mcServer;
- //private final ISaveFormat anvilConverterForAnvilFile;
-
- /** The PlayerUsageSnooper instance. */
- private final File anvilFile;
-
- /**
- * Collection of objects to update every tick. Type: List
- */
- private final List tickables = new ArrayList();
- private final ICommandManager commandManager;
-
- /** The server's hostname. */
- private String hostname;
-
- /** The server's port. */
- private int serverPort = -1;
-
- /** The server world instances. */
- public WorldServer[] worldServers;
-
- /** The ServerConfigurationManager instance. */
- private ServerConfigurationManager serverConfigManager;
-
- /**
- * Indicates whether the server is running or not. Set to false to initiate a shutdown.
- */
- private boolean serverRunning = true;
-
- /** Indicates to other classes that the server is safely stopped. */
- private boolean serverStopped;
-
- /** Incremented every tick. */
- private int tickCounter;
- protected Proxy serverProxy;
-
- /**
- * The task the server is currently working on(and will output on outputPercentRemaining).
- */
- public String currentTask;
-
- /** The percentage of the current task finished so far. */
- public int percentDone;
-
- /** True if the server is in online mode. */
- private boolean onlineMode;
-
- /** True if the server has animals turned on. */
- private boolean canSpawnAnimals;
- private boolean canSpawnNPCs;
-
- /** Indicates whether PvP is active on the server or not. */
- private boolean pvpEnabled;
-
- /** Determines if flight is allowed or not. */
- private boolean allowFlight;
-
- /** The server MOTD string. */
- private String motd;
-
- /** Maximum build height. */
- private int buildLimit;
- private int field_143008_E;
- private long lastSentPacketID;
- private long lastSentPacketSize;
- private long lastReceivedID;
- private long lastReceivedSize;
- public final long[] sentPacketCountArray;
- public final long[] sentPacketSizeArray;
- public final long[] receivedPacketCountArray;
- public final long[] receivedPacketSizeArray;
- public final long[] tickTimeArray;
-
- /** Stats are [dimension][tick%100] system.nanoTime is stored. */
- public long[][] timeOfLastDimensionTick;
- private KeyPair serverKeyPair;
-
- /** Username of the server owner (for integrated servers) */
- private String serverOwner;
- private String folderName;
- private String worldName;
- private boolean isDemo;
- private boolean enableBonusChest;
-
- /**
- * If true, there is no need to save chunks or stop the server, because that is already being done.
- */
- private boolean worldIsBeingDeleted;
- private String texturePack;
- private boolean serverIsRunning;
-
- /**
- * Set when warned for "Can't keep up", which triggers again after 15 seconds.
- */
- private long timeOfLastWarning;
- private String userMessage;
- private boolean startProfiling;
- private boolean isGamemodeForced;
-
- public MinecraftServer(File par1File)
- {
- this.serverProxy = Proxy.NO_PROXY;
- this.field_143008_E = 0;
- this.sentPacketCountArray = new long[100];
- this.sentPacketSizeArray = new long[100];
- this.receivedPacketCountArray = new long[100];
- this.receivedPacketSizeArray = new long[100];
- this.tickTimeArray = new long[100];
- this.texturePack = "";
- mcServer = this;
- this.anvilFile = par1File;
- this.commandManager = new ServerCommandManager();
- //this.anvilConverterForAnvilFile = new AnvilSaveConverter(par1File);
- this.registerDispenseBehaviors();
- }
-
- /**
- * Register all dispense behaviors.
- */
- private void registerDispenseBehaviors()
- {
- DispenserBehaviors.registerDispenserBehaviours();
- }
-
- /**
- * Initialises the server and starts it.
- */
- protected abstract boolean startServer() throws IOException;
-
- protected void convertMapIfNeeded(String par1Str)
- {
- if (this.getActiveAnvilConverter().isOldMapFormat(par1Str))
- {
- this.getLogAgent().logInfo("Converting map!");
- this.setUserMessage("menu.convertingLevel");
- this.getActiveAnvilConverter().convertMapFormat(par1Str, new ConvertingProgressUpdate(this));
- }
- }
-
- /**
- * Typically "menu.convertingLevel", "menu.loadingLevel" or others.
- */
- protected synchronized void setUserMessage(String par1Str)
- {
- this.userMessage = par1Str;
- }
-
- public synchronized String getUserMessage()
- {
- return this.userMessage;
- }
-
- protected void loadAllWorlds(String par1Str, String par2Str, long par3, WorldType par5WorldType, String par6Str)
- {
- this.convertMapIfNeeded(par1Str);
- this.setUserMessage("menu.loadingLevel");
- this.worldServers = new WorldServer[3];
- this.timeOfLastDimensionTick = new long[this.worldServers.length][100];
- ISaveHandler var7 = null;
- WorldInfo var9 = var7.loadWorldInfo();
- WorldSettings var8;
-
- if (var9 == null)
- {
- var8 = new WorldSettings(par3, this.getGameType(), this.canStructuresSpawn(), this.isHardcore(), par5WorldType);
- var8.func_82750_a(par6Str);
- }
- else
- {
- var8 = new WorldSettings(var9);
- }
-
- if (this.enableBonusChest)
- {
- var8.enableBonusChest();
- }
-
- for (int var10 = 0; var10 < this.worldServers.length; ++var10)
- {
- byte var11 = 0;
-
- if (var10 == 1)
- {
- var11 = -1;
- }
-
- if (var10 == 2)
- {
- var11 = 1;
- }
-
- if (var10 == 0)
- {
- this.worldServers[var10] = new WorldServer(this, var7, par2Str, var11, var8, this.getLogAgent());
-
- }
- else
- {
- this.worldServers[var10] = new WorldServerMulti(this, var7, par2Str, var11, var8, this.worldServers[0], this.getLogAgent());
- }
-
- this.worldServers[var10].addWorldAccess(new WorldManager(this, this.worldServers[var10]));
-
- if (!this.isSinglePlayer())
- {
- this.worldServers[var10].getWorldInfo().setGameType(this.getGameType());
- }
-
- this.serverConfigManager.setPlayerManager(this.worldServers);
- }
-
- this.setDifficultyForAllWorlds(this.getDifficulty());
- this.initialWorldChunkLoad();
- }
-
- protected void initialWorldChunkLoad()
- {
- boolean var1 = true;
- boolean var2 = true;
- boolean var3 = true;
- boolean var4 = true;
- int var5 = 0;
- this.setUserMessage("menu.generatingTerrain");
- byte var6 = 0;
- this.getLogAgent().logInfo("Preparing start region for level " + var6);
- WorldServer var7 = this.worldServers[var6];
- ChunkCoordinates var8 = var7.getSpawnPoint();
- long var9 = getSystemTimeMillis();
-
- for (int var11 = -192; var11 <= 192 && this.isServerRunning(); var11 += 16)
- {
- for (int var12 = -192; var12 <= 192 && this.isServerRunning(); var12 += 16)
- {
- long var13 = getSystemTimeMillis();
-
- if (var13 - var9 > 1000L)
- {
- this.outputPercentRemaining("Preparing spawn area", var5 * 100 / 625);
- var9 = var13;
- }
-
- ++var5;
- var7.theChunkProviderServer.loadChunk(var8.posX + var11 >> 4, var8.posZ + var12 >> 4);
- }
- }
-
- this.clearCurrentTask();
- }
-
- public abstract boolean canStructuresSpawn();
-
- public abstract EnumGameType getGameType();
-
- /**
- * Defaults to "1" (Easy) for the dedicated server, defaults to "2" (Normal) on the client.
- */
- public abstract int getDifficulty();
-
- /**
- * Defaults to false.
- */
- public abstract boolean isHardcore();
-
- public abstract int func_110455_j();
-
- /**
- * Used to display a percent remaining given text and the percentage.
- */
- protected void outputPercentRemaining(String par1Str, int par2)
- {
- this.currentTask = par1Str;
- this.percentDone = par2;
- this.getLogAgent().logInfo(par1Str + ": " + par2 + "%");
- }
-
- /**
- * Set current task to null and set its percentage to 0.
- */
- protected void clearCurrentTask()
- {
- this.currentTask = null;
- this.percentDone = 0;
- }
-
- /**
- * par1 indicates if a log message should be output.
- */
- protected void saveAllWorlds(boolean par1)
- {
- if (!this.worldIsBeingDeleted)
- {
- WorldServer[] var2 = this.worldServers;
- int var3 = var2.length;
-
- for (int var4 = 0; var4 < var3; ++var4)
- {
- WorldServer var5 = var2[var4];
-
- if (var5 != null)
- {
- if (!par1)
- {
- this.getLogAgent().logInfo("Saving chunks for level \'" + var5.getWorldInfo().getWorldName() + "\'/" + var5.provider.getDimensionName());
- }
-
- try
- {
- var5.saveAllChunks(true, (IProgressUpdate)null);
- }
- catch (MinecraftException var7)
- {
- this.getLogAgent().logWarning(var7.getMessage());
- }
- }
- }
- }
- }
-
- /**
- * Saves all necessary data as preparation for stopping the server.
- */
- public void stopServer()
- {
- if (!this.worldIsBeingDeleted)
- {
- this.getLogAgent().logInfo("Stopping server");
-
- if (this.serverConfigManager != null)
- {
- this.getLogAgent().logInfo("Saving players");
- this.serverConfigManager.saveAllPlayerData();
- this.serverConfigManager.removeAllPlayers();
- }
-
- this.getLogAgent().logInfo("Saving worlds");
- this.saveAllWorlds(false);
-
- for (int var1 = 0; var1 < this.worldServers.length; ++var1)
- {
- WorldServer var2 = this.worldServers[var1];
- var2.flush();
- }
- }
- }
-
- /**
- * "getHostname" is already taken, but both return the hostname.
- */
- public String getServerHostname()
- {
- return this.hostname;
- }
-
- public void setHostname(String par1Str)
- {
- this.hostname = par1Str;
- }
-
- public boolean isServerRunning()
- {
- return this.serverRunning;
- }
-
- /**
- * Sets the serverRunning variable to false, in order to get the server to shut down.
- */
- public void initiateShutdown()
- {
- this.serverRunning = false;
- }
-
- public void run()
- {
- try
- {
- if (this.startServer())
- {
- long var1 = getSystemTimeMillis();
-
- for (long var50 = 0L; this.serverRunning; this.serverIsRunning = true)
- {
- long var5 = getSystemTimeMillis();
- long var7 = var5 - var1;
-
- if (var7 > 2000L && var1 - this.timeOfLastWarning >= 15000L)
- {
- this.getLogAgent().logWarning("Can\'t keep up! Did the system time change, or is the server overloaded?");
- var7 = 2000L;
- this.timeOfLastWarning = var1;
- }
-
- if (var7 < 0L)
- {
- this.getLogAgent().logWarning("Time ran backwards! Did the system time change?");
- var7 = 0L;
- }
-
- var50 += var7;
- var1 = var5;
-
- if (this.worldServers[0].areAllPlayersAsleep())
- {
- this.tick();
- var50 = 0L;
- }
- else
- {
- while (var50 > 50L)
- {
- var50 -= 50L;
- this.tick();
- }
- }
-
- Thread.sleep(1L);
- }
- }
- }
- catch (Throwable var48)
- {
- var48.printStackTrace();
- this.getLogAgent().logSevereException("Encountered an unexpected exception " + var48.getClass().getSimpleName(), var48);
-
- File var3 = new File(new File(this.getDataDirectory(), "crash-reports"), "crash-" + (new SimpleDateFormat("yyyy-MM-dd_HH.mm.ss")).format(new Date()) + "-server.txt");
- }
- finally
- {
- try
- {
- this.stopServer();
- this.serverStopped = true;
- }
- catch (Throwable var46)
- {
- var46.printStackTrace();
- }
- finally
- {
- this.systemExitNow();
- }
- }
- }
-
- protected File getDataDirectory()
- {
- return new File(".");
- }
-
- /**
- * Directly calls System.exit(0), instantly killing the program.
- */
- protected void systemExitNow() {}
-
- /**
- * Main function called by run() every loop.
- */
- public void tick()
- {
- long var1 = System.nanoTime();
- AxisAlignedBB.getAABBPool().cleanPool();
- ++this.tickCounter;
-
- this.updateTimeLightAndEntities();
-
- if (this.tickCounter % 900 == 0)
- {
- this.serverConfigManager.saveAllPlayerData();
- this.saveAllWorlds(true);
- }
-
- this.tickTimeArray[this.tickCounter % 100] = System.nanoTime() - var1;
- this.sentPacketCountArray[this.tickCounter % 100] = Packet.sentID - this.lastSentPacketID;
- this.lastSentPacketID = Packet.sentID;
- this.sentPacketSizeArray[this.tickCounter % 100] = Packet.sentSize - this.lastSentPacketSize;
- this.lastSentPacketSize = Packet.sentSize;
- this.receivedPacketCountArray[this.tickCounter % 100] = Packet.receivedID - this.lastReceivedID;
- this.lastReceivedID = Packet.receivedID;
- this.receivedPacketSizeArray[this.tickCounter % 100] = Packet.receivedSize - this.lastReceivedSize;
- this.lastReceivedSize = Packet.receivedSize;
- }
-
- public void updateTimeLightAndEntities()
- {
- int var1;
-
- for (var1 = 0; var1 < this.worldServers.length; ++var1)
- {
- long var2 = System.nanoTime();
-
- if (var1 == 0 || this.getAllowNether())
- {
- WorldServer var4 = this.worldServers[var1];
- var4.getWorldVec3Pool().clear();
-
- if (this.tickCounter % 20 == 0)
- {
- this.serverConfigManager.sendPacketToAllPlayersInDimension(new Packet4UpdateTime(var4.getTotalWorldTime(), var4.getWorldTime(), var4.getGameRules().getGameRuleBooleanValue("doDaylightCycle")), var4.provider.dimensionId);
- }
-
- var4.tick();
- var4.updateEntities();
- var4.getEntityTracker().updateTrackedEntities();
- }
-
- this.timeOfLastDimensionTick[var1][this.tickCounter % 100] = System.nanoTime() - var2;
- }
-
- this.serverConfigManager.sendPlayerInfoToAllPlayers();
-
- for (var1 = 0; var1 < this.tickables.size(); ++var1)
- {
- ((IUpdatePlayerListBox)this.tickables.get(var1)).update();
- }
- }
-
- public boolean getAllowNether()
- {
- return true;
- }
-
- /**
- * Returns a File object from the specified string.
- */
- public File getFile(String par1Str)
- {
- return new File(this.getDataDirectory(), par1Str);
- }
-
- /**
- * Logs the message with a level of INFO.
- */
- public void logInfo(String par1Str)
- {
- this.getLogAgent().logInfo(par1Str);
- }
-
- /**
- * Logs the message with a level of WARN.
- */
- public void logWarning(String par1Str)
- {
- this.getLogAgent().logWarning(par1Str);
- }
-
- /**
- * Gets the worldServer by the given dimension.
- */
- public WorldServer worldServerForDimension(int par1)
- {
- return par1 == -1 ? this.worldServers[1] : (par1 == 1 ? this.worldServers[2] : this.worldServers[0]);
- }
-
- /**
- * Returns the server's hostname.
- */
- public String getHostname()
- {
- return this.hostname;
- }
-
- /**
- * Never used, but "getServerPort" is already taken.
- */
- public int getPort()
- {
- return this.serverPort;
- }
-
- /**
- * Returns the server message of the day
- */
- public String getServerMOTD()
- {
- return this.motd;
- }
-
- /**
- * Returns the server's Minecraft version as string.
- */
- public String getMinecraftVersion()
- {
- return "1.6.4";
- }
-
- /**
- * Returns the number of players currently on the server.
- */
- public int getCurrentPlayerCount()
- {
- return this.serverConfigManager.getCurrentPlayerCount();
- }
-
- /**
- * Returns the maximum number of players allowed on the server.
- */
- public int getMaxPlayers()
- {
- return this.serverConfigManager.getMaxPlayers();
- }
-
- /**
- * Returns an array of the usernames of all the connected players.
- */
- public String[] getAllUsernames()
- {
- return this.serverConfigManager.getAllUsernames();
- }
-
- /**
- * Used by RCon's Query in the form of "MajorServerMod 1.2.3: MyPlugin 1.3; AnotherPlugin 2.1; AndSoForth 1.0".
- */
- public String getPlugins()
- {
- return "";
- }
-
- /**
- * Returns true if debugging is enabled, false otherwise.
- */
- public boolean isDebuggingEnabled()
- {
- return false;
- }
-
- /**
- * Logs the error message with a level of SEVERE.
- */
- public void logSevere(String par1Str)
- {
- this.getLogAgent().logSevere(par1Str);
- }
-
- /**
- * If isDebuggingEnabled(), logs the message with a level of INFO.
- */
- public void logDebug(String par1Str)
- {
- if (this.isDebuggingEnabled())
- {
- this.getLogAgent().logInfo(par1Str);
- }
- }
-
- public String getServerModName()
- {
- return "vanilla";
- }
-
- /**
- * If par2Str begins with /, then it searches for commands, otherwise it returns players.
- */
- public List getPossibleCompletions(ICommandSender par1ICommandSender, String par2Str)
- {
- ArrayList var3 = new ArrayList();
-
- if (par2Str.startsWith("/"))
- {
- par2Str = par2Str.substring(1);
- boolean var10 = !par2Str.contains(" ");
- List var11 = this.commandManager.getPossibleCommands(par1ICommandSender, par2Str);
-
- if (var11 != null)
- {
- Iterator var12 = var11.iterator();
-
- while (var12.hasNext())
- {
- String var13 = (String)var12.next();
-
- if (var10)
- {
- var3.add("/" + var13);
- }
- else
- {
- var3.add(var13);
- }
- }
- }
-
- return var3;
- }
- else
- {
- String[] var4 = par2Str.split(" ", -1);
- String var5 = var4[var4.length - 1];
- String[] var6 = this.serverConfigManager.getAllUsernames();
- int var7 = var6.length;
-
- for (int var8 = 0; var8 < var7; ++var8)
- {
- String var9 = var6[var8];
-
- if (CommandBase.doesStringStartWith(var5, var9))
- {
- var3.add(var9);
- }
- }
-
- return var3;
- }
- }
-
- /**
- * Gets mcServer.
- */
- public static MinecraftServer getServer()
- {
- return mcServer;
- }
-
- /**
- * Gets the name of this command sender (usually username, but possibly "Rcon")
- */
- public String getCommandSenderName()
- {
- return "Server";
- }
-
- public void sendChatToPlayer(String par1ChatMessageComponent)
- {
- this.getLogAgent().logInfo(par1ChatMessageComponent);
- }
-
- /**
- * Returns true if the command sender is allowed to use the given command.
- */
- public boolean canCommandSenderUseCommand(int par1, String par2Str)
- {
- return true;
- }
-
- public ICommandManager getCommandManager()
- {
- return this.commandManager;
- }
-
- /**
- * Gets KeyPair instanced in MinecraftServer.
- */
- public KeyPair getKeyPair()
- {
- return this.serverKeyPair;
- }
-
- /**
- * Gets serverPort.
- */
- public int getServerPort()
- {
- return this.serverPort;
- }
-
- public void setServerPort(int par1)
- {
- this.serverPort = par1;
- }
-
- /**
- * Returns the username of the server owner (for integrated servers)
- */
- public String getServerOwner()
- {
- return this.serverOwner;
- }
-
- /**
- * Sets the username of the owner of this server (in the case of an integrated server)
- */
- public void setServerOwner(String par1Str)
- {
- this.serverOwner = par1Str;
- }
-
- public boolean isSinglePlayer()
- {
- return this.serverOwner != null;
- }
-
- public String getFolderName()
- {
- return this.folderName;
- }
-
- public void setFolderName(String par1Str)
- {
- this.folderName = par1Str;
- }
-
- public void setWorldName(String par1Str)
- {
- this.worldName = par1Str;
- }
-
- public String getWorldName()
- {
- return this.worldName;
- }
-
- public void setKeyPair(KeyPair par1KeyPair)
- {
- this.serverKeyPair = par1KeyPair;
- }
-
- public void setDifficultyForAllWorlds(int par1)
- {
- for (int var2 = 0; var2 < this.worldServers.length; ++var2)
- {
- WorldServer var3 = this.worldServers[var2];
-
- if (var3 != null)
- {
- if (var3.getWorldInfo().isHardcoreModeEnabled())
- {
- var3.difficultySetting = 3;
- var3.setAllowedSpawnTypes(true, true);
- }
- else if (this.isSinglePlayer())
- {
- var3.difficultySetting = par1;
- var3.setAllowedSpawnTypes(var3.difficultySetting > 0, true);
- }
- else
- {
- var3.difficultySetting = par1;
- var3.setAllowedSpawnTypes(this.allowSpawnMonsters(), this.canSpawnAnimals);
- }
- }
- }
- }
-
- protected boolean allowSpawnMonsters()
- {
- return true;
- }
-
- /**
- * Gets whether this is a demo or not.
- */
- public boolean isDemo()
- {
- return this.isDemo;
- }
-
- /**
- * Sets whether this is a demo or not.
- */
- public void setDemo(boolean par1)
- {
- this.isDemo = par1;
- }
-
- public void canCreateBonusChest(boolean par1)
- {
- this.enableBonusChest = par1;
- }
-
- public ISaveFormat getActiveAnvilConverter()
- {
- return null;
- }
-
- /**
- * WARNING : directly calls
- * getActiveAnvilConverter().deleteWorldDirectory(theWorldServer[0].getSaveHandler().getWorldDirectoryName());
- */
- public void deleteWorldAndStopServer()
- {
- this.worldIsBeingDeleted = true;
- this.getActiveAnvilConverter().flushCache();
-
- for (int var1 = 0; var1 < this.worldServers.length; ++var1)
- {
- WorldServer var2 = this.worldServers[var1];
-
- if (var2 != null)
- {
- var2.flush();
- }
- }
-
- this.getActiveAnvilConverter().deleteWorldDirectory(this.worldServers[0].getSaveHandler().getWorldDirectoryName());
- this.initiateShutdown();
- }
-
- public String getTexturePack()
- {
- return this.texturePack;
- }
-
- public void setTexturePack(String par1Str)
- {
- this.texturePack = par1Str;
- }
-
- /**
- * This is checked to be 16 upon receiving the packet, otherwise the packet is ignored.
- */
- public int textureSize()
- {
- return 16;
- }
-
- public abstract boolean isDedicatedServer();
-
- public boolean isServerInOnlineMode()
- {
- return this.onlineMode;
- }
-
- public void setOnlineMode(boolean par1)
- {
- this.onlineMode = par1;
- }
-
- public boolean getCanSpawnAnimals()
- {
- return this.canSpawnAnimals;
- }
-
- public void setCanSpawnAnimals(boolean par1)
- {
- this.canSpawnAnimals = par1;
- }
-
- public boolean getCanSpawnNPCs()
- {
- return this.canSpawnNPCs;
- }
-
- public void setCanSpawnNPCs(boolean par1)
- {
- this.canSpawnNPCs = par1;
- }
-
- public boolean isPVPEnabled()
- {
- return this.pvpEnabled;
- }
-
- public void setAllowPvp(boolean par1)
- {
- this.pvpEnabled = par1;
- }
-
- public boolean isFlightAllowed()
- {
- return this.allowFlight;
- }
-
- public void setAllowFlight(boolean par1)
- {
- this.allowFlight = par1;
- }
-
- /**
- * Return whether command blocks are enabled.
- */
- public abstract boolean isCommandBlockEnabled();
-
- public String getMOTD()
- {
- return this.motd;
- }
-
- public void setMOTD(String par1Str)
- {
- this.motd = par1Str;
- }
-
- public int getBuildLimit()
- {
- return this.buildLimit;
- }
-
- public void setBuildLimit(int par1)
- {
- this.buildLimit = par1;
- }
-
- public boolean isServerStopped()
- {
- return this.serverStopped;
- }
-
- public ServerConfigurationManager getConfigurationManager()
- {
- return this.serverConfigManager;
- }
-
- public void setConfigurationManager(ServerConfigurationManager par1ServerConfigurationManager)
- {
- this.serverConfigManager = par1ServerConfigurationManager;
- }
-
- /**
- * Sets the game type for all worlds.
- */
- public void setGameType(EnumGameType par1EnumGameType)
- {
- for (int var2 = 0; var2 < this.worldServers.length; ++var2)
- {
- getServer().worldServers[var2].getWorldInfo().setGameType(par1EnumGameType);
- }
- }
-
-
- public boolean serverIsInRunLoop()
- {
- return this.serverIsRunning;
- }
-
- public boolean getGuiEnabled()
- {
- return false;
- }
-
- /**
- * On dedicated does nothing. On integrated, sets commandsAllowedForAll, gameType and allows external connections.
- */
- public abstract String shareToLAN(EnumGameType var1, boolean var2);
-
- public int getTickCounter()
- {
- return this.tickCounter;
- }
-
- public void enableProfiling()
- {
- this.startProfiling = true;
- }
- /**
- * Return the position for this command sender.
- */
- public ChunkCoordinates getPlayerCoordinates()
- {
- return new ChunkCoordinates(0, 0, 0);
- }
-
- public World getEntityWorld()
- {
- return this.worldServers[0];
- }
-
- /**
- * Return the spawn protection area's size.
- */
- public int getSpawnProtectionSize()
- {
- return 16;
- }
-
- /**
- * Returns true if a player does not have permission to edit the block at the given coordinates.
- */
- public boolean isBlockProtected(World par1World, int par2, int par3, int par4, EntityPlayer par5EntityPlayer)
- {
- return false;
- }
-
- public abstract ILogAgent getLogAgent();
-
- public void setForceGamemode(boolean par1)
- {
- this.isGamemodeForced = par1;
- }
-
- public boolean getForceGamemode()
- {
- return this.isGamemodeForced;
- }
-
- public Proxy getServerProxy()
- {
- return this.serverProxy;
- }
-
- /**
- * returns the difference, measured in milliseconds, between the current system time and midnight, January 1, 1970
- * UTC.
- */
- public static long getSystemTimeMillis()
- {
- return System.currentTimeMillis();
- }
-
- public int func_143007_ar()
- {
- return this.field_143008_E;
- }
-
- public void func_143006_e(int par1)
- {
- this.field_143008_E = par1;
- }
-
- /**
- * Gets the current player count, maximum player count, and player entity list.
- */
- public static ServerConfigurationManager getServerConfigurationManager(MinecraftServer par0MinecraftServer)
- {
- return par0MinecraftServer.serverConfigManager;
- }
+
+public abstract class MinecraftServer implements ICommandSender, Runnable {
+ /** Instance of Minecraft Server. */
+ protected static MinecraftServer mcServer = null;
+
+ /** List of names of players who are online. */
+ protected final List playersOnline = new ArrayList();
+ protected final ICommandManager commandManager;
+
+ /** The server world instances. */
+ public WorldServer[] worldServers;
+
+ /** The ServerConfigurationManager instance. */
+ protected ServerConfigurationManager serverConfigManager;
+
+ /**
+ * Indicates whether the server is running or not. Set to false to initiate a
+ * shutdown.
+ */
+ protected boolean serverRunning = true;
+
+ /** Indicates to other classes that the server is safely stopped. */
+ protected boolean serverStopped = false;
+
+ /** Incremented every tick. */
+ protected int tickCounter = 0;
+
+ /**
+ * The task the server is currently working on(and will output on
+ * outputPercentRemaining).
+ */
+ protected String currentTask;
+
+ /** The percentage of the current task finished so far. */
+ protected int percentDone;
+
+ /** True if the server has animals turned on. */
+ protected boolean canSpawnAnimals;
+ protected boolean canSpawnNPCs;
+
+ /** Indicates whether PvP is active on the server or not. */
+ protected boolean pvpEnabled;
+
+ /** Determines if flight is allowed or not. */
+ protected boolean allowFlight;
+
+ /** The server MOTD string. */
+ protected String motd;
+
+ /** Maximum build height. */
+ protected int buildLimit;
+ protected long lastSentPacketID;
+ protected long lastSentPacketSize;
+ protected long lastReceivedID;
+ protected long lastReceivedSize;
+ public final long[] sentPacketCountArray = new long[100];
+ public final long[] sentPacketSizeArray = new long[100];
+ public final long[] receivedPacketCountArray = new long[100];
+ public final long[] receivedPacketSizeArray = new long[100];
+ public final long[] tickTimeArray = new long[100];
+
+ /** Stats are [dimension][tick%100] system.nanoTime is stored. */
+ public long[][] timeOfLastDimensionTick;
+
+ /** Username of the server owner (for integrated servers) */
+ protected String serverOwner;
+ protected String folderName;
+
+ /**
+ * If true, there is no need to save chunks or stop the server, because that is
+ * already being done.
+ */
+ protected boolean worldIsBeingDeleted;
+ protected String texturePack = "";
+ protected boolean serverIsRunning = false;
+
+ /**
+ * Set when warned for "Can't keep up", which triggers again after 15 seconds.
+ */
+ protected long timeOfLastWarning;
+ protected String userMessage;
+ protected boolean field_104057_T = false;
+
+ public MinecraftServer(String folder) {
+ mcServer = this;
+ this.folderName = folder;
+ this.commandManager = new ServerCommandManager();
+ this.registerDispenseBehaviors();
+ }
+
+ /**
+ * Register all dispense behaviors.
+ */
+ private void registerDispenseBehaviors() {
+ DispenserBehaviors.registerDispenserBehaviours();
+ }
+
+ /**
+ * Initialises the server and starts it.
+ */
+ protected abstract boolean startServer() throws IOException;
+
+ protected void convertMapIfNeeded(String par1Str) {
+ // no
+ }
+
+ /**
+ * Typically "menu.convertingLevel", "menu.loadingLevel" or others.
+ */
+ protected void setUserMessage(String par1Str) {
+ IntegratedServer.sendIPCPacket(new IPCPacket0DProgressUpdate(par1Str, 0.0f));
+ this.logInfo(par1Str);
+ this.userMessage = par1Str;
+ }
+
+ protected void setUserMessage(String par1Str, float prog) {
+ IntegratedServer.sendIPCPacket(new IPCPacket0DProgressUpdate(par1Str, prog));
+ this.logInfo(par1Str + ": " + (prog > 1.0f ? "" + (int)prog : "" + (int)(prog * 100.0f) + "%"));
+ this.userMessage = par1Str;
+ }
+
+ protected void loadAllWorlds(String par1Str, long par3, WorldSettings par5WorldType) {
+ this.setUserMessage("menu.loadingLevel");
+ this.worldServers = new WorldServer[3];
+ this.timeOfLastDimensionTick = new long[this.worldServers.length][100];
+ ISaveHandler var7 = new VFSSaveHandler(new VFile("worlds", par1Str));
+ WorldInfo var9 = var7.loadWorldInfo();
+ WorldSettings var8;
+
+ if (var9 == null) {
+ if(par5WorldType == null) {
+ throw new IllegalArgumentException("World '" + par1Str + "' does not exist and WorldSettings is null");
+ }
+ var8 = par5WorldType;
+ } else {
+ var8 = new WorldSettings(var9);
+ }
+
+ for (int var10 = 0; var10 < this.worldServers.length; ++var10) {
+ byte var11 = 0;
+
+ if (var10 == 1) {
+ var11 = -1;
+ }
+
+ if (var10 == 2) {
+ var11 = 1;
+ }
+
+ if (var10 == 0) {
+ this.worldServers[var10] = new WorldServer(this, var7, par1Str, var11, var8, this.getLogAgent());
+ } else {
+ this.worldServers[var10] = new WorldServerMulti(this, var7, par1Str, var11, var8, this.worldServers[0], this.getLogAgent());
+ }
+
+ this.worldServers[var10].addWorldAccess(new WorldManager(this, this.worldServers[var10]));
+ this.worldServers[var10].getWorldInfo().setGameType(this.getGameType());
+
+ this.serverConfigManager.setPlayerManager(this.worldServers);
+ }
+
+ this.setDifficultyForAllWorlds(this.getDifficulty());
+ this.setGameType(var8.getGameType());
+ this.initialWorldChunkLoad();
+ }
+
+ protected void initialWorldChunkLoad() {
+ int var5 = 0;
+ //this.setUserMessage("menu.generatingTerrain");
+ byte var6 = 0;
+ this.setUserMessage("Preparing start region for level " + var6);
+
+ // Removed 'spawn chunks' for performance, they are unnecessary
+
+ /*
+ WorldServer var7 = this.worldServers[var6];
+ ChunkCoordinates var8 = var7.getSpawnPoint();
+ long var9 = System.currentTimeMillis();
+
+ int prepareRadius = 64;
+
+ for (int var11 = -prepareRadius; var11 <= prepareRadius && this.isServerRunning(); var11 += 16) {
+ for (int var12 = -prepareRadius; var12 <= prepareRadius && this.isServerRunning(); var12 += 16) {
+ long var13 = System.currentTimeMillis();
+
+ if (var13 - var9 > 1000L) {
+ setUserMessage("Preparing spawn area", Math.min(var5 / 64.0f, 0.99f));
+ var9 = var13;
+ }
+
+ ++var5;
+ var7.theChunkProviderServer.loadChunk(var8.posX + var11 >> 4, var8.posZ + var12 >> 4);
+ }
+ }
+ */
+
+ this.clearCurrentTask();
+ }
+
+ public abstract boolean canStructuresSpawn();
+
+ public abstract EnumGameType getGameType();
+
+ /**
+ * Defaults to "1" (Easy) for the dedicated server, defaults to "2" (Normal) on
+ * the client.
+ */
+ public abstract int getDifficulty();
+
+ /**
+ * Defaults to false.
+ */
+ public abstract boolean isHardcore();
+
+ /**
+ * Used to display a percent remaining given text and the percentage.
+ */
+ protected void outputPercentRemaining(String par1Str, int par2) {
+ this.currentTask = par1Str;
+ this.percentDone = par2;
+ setUserMessage(par1Str, (par2 / 100.0f));
+ }
+
+ /**
+ * Set current task to null and set its percentage to 0.
+ */
+ protected void clearCurrentTask() {
+ this.currentTask = null;
+ this.percentDone = 0;
+ }
+
+ /**
+ * par1 indicates if a log message should be output.
+ */
+ public void saveAllWorlds(boolean par1) {
+ if (!this.worldIsBeingDeleted) {
+ WorldServer[] var2 = this.worldServers;
+ int var3 = var2.length;
+
+ for (int var4 = 0; var4 < var3; ++var4) {
+ WorldServer var5 = var2[var4];
+
+ if (var5 != null) {
+ setUserMessage("Saving chunks for level \'" + var5.getWorldInfo().getWorldName() + "\'/" + var5.provider.getDimensionName());
+
+ try {
+ var5.saveAllChunks(true, (IProgressUpdate) null);
+ } catch (MinecraftException var7) {
+ this.getLogAgent().logWarning(var7.getMessage());
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Saves all necessary data as preparation for stopping the server.
+ */
+ public void stopServer() {
+ if (!this.worldIsBeingDeleted) {
+ setUserMessage("Stopping server");
+
+ if (this.getNetworkThread() != null) {
+ this.getNetworkThread().stopListening();
+ }
+
+ if (this.serverConfigManager != null) {
+ this.getLogAgent().logInfo("Saving players");
+ this.serverConfigManager.saveAllPlayerData();
+ this.serverConfigManager.removeAllPlayers();
+ }
+
+ setUserMessage("Saving worlds");
+ this.saveAllWorlds(false);
+
+ for (int var1 = 0; var1 < this.worldServers.length; ++var1) {
+ WorldServer var2 = this.worldServers[var1];
+ var2.flush();
+ }
+ }
+ }
+
+ /**
+ * "getHostname" is already taken, but both return the hostname.
+ */
+ public String getServerHostname() {
+ return "127.1.1.1";
+ }
+
+ public void setHostname(String par1Str) {
+ throw new IllegalArgumentException("variable removed");
+ }
+
+ public boolean isServerRunning() {
+ return this.serverRunning;
+ }
+
+ /**
+ * Sets the serverRunning variable to false, in order to get the server to shut
+ * down.
+ */
+ public void initiateShutdown() {
+ this.serverRunning = false;
+ }
+
+ public void run() {
+ try {
+ if (this.startServer()) {
+ long var1 = SysUtil.steadyTimeMillis();
+
+ for (long var50 = 0L; this.serverRunning; this.serverIsRunning = true) {
+ long var5 = SysUtil.steadyTimeMillis();
+ long var7 = var5 - var1;
+
+ if (var7 > 2000L && var1 - this.timeOfLastWarning >= 15000L) {
+ this.getLogAgent().logWarning(
+ "Can\'t keep up! Did the system time change, or is the server overloaded?");
+ var7 = 2000L;
+ this.timeOfLastWarning = var1;
+ }
+
+ if (var7 < 0L) {
+ this.getLogAgent().logWarning("Time ran backwards! Did the system time change?");
+ var7 = 0L;
+ }
+
+ var50 += var7;
+ var1 = var5;
+
+ if (this.worldServers[0].areAllPlayersAsleep()) {
+ this.tick();
+ var50 = 0L;
+ } else {
+ while (var50 > 50L) {
+ var50 -= 50L;
+ this.tick();
+ }
+ }
+
+ SysUtil.sleep(1);
+ }
+ } else {
+ throw new RuntimeException("Server did not init correctly");
+ }
+ } catch (Throwable var48) {
+ this.getLogAgent().logSevereException(
+ "Encountered an unexpected exception " + var48.getClass().getSimpleName(), var48);
+ var48.printStackTrace();
+ IntegratedServer.throwExceptionToClient("Encountered an unexpected exception", var48);
+ } finally {
+ try {
+ this.stopServer();
+ this.serverStopped = true;
+ } catch (Throwable var46) {
+ var46.printStackTrace();
+ } finally {
+ this.systemExitNow();
+ }
+ }
+ }
+
+ protected VFile getDataDirectory() {
+ return new VFile(".");
+ }
+
+ /**
+ * Directly calls System.exit(0), instantly killing the program.
+ */
+ protected void systemExitNow() {
+ }
+
+ /**
+ * Main function called by run() every loop.
+ */
+ protected void tick() {
+ long var1 = System.nanoTime();
+ AxisAlignedBB.getAABBPool().cleanPool();
+ ++this.tickCounter;
+
+ this.updateTimeLightAndEntities();
+
+ if (this.tickCounter % 900 == 0) {
+ this.serverConfigManager.saveAllPlayerData();
+ this.saveAllWorlds(true);
+ }
+
+ this.tickTimeArray[this.tickCounter % 100] = System.nanoTime() - var1;
+ this.sentPacketCountArray[this.tickCounter % 100] = Packet.sentID - this.lastSentPacketID;
+ this.lastSentPacketID = Packet.sentID;
+ this.sentPacketSizeArray[this.tickCounter % 100] = Packet.sentSize - this.lastSentPacketSize;
+ this.lastSentPacketSize = Packet.sentSize;
+ this.receivedPacketCountArray[this.tickCounter % 100] = Packet.receivedID - this.lastReceivedID;
+ this.lastReceivedID = Packet.receivedID;
+ this.receivedPacketSizeArray[this.tickCounter % 100] = Packet.receivedSize - this.lastReceivedSize;
+ this.lastReceivedSize = Packet.receivedSize;
+ }
+
+ public List getTPSAndChunkBuffer(int tpsCounter) {
+ ArrayList strs = new ArrayList();
+ strs.add("Ticks/Second: " + tpsCounter + "/20");
+
+ int c = 0;
+ int oc = 0;
+ int e = 0;
+ int te = 0;
+ int r = 0;
+ int w = 0;
+ int g = 0;
+ int tu = 0;
+ int lu = 0;
+ for(int i = 0; i < worldServers.length; ++i) {
+ c += worldServers[i].getChunkProvider().getLoadedChunkCount();
+ e += worldServers[i].loadedEntityList.size();
+ te += worldServers[i].loadedTileEntityList.size();
+ r += worldServers[i].getR();
+ w += worldServers[i].getW();
+ g += worldServers[i].getG();
+ lu += worldServers[i].getLU();
+ tu += worldServers[i].getTU();
+ }
+ for(EntityPlayerMP p : (List)this.playersOnline) {
+ oc += p.loadedChunks.size();
+ }
+
+ strs.add("Chunks: " + c + "/" + (c + oc));
+ strs.add("Entities: " + e + "+" + te);
+ strs.add("R: " + r + ", G: " + g + ", W: " + w);
+ strs.add("TU: " + tu + " LU: " + lu);
+ int pp = this.playersOnline.size();
+ if(pp > 1) {
+ strs.add("Players: " + pp);
+ }
+ return strs;
+ }
+
+ public void updateTimeLightAndEntities() {
+ int var1;
+
+ for (var1 = 0; var1 < this.worldServers.length; ++var1) {
+ long var2 = System.nanoTime();
+
+ if (var1 == 0 || this.getAllowNether()) {
+ WorldServer var4 = this.worldServers[var1];
+ var4.getWorldVec3Pool().clear();
+
+ if (this.tickCounter % 20 == 0) {
+ this.serverConfigManager.sendPacketToAllPlayersInDimension(
+ new Packet4UpdateTime(var4.getTotalWorldTime(), var4.getWorldTime(), this.worldServers[var1].getGameRules().getGameRuleBooleanValue("doDaylightCycle")),
+ var4.provider.dimensionId);
+ }
+
+ var4.tick();
+ var4.updateEntities();
+
+ var4.getEntityTracker().updateTrackedEntities();
+ }
+
+ this.timeOfLastDimensionTick[var1][this.tickCounter % 100] = System.nanoTime() - var2;
+ }
+
+ this.getNetworkThread().handleNetworkListenThread();
+ this.serverConfigManager.sendPlayerInfoToAllPlayers();
+
+ for (var1 = 0; var1 < this.playersOnline.size(); ++var1) {
+ ((IUpdatePlayerListBox) this.playersOnline.get(var1)).update();
+ }
+ }
+
+ public boolean getAllowNether() {
+ return true;
+ }
+
+ public void func_82010_a(IUpdatePlayerListBox par1IUpdatePlayerListBox) {
+ this.playersOnline.add(par1IUpdatePlayerListBox);
+ }
+
+ /**
+ * Returns a File object from the specified string.
+ */
+ public VFile getFile(String par1Str) {
+ return new VFile(folderName, par1Str);
+ }
+
+ /**
+ * Logs the message with a level of INFO.
+ */
+ public void logInfo(String par1Str) {
+ this.getLogAgent().logInfo(par1Str);
+ }
+
+ /**
+ * Logs the message with a level of WARN.
+ */
+ public void logWarning(String par1Str) {
+ this.getLogAgent().logWarning(par1Str);
+ }
+
+ /**
+ * Gets the worldServer by the given dimension.
+ */
+ public WorldServer worldServerForDimension(int par1) {
+ return par1 == -1 ? this.worldServers[1] : (par1 == 1 ? this.worldServers[2] : this.worldServers[0]);
+ }
+
+ /**
+ * Returns the server's hostname.
+ */
+ public String getHostname() {
+ return this.getServerHostname();
+ }
+
+ /**
+ * Never used, but "getServerPort" is already taken.
+ */
+ public int getPort() {
+ return this.getServerPort();
+ }
+
+ /**
+ * Returns the server message of the day
+ */
+ public String getMotd() {
+ return this.motd;
+ }
+
+ /**
+ * Returns the server's Minecraft version as string.
+ */
+ public String getMinecraftVersion() {
+ return "1.5.2";
+ }
+
+ /**
+ * Returns the number of players currently on the server.
+ */
+ public int getCurrentPlayerCount() {
+ return this.serverConfigManager.getCurrentPlayerCount();
+ }
+
+ /**
+ * Returns the maximum number of players allowed on the server.
+ */
+ public int getMaxPlayers() {
+ return this.serverConfigManager.getMaxPlayers();
+ }
+
+ /**
+ * Returns an array of the usernames of all the connected players.
+ */
+ public String[] getAllUsernames() {
+ return this.serverConfigManager.getAllUsernames();
+ }
+
+ /**
+ * Used by RCon's Query in the form of "MajorServerMod 1.2.3: MyPlugin 1.3;
+ * AnotherPlugin 2.1; AndSoForth 1.0".
+ */
+ public String getPlugins() {
+ return "";
+ }
+
+ /**
+ * Handle a command received by an RCon instance
+ */
+ public String handleRConCommand(String par1Str) {
+ return "fuck off";
+ }
+
+ /**
+ * Returns true if debugging is enabled, false otherwise.
+ */
+ public boolean isDebuggingEnabled() {
+ return true;
+ }
+
+ /**
+ * Logs the error message with a level of SEVERE.
+ */
+ public void logSevere(String par1Str) {
+ this.getLogAgent().logSevere(par1Str);
+ }
+
+ /**
+ * If isDebuggingEnabled(), logs the message with a level of INFO.
+ */
+ public void logDebug(String par1Str) {
+ if (this.isDebuggingEnabled()) {
+ this.getLogAgent().logInfo(par1Str);
+ }
+ }
+
+ public String getServerModName() {
+ return "eaglercraft";
+ }
+
+ /**
+ * If par2Str begins with /, then it searches for commands, otherwise it returns
+ * players.
+ */
+ public List getPossibleCompletions(ICommandSender par1ICommandSender, String par2Str) {
+ ArrayList var3 = new ArrayList();
+
+ if (par2Str.startsWith("/")) {
+ par2Str = par2Str.substring(1);
+ boolean var10 = !par2Str.contains(" ");
+ List var11 = this.commandManager.getPossibleCommands(par1ICommandSender, par2Str);
+
+ if (var11 != null) {
+ Iterator var12 = var11.iterator();
+
+ while (var12.hasNext()) {
+ String var13 = (String) var12.next();
+
+ if (var10) {
+ var3.add("/" + var13);
+ } else {
+ var3.add(var13);
+ }
+ }
+ }
+
+ return var3;
+ } else {
+ String[] var4 = par2Str.split(" ", -1);
+ String var5 = var4[var4.length - 1];
+ String[] var6 = this.serverConfigManager.getAllUsernames();
+ int var7 = var6.length;
+
+ for (int var8 = 0; var8 < var7; ++var8) {
+ String var9 = var6[var8];
+
+ if (CommandBase.doesStringStartWith(var5, var9)) {
+ var3.add(var9);
+ }
+ }
+
+ return var3;
+ }
+ }
+
+ /**
+ * Gets mcServer.
+ */
+ public static MinecraftServer getServer() {
+ return mcServer;
+ }
+
+ /**
+ * Gets the name of this command sender (usually username, but possibly "Rcon")
+ */
+ public String getCommandSenderName() {
+ return "Host";
+ }
+
+ public void sendChatToPlayer(String par1Str) {
+ this.getLogAgent().logInfo(StringUtils.stripControlCodes(par1Str));
+ }
+
+ /**
+ * Returns true if the command sender is allowed to use the given command.
+ */
+ public boolean canCommandSenderUseCommand(int par1, String par2Str) {
+ return par2Str.equals(this.getServerOwner());
+ }
+
+ /**
+ * Translates and formats the given string key with the given arguments.
+ */
+ public String translateString(String par1Str, Object... par2ArrayOfObj) {
+ return StringTranslate.getInstance().translateKeyFormat(par1Str, par2ArrayOfObj);
+ }
+
+ public ICommandManager getCommandManager() {
+ return this.commandManager;
+ }
+
+ /**
+ * Gets serverPort.
+ */
+ public int getServerPort() {
+ return 1;
+ }
+
+ public void setServerPort(int par1) {
+ throw new IllegalArgumentException("variable removed");
+ }
+
+ /**
+ * Returns the username of the server owner (for integrated servers)
+ */
+ public String getServerOwner() {
+ return this.serverOwner;
+ }
+
+ /**
+ * Sets the username of the owner of this server (in the case of an integrated
+ * server)
+ */
+ public void setServerOwner(String par1Str) {
+ this.serverOwner = par1Str;
+ }
+
+ public boolean isSinglePlayer() {
+ return this.serverOwner != null;
+ }
+
+ public String getFolderName() {
+ return this.folderName;
+ }
+
+ public void setFolderName(String par1Str) {
+ this.folderName = par1Str;
+ }
+
+ public void setDifficultyForAllWorlds(int par1) {
+ for (int var2 = 0; var2 < this.worldServers.length; ++var2) {
+ WorldServer var3 = this.worldServers[var2];
+
+ if (var3 != null) {
+ if (var3.getWorldInfo().isHardcoreModeEnabled()) {
+ var3.difficultySetting = 3;
+ var3.setAllowedSpawnTypes(true, true);
+ } else if (this.isSinglePlayer()) {
+ var3.difficultySetting = par1;
+ var3.setAllowedSpawnTypes(var3.difficultySetting > 0, true);
+ } else {
+ var3.difficultySetting = par1;
+ var3.setAllowedSpawnTypes(this.allowSpawnMonsters(), this.canSpawnAnimals);
+ }
+ }
+ }
+ }
+
+ protected boolean allowSpawnMonsters() {
+ return true;
+ }
+
+ /**
+ * Gets whether this is a demo or not.
+ */
+ public boolean isDemo() {
+ return false;
+ }
+
+ /**
+ * Sets whether this is a demo or not.
+ */
+ public void setDemo(boolean par1) {
+ throw new IllegalArgumentException("variable removed");
+ }
+
+ public void canCreateBonusChest(boolean par1) {
+ throw new IllegalArgumentException("variable removed");
+ }
+
+ /**
+ * WARNING : directly calls
+ * getActiveAnvilConverter().deleteWorldDirectory(theWorldServer[0].getSaveHandler().getWorldDirectoryName());
+ */
+ public void deleteWorldAndStopServer() {
+ this.worldIsBeingDeleted = true;
+
+ for (int var1 = 0; var1 < this.worldServers.length; ++var1) {
+ WorldServer var2 = this.worldServers[var1];
+
+ if (var2 != null) {
+ var2.flush();
+ }
+ }
+
+ String dir = this.worldServers[0].getSaveHandler().getWorldDirectoryName();
+ SYS.VFS.deleteFiles(dir);
+ String[] worldsTxt = SYS.VFS.getFile("worlds.txt").getAllLines();
+ if(worldsTxt != null) {
+ LinkedList newWorlds = new LinkedList();
+ for(String str : worldsTxt) {
+ if(!str.equalsIgnoreCase(dir)) {
+ newWorlds.add(str);
+ }
+ }
+ SYS.VFS.getFile("worlds.txt").setAllChars(String.join("\n", newWorlds));
+ }
+
+ this.initiateShutdown();
+ }
+
+ public String getTexturePack() {
+ return null;
+ }
+
+ public void setTexturePack(String par1Str) {
+ throw new IllegalArgumentException("variable removed");
+ }
+
+ /**
+ * This is checked to be 16 upon receiving the packet, otherwise the packet is
+ * ignored.
+ */
+ public int textureSize() {
+ return 16;
+ }
+
+ public abstract boolean isDedicatedServer();
+
+ public boolean isServerInOnlineMode() {
+ return false;
+ }
+
+ public void setOnlineMode(boolean par1) {
+ throw new IllegalArgumentException("variable removed");
+ }
+
+ public boolean getCanSpawnAnimals() {
+ return this.canSpawnAnimals;
+ }
+
+ public void setCanSpawnAnimals(boolean par1) {
+ this.canSpawnAnimals = par1;
+ }
+
+ public boolean getCanSpawnNPCs() {
+ return this.canSpawnNPCs;
+ }
+
+ public void setCanSpawnNPCs(boolean par1) {
+ this.canSpawnNPCs = par1;
+ }
+
+ public boolean isPVPEnabled() {
+ return this.pvpEnabled;
+ }
+
+ public void setAllowPvp(boolean par1) {
+ this.pvpEnabled = par1;
+ }
+
+ public boolean isFlightAllowed() {
+ return this.allowFlight;
+ }
+
+ public void setAllowFlight(boolean par1) {
+ this.allowFlight = par1;
+ }
+
+ /**
+ * Return whether command blocks are enabled.
+ */
+ public abstract boolean isCommandBlockEnabled();
+
+ public String getMOTD() {
+ return this.motd;
+ }
+
+ public void setMOTD(String par1Str) {
+ this.motd = par1Str;
+ }
+
+ public int getBuildLimit() {
+ return 256;
+ }
+
+ public void setBuildLimit(int par1) {
+ throw new IllegalArgumentException("variable removed");
+ }
+
+ public boolean isServerStopped() {
+ return this.serverStopped;
+ }
+
+ public ServerConfigurationManager getConfigurationManager() {
+ return this.serverConfigManager;
+ }
+
+ public void setConfigurationManager(ServerConfigurationManager par1ServerConfigurationManager) {
+ this.serverConfigManager = par1ServerConfigurationManager;
+ }
+
+ /**
+ * Sets the game type for all worlds.
+ */
+ public void setGameType(EnumGameType par1EnumGameType) {
+ for (int var2 = 0; var2 < this.worldServers.length; ++var2) {
+ getServer().worldServers[var2].getWorldInfo().setGameType(par1EnumGameType);
+ }
+ }
+
+ public abstract WorkerListenThread getNetworkThread();
+
+ public boolean getGuiEnabled() {
+ return false;
+ }
+
+ /**
+ * On dedicated does nothing. On integrated, sets commandsAllowedForAll,
+ * gameType and allows external connections.
+ */
+ public abstract String shareToLAN(EnumGameType var1, boolean var2);
+
+ public int getTickCounter() {
+ return this.tickCounter;
+ }
+
+ /**
+ * Return the position for this command sender.
+ */
+ public ChunkCoordinates getPlayerCoordinates() {
+ return new ChunkCoordinates(0, 0, 0);
+ }
+
+ /**
+ * Return the spawn protection area's size.
+ */
+ public int getSpawnProtectionSize() {
+ return 0;
+ }
+
+ public boolean isBlockProtected(World par1World, int par2, int par3, int par4, EntityPlayer par5EntityPlayer) {
+ return false;
+ }
+
+ public abstract ILogAgent getLogAgent();
+
+ public void func_104055_i(boolean par1) {
+ this.field_104057_T = par1;
+ }
+
+ public boolean func_104056_am() {
+ return this.field_104057_T;
+ }
+
+ /**
+ * Gets the current player count, maximum player count, and player entity list.
+ */
+ public static ServerConfigurationManager getServerConfigurationManager(MinecraftServer par0MinecraftServer) {
+ return par0MinecraftServer.serverConfigManager;
+ }
}
diff --git a/src/main/java/net/minecraft/src/AchievementMap.java b/src/main/java/net/minecraft/src/AchievementMap.java
index 39bc87a..2fa376c 100644
--- a/src/main/java/net/minecraft/src/AchievementMap.java
+++ b/src/main/java/net/minecraft/src/AchievementMap.java
@@ -1,39 +1,34 @@
package net.minecraft.src;
-import net.lax1dude.eaglercraft.EaglerMisc;
-
-import java.io.BufferedReader;
-import java.io.InputStreamReader;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
-public class AchievementMap
-{
+public class AchievementMap {
/** Holds the singleton instance of AchievementMap. */
- public static AchievementMap instance = new AchievementMap();
+ public static AchievementMap instance = null;
+
+ public static void init(List guid) {
+ instance = new AchievementMap(guid);
+ StatList.initStats();
+ StatList.initBreakableStats();
+ }
/** Maps a achievement id with it's unique GUID. */
private Map guidMap = new HashMap();
- private AchievementMap()
- {
- try {
- String[] strs = EaglerMisc.bytesToLines(Minecraft.getMinecraft().texturePackList.getSelectedTexturePack().getResourceAsBytes("/achievement/map.txt"));
- for(String str : strs) {
- String[] var3 = str.split(",");
- int var4 = Integer.parseInt(var3[0]);
- this.guidMap.put(Integer.valueOf(var4), var3[1]);
- }
- } catch (Exception var5) {
- var5.printStackTrace();
+ private AchievementMap(List guid) {
+ for (String var2 : guid) {
+ String[] var3 = var2.split(",");
+ int var4 = Integer.parseInt(var3[0]);
+ this.guidMap.put(Integer.valueOf(var4), var3[1]);
}
}
/**
* Returns the unique GUID of a achievement id.
*/
- public static String getGuid(int par0)
- {
- return (String)instance.guidMap.get(Integer.valueOf(par0));
+ public static String getGuid(int par0) {
+ return (String) instance.guidMap.get(Integer.valueOf(par0));
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/net/minecraft/src/BiomeCache.java b/src/main/java/net/minecraft/src/BiomeCache.java
index d2a944b..745f544 100644
--- a/src/main/java/net/minecraft/src/BiomeCache.java
+++ b/src/main/java/net/minecraft/src/BiomeCache.java
@@ -1,5 +1,6 @@
package net.minecraft.src;
+import net.lax1dude.eaglercraft.EaglerAdapter;
import net.minecraft.server.MinecraftServer;
import java.util.ArrayList;
@@ -43,7 +44,7 @@ public class BiomeCache
this.cache.add(var5);
}
- var5.lastAccessTime = MinecraftServer.getSystemTimeMillis();
+ var5.lastAccessTime = EaglerAdapter.steadyTimeMillis();
return var5;
}
@@ -60,7 +61,7 @@ public class BiomeCache
*/
public void cleanupCache()
{
- long var1 = MinecraftServer.getSystemTimeMillis();
+ long var1 = EaglerAdapter.steadyTimeMillis();
long var3 = var1 - this.lastCleanupTime;
if (var3 > 7500L || var3 < 0L)
diff --git a/src/main/java/net/minecraft/src/Chunk.java b/src/main/java/net/minecraft/src/Chunk.java
index 0bea93c..70abca5 100644
--- a/src/main/java/net/minecraft/src/Chunk.java
+++ b/src/main/java/net/minecraft/src/Chunk.java
@@ -385,6 +385,8 @@ public class Chunk
}
}
+ public static int totalBlockLightUpdates = 0;
+
/**
* Initiates the recalculation of both the block-light and sky-light for a given block inside a chunk.
*/
@@ -495,6 +497,7 @@ public class Chunk
this.updateSkylightNeighborHeight(var6, var7, var12, var13);
}
+ ++totalBlockLightUpdates;
this.isModified = true;
}
}
diff --git a/src/main/java/net/minecraft/src/ChunkProviderServer.java b/src/main/java/net/minecraft/src/ChunkProviderServer.java
index b668c3a..863dc04 100644
--- a/src/main/java/net/minecraft/src/ChunkProviderServer.java
+++ b/src/main/java/net/minecraft/src/ChunkProviderServer.java
@@ -338,4 +338,26 @@ public class ChunkProviderServer implements IChunkProvider
}
public void recreateStructures(int par1, int par2) {}
+
+ private int _r = 0;
+ private int _w = 0;
+ private int _g = 0;
+
+ public int statR() {
+ int r = _r;
+ _r = 0;
+ return r;
+ }
+
+ public int statW() {
+ int w = _w;
+ _w = 0;
+ return w;
+ }
+
+ public int statG() {
+ int g = _g;
+ _g = 0;
+ return g;
+ }
}
diff --git a/src/main/java/net/minecraft/src/CommandDebug.java b/src/main/java/net/minecraft/src/CommandDebug.java
index 4cbceec..2abc049 100644
--- a/src/main/java/net/minecraft/src/CommandDebug.java
+++ b/src/main/java/net/minecraft/src/CommandDebug.java
@@ -1,5 +1,6 @@
package net.minecraft.src;
+import net.lax1dude.eaglercraft.EaglerAdapter;
import net.minecraft.server.MinecraftServer;
import java.io.File;
@@ -41,15 +42,15 @@ public class CommandDebug extends CommandBase
if (par2ArrayOfStr[0].equals("start"))
{
notifyAdmins(par1ICommandSender, "commands.debug.start", new Object[0]);
- MinecraftServer.getServer().enableProfiling();
- this.startTime = MinecraftServer.getSystemTimeMillis();
+ //MinecraftServer.getServer().enableProfiling();
+ this.startTime = EaglerAdapter.steadyTimeMillis();
this.startTicks = MinecraftServer.getServer().getTickCounter();
return;
}
if (par2ArrayOfStr[0].equals("stop"))
{
- long var3 = MinecraftServer.getSystemTimeMillis();
+ long var3 = EaglerAdapter.steadyTimeMillis();
int var5 = MinecraftServer.getServer().getTickCounter();
long var6 = var3 - this.startTime;
int var8 = var5 - this.startTicks;
diff --git a/src/main/java/net/minecraft/src/CommandDefaultGameMode.java b/src/main/java/net/minecraft/src/CommandDefaultGameMode.java
index 5d104c3..11f7597 100644
--- a/src/main/java/net/minecraft/src/CommandDefaultGameMode.java
+++ b/src/main/java/net/minecraft/src/CommandDefaultGameMode.java
@@ -31,17 +31,6 @@ public class CommandDefaultGameMode extends CommandGameMode
protected void setGameType(EnumGameType par1EnumGameType)
{
- MinecraftServer var2 = MinecraftServer.getServer();
- var2.setGameType(par1EnumGameType);
- EntityPlayerMP var4;
-
- if (var2.getForceGamemode())
- {
- for (Iterator var3 = MinecraftServer.getServer().getConfigurationManager().playerEntityList.iterator(); var3.hasNext(); var4.fallDistance = 0.0F)
- {
- var4 = (EntityPlayerMP)var3.next();
- var4.setGameType(par1EnumGameType);
- }
- }
+ MinecraftServer.getServer().setGameType(par1EnumGameType);
}
}
diff --git a/src/main/java/net/minecraft/src/CommandSetPlayerTimeout.java b/src/main/java/net/minecraft/src/CommandSetPlayerTimeout.java
index e117a3b..497194e 100644
--- a/src/main/java/net/minecraft/src/CommandSetPlayerTimeout.java
+++ b/src/main/java/net/minecraft/src/CommandSetPlayerTimeout.java
@@ -27,7 +27,7 @@ public class CommandSetPlayerTimeout extends CommandBase
if (par2ArrayOfStr.length == 1)
{
int var3 = parseIntWithMin(par1ICommandSender, par2ArrayOfStr[0], 0);
- MinecraftServer.getServer().func_143006_e(var3);
+ //MinecraftServer.getServer().func_143006_e(var3);
notifyAdmins(par1ICommandSender, "commands.setidletimeout.success", new Object[] {Integer.valueOf(var3)});
}
else
diff --git a/src/main/java/net/minecraft/src/CompressedStreamTools.java b/src/main/java/net/minecraft/src/CompressedStreamTools.java
index eabe70c..97b394e 100644
--- a/src/main/java/net/minecraft/src/CompressedStreamTools.java
+++ b/src/main/java/net/minecraft/src/CompressedStreamTools.java
@@ -13,8 +13,10 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
+
+import com.jcraft.jzlib.Deflater;
+import com.jcraft.jzlib.GZIPInputStream;
+import com.jcraft.jzlib.GZIPOutputStream;
public class CompressedStreamTools
{
@@ -182,16 +184,12 @@ public class CompressedStreamTools
/**
* Reads from a CompressedStream.
*/
- public static NBTTagCompound read(DataInput par0DataInput) throws IOException
- {
+ public static NBTTagCompound read(DataInput par0DataInput) throws IOException {
NBTBase var1 = NBTBase.readNamedTag(par0DataInput);
- if (var1 instanceof NBTTagCompound)
- {
- return (NBTTagCompound)var1;
- }
- else
- {
+ if (var1 instanceof NBTTagCompound) {
+ return (NBTTagCompound) var1;
+ } else {
throw new IOException("Root tag must be a named compound tag");
}
}
diff --git a/src/main/java/net/minecraft/src/ConvertingProgressUpdate.java b/src/main/java/net/minecraft/src/ConvertingProgressUpdate.java
index b2cefa1..2ff7e82 100644
--- a/src/main/java/net/minecraft/src/ConvertingProgressUpdate.java
+++ b/src/main/java/net/minecraft/src/ConvertingProgressUpdate.java
@@ -1,5 +1,6 @@
package net.minecraft.src;
+import net.lax1dude.eaglercraft.EaglerAdapter;
import net.minecraft.server.MinecraftServer;
public class ConvertingProgressUpdate implements IProgressUpdate
@@ -12,7 +13,7 @@ public class ConvertingProgressUpdate implements IProgressUpdate
public ConvertingProgressUpdate(MinecraftServer par1MinecraftServer)
{
this.mcServer = par1MinecraftServer;
- this.field_96245_b = MinecraftServer.getSystemTimeMillis();
+ this.field_96245_b = EaglerAdapter.steadyTimeMillis();
}
/**
@@ -25,9 +26,9 @@ public class ConvertingProgressUpdate implements IProgressUpdate
*/
public void setLoadingProgress(int par1)
{
- if (MinecraftServer.getSystemTimeMillis() - this.field_96245_b >= 1000L)
+ if (EaglerAdapter.steadyTimeMillis() - this.field_96245_b >= 1000L)
{
- this.field_96245_b = MinecraftServer.getSystemTimeMillis();
+ this.field_96245_b = EaglerAdapter.steadyTimeMillis();
this.mcServer.getLogAgent().logInfo("Converting... " + par1 + "%");
}
}
diff --git a/src/main/java/net/minecraft/src/EntityPlayerMP.java b/src/main/java/net/minecraft/src/EntityPlayerMP.java
index dae4783..8530f48 100644
--- a/src/main/java/net/minecraft/src/EntityPlayerMP.java
+++ b/src/main/java/net/minecraft/src/EntityPlayerMP.java
@@ -9,6 +9,8 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+
+import net.lax1dude.eaglercraft.EaglerAdapter;
import net.minecraft.server.MinecraftServer;
public class EntityPlayerMP extends EntityPlayer implements ICrafting
@@ -55,7 +57,7 @@ public class EntityPlayerMP extends EntityPlayer implements ICrafting
private int initialInvulnerability = 60;
/** must be between 3>x>15 (strictly between) */
- private int renderDistance;
+ int renderDistance;
private int chatVisibility;
private boolean chatColours = true;
private long field_143005_bX = 0L;
@@ -117,7 +119,7 @@ public class EntityPlayerMP extends EntityPlayer implements ICrafting
if (par1NBTTagCompound.hasKey("playerGameType"))
{
- if (MinecraftServer.getServer().getForceGamemode())
+ if (true) // FIX THIS SHIT
{
this.theItemInWorldManager.setGameType(MinecraftServer.getServer().getGameType());
}
@@ -234,10 +236,10 @@ public class EntityPlayerMP extends EntityPlayer implements ICrafting
}
}
- if (this.field_143005_bX > 0L && this.mcServer.func_143007_ar() > 0 && MinecraftServer.getSystemTimeMillis() - this.field_143005_bX > (long)(this.mcServer.func_143007_ar() * 1000 * 60))
+ /*if (this.field_143005_bX > 0L && this.mcServer.func_143007_ar() > 0 && EaglerAdapter.steadyTimeMillis() - this.field_143005_bX > (long)(this.mcServer.func_143007_ar() * 1000 * 60))
{
this.playerNetServerHandler.kickPlayerFromServer("You have been idle for too long!");
- }
+ }*/
}
public void onUpdateEntity()
@@ -905,7 +907,10 @@ public class EntityPlayerMP extends EntityPlayer implements ICrafting
*/
public boolean canCommandSenderUseCommand(int par1, String par2Str)
{
- return "seed".equals(par2Str) && !this.mcServer.isDedicatedServer() ? true : (!"tell".equals(par2Str) && !"help".equals(par2Str) && !"me".equals(par2Str) ? (this.mcServer.getConfigurationManager().isPlayerOpped(this.username) ? this.mcServer.func_110455_j() >= par1 : false) : true);
+ return "seed".equals(par2Str) && !this.mcServer.isDedicatedServer() ? true
+ : (!"tell".equals(par2Str) && !"help".equals(par2Str) && !"me".equals(par2Str)
+ ? this.mcServer.getConfigurationManager().isPlayerOpped(this.username)
+ : true);
}
/**
@@ -913,7 +918,7 @@ public class EntityPlayerMP extends EntityPlayer implements ICrafting
*/
public String getPlayerIP()
{
- return null;
+ return "Cannot get IP over websocket";
}
public void updateClientInfo(Packet204ClientInfo par1Packet204ClientInfo)
@@ -961,6 +966,6 @@ public class EntityPlayerMP extends EntityPlayer implements ICrafting
public void func_143004_u()
{
- this.field_143005_bX = MinecraftServer.getSystemTimeMillis();
+ this.field_143005_bX = EaglerAdapter.steadyTimeMillis();
}
}
diff --git a/src/main/java/net/minecraft/src/GuiMainMenu.java b/src/main/java/net/minecraft/src/GuiMainMenu.java
index 1fb0444..2eda74a 100644
--- a/src/main/java/net/minecraft/src/GuiMainMenu.java
+++ b/src/main/java/net/minecraft/src/GuiMainMenu.java
@@ -142,7 +142,7 @@ public class GuiMainMenu extends GuiScreen {
StringTranslate var2 = StringTranslate.getInstance();
int var4 = this.height / 4 + 48;
- if(false) { // EaglerAdapter.isIntegratedServerAvailable()
+ if(EaglerAdapter.isIntegratedServerAvailable()) { // EaglerAdapter.isIntegratedServerAvailable()
this.buttonList.add(new GuiButton(1, this.width / 2 - 100, var4, var2.translateKey("menu.singleplayer")));
this.buttonList.add(new GuiButton(2, this.width / 2 - 100, var4 + 24 * 1, var2.translateKey("menu.multiplayer")));
this.buttonList.add(new GuiButton(3, this.width / 2 - 100, var4 + 24 * 2, var2.translateKey("menu.forkme")));
diff --git a/src/main/java/net/minecraft/src/ICommandSender.java b/src/main/java/net/minecraft/src/ICommandSender.java
index 70953fe..011170b 100644
--- a/src/main/java/net/minecraft/src/ICommandSender.java
+++ b/src/main/java/net/minecraft/src/ICommandSender.java
@@ -19,5 +19,5 @@ public interface ICommandSender
*/
ChunkCoordinates getPlayerCoordinates();
- World getEntityWorld();
+ //World getEntityWorld();
}
diff --git a/src/main/java/net/minecraft/src/NetHandler.java b/src/main/java/net/minecraft/src/NetHandler.java
index 4a3e6de..9f17654 100644
--- a/src/main/java/net/minecraft/src/NetHandler.java
+++ b/src/main/java/net/minecraft/src/NetHandler.java
@@ -12,12 +12,15 @@ public abstract class NetHandler
*/
public void handleMapChunk(Packet51MapChunk par1Packet51MapChunk) {}
- /**
- * Default handler called for packets that don't have their own handlers in NetClientHandler; currentlly does
- * nothing.
- */
+ public boolean shouldBeRemoved() {
+ return true;
+ }
+
public void unexpectedPacket(Packet par1Packet) {}
+ public void handlePackets() {
+ }
+
public void handleErrorMessage(String par1Str, Object[] par2ArrayOfObj) {}
public void handleKickDisconnect(Packet255KickDisconnect par1Packet255KickDisconnect)
diff --git a/src/main/java/net/minecraft/src/NetLoginHandler.java b/src/main/java/net/minecraft/src/NetLoginHandler.java
new file mode 100644
index 0000000..c893162
--- /dev/null
+++ b/src/main/java/net/minecraft/src/NetLoginHandler.java
@@ -0,0 +1,242 @@
+package net.minecraft.src;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import net.lax1dude.eaglercraft.sp.EaglercraftRandom;
+import net.lax1dude.eaglercraft.sp.WorkerNetworkManager;
+import net.minecraft.server.MinecraftServer;
+
+public class NetLoginHandler extends NetHandler {
+ /** The Random object used to generate serverId hex strings. */
+ private static EaglercraftRandom rand = new EaglercraftRandom();
+
+ /** Reference to the MinecraftServer object. */
+ private final MinecraftServer mcServer;
+ public final WorkerNetworkManager myTCPConnection;
+
+ /**
+ * Returns if the login handler is finished and can be removed. It is set to
+ * true on either error or successful login.
+ */
+ public boolean finishedProcessing = false;
+
+ /** While waiting to login, if this field ++'s to 600 it will kick you. */
+ private int loginTimer = 0;
+ private String clientUsername = null;
+ private volatile boolean field_72544_i = false;
+
+ private boolean field_92079_k = false;
+
+ private int hash = 0;
+ private static int hashBase = 69696969;
+
+ private int viewDistance = 2;
+
+ public NetLoginHandler(MinecraftServer par1MinecraftServer, WorkerNetworkManager par2Socket) {
+ this.mcServer = par1MinecraftServer;
+ this.myTCPConnection = par2Socket;
+ hash = ++hashBase;
+ }
+
+ public boolean shouldBeRemoved() {
+ return this.finishedProcessing;
+ }
+
+ /**
+ * Logs the user in if a login packet is found, otherwise keeps processing
+ * network packets unless the timeout has occurred.
+ */
+ public void handlePackets() {
+ System.out.println("[Server][LOGIN][HANDLE][" + clientUsername + "]");
+ if (this.field_72544_i) {
+ this.initializePlayerConnection();
+ return;
+ }
+
+ if (this.loginTimer++ == 600) {
+ this.kickUser("Took too long to log in");
+ } else {
+ this.myTCPConnection.processReadPackets();
+ }
+ }
+
+ public boolean equals(Object o) {
+ return (o instanceof NetLoginHandler) && ((NetLoginHandler)o).hash == hash;
+ }
+
+ public int hashCode() {
+ return hash;
+ }
+
+ /**
+ * Disconnects the user with the given reason.
+ */
+ public void kickUser(String par1Str) {
+ try {
+ this.mcServer.getLogAgent().logInfo("Disconnecting " + this.getUsernameAndAddress() + ": " + par1Str);
+ this.myTCPConnection.addToSendQueue(new Packet255KickDisconnect(par1Str));
+ this.myTCPConnection.serverShutdown();
+ this.finishedProcessing = true;
+ } catch (Exception var3) {
+ var3.printStackTrace();
+ }
+ }
+
+ public void handleClientProtocol(Packet2ClientProtocol par1Packet2ClientProtocol) {
+ this.clientUsername = par1Packet2ClientProtocol.getUsername();
+ int var2 = 64 << 3 - par1Packet2ClientProtocol.getViewDistance();
+ if(var2 > 400) {
+ var2 = 400;
+ }
+ var2 = (var2 >> 5) + 2;
+ this.viewDistance = var2;
+ System.out.println("[Server][HANDSHAKE][" + this.clientUsername + "]");
+
+ if (!this.clientUsername.equals(StringUtils.stripControlCodes(this.clientUsername))) {
+ this.kickUser("Invalid username!");
+ } else {
+ if (par1Packet2ClientProtocol.getProtocolVersion() != 61) {
+ if (par1Packet2ClientProtocol.getProtocolVersion() > 61) {
+ this.kickUser("Outdated server!");
+ } else {
+ this.kickUser("Outdated client!");
+ }
+ }else {
+ this.initializePlayerConnection();
+ }
+ }
+ }
+
+ public void handleClientCommand(Packet205ClientCommand par1Packet205ClientCommand) {
+ if (par1Packet205ClientCommand.forceRespawn == 0) {
+ if (this.field_92079_k) {
+ this.kickUser("Duplicate login");
+ return;
+ }
+
+ this.field_92079_k = true;
+ this.field_72544_i = true;
+ }
+ }
+
+ public void handleLogin(Packet1Login par1Packet1Login) {
+ }
+
+ /**
+ * on success the specified username is connected to the minecraftInstance,
+ * otherwise they are packet255'd
+ */
+ public void initializePlayerConnection() {
+ String var1 = this.mcServer.getConfigurationManager().allowUserToConnect(this.clientUsername);
+
+ if (var1 != null) {
+ this.kickUser(var1);
+ } else {
+ EntityPlayerMP var2 = this.mcServer.getConfigurationManager().createPlayerForUser(this.clientUsername);
+ if (var2 != null) {
+ if (this.mcServer.getServerOwner().equals(this.clientUsername)) {
+ var2.renderDistance = this.viewDistance;
+ } else {
+ EntityPlayerMP fard = this.mcServer.getConfigurationManager().getPlayerForUsername(this.mcServer.getServerOwner());
+ int maxRenderDistance = fard == null ? 10 : (fard.renderDistance > 10 ? 10 : fard.renderDistance);
+ var2.renderDistance = this.viewDistance > maxRenderDistance ? maxRenderDistance : this.viewDistance;
+ }
+ this.mcServer.getConfigurationManager().initializeConnectionToPlayer(this.myTCPConnection, var2);
+ }else {
+ this.kickUser("Could not construct EntityPlayerMP for '" + var1 + "'");
+ }
+ }
+
+ this.finishedProcessing = true;
+ }
+
+ public void handleErrorMessage(String par1Str, Object[] par2ArrayOfObj) {
+ this.mcServer.getLogAgent().logInfo(this.getUsernameAndAddress() + " lost connection");
+ this.finishedProcessing = true;
+ }
+
+ /**
+ * Handle a server ping packet.
+ */
+ public void handleServerPing(Packet254ServerPing par1Packet254ServerPing) {
+ try {
+ ServerConfigurationManager var2 = this.mcServer.getConfigurationManager();
+ String var3 = null;
+
+ if (par1Packet254ServerPing.readSuccessfully == 1) {
+ List var4 = Arrays.asList(new Serializable[] { Integer.valueOf(1), Integer.valueOf(61),
+ this.mcServer.getMinecraftVersion(), this.mcServer.getMOTD(),
+ Integer.valueOf(var2.getCurrentPlayerCount()), Integer.valueOf(var2.getMaxPlayers()) });
+ Object var6;
+
+ for (Iterator var5 = var4.iterator(); var5
+ .hasNext(); var3 = var3 + var6.toString().replaceAll("\u0000", "")) {
+ var6 = var5.next();
+
+ if (var3 == null) {
+ var3 = "\u00a7";
+ } else {
+ var3 = var3 + "\u0000";
+ }
+ }
+ } else {
+ var3 = this.mcServer.getMOTD() + "\u00a7" + var2.getCurrentPlayerCount() + "\u00a7"
+ + var2.getMaxPlayers();
+ }
+
+ this.myTCPConnection.addToSendQueue(new Packet255KickDisconnect(var3));
+ this.myTCPConnection.serverShutdown();
+
+ this.finishedProcessing = true;
+ } catch (Exception var7) {
+ var7.printStackTrace();
+ }
+ }
+
+ /**
+ * Default handler called for packets that don't have their own handlers in
+ * NetServerHandler; kicks player from the server.
+ */
+ public void unexpectedPacket(Packet par1Packet) {
+ this.kickUser("Protocol error");
+ }
+
+ public String getUsernameAndAddress() {
+ return this.clientUsername + "[EAG]";
+ }
+
+ /**
+ * determine if it is a server handler
+ */
+ public boolean isServerHandler() {
+ return true;
+ }
+
+ /**
+ * Returns the server Id randomly generated by this login handler.
+ */
+ static String getServerId(NetLoginHandler par0NetLoginHandler) {
+ return "you eagler";
+ }
+
+ /**
+ * Returns the reference to Minecraft Server.
+ */
+ static MinecraftServer getLoginMinecraftServer(NetLoginHandler par0NetLoginHandler) {
+ return par0NetLoginHandler.mcServer;
+ }
+
+ /**
+ * Returns the connecting client username.
+ */
+ static String getClientUsername(NetLoginHandler par0NetLoginHandler) {
+ return par0NetLoginHandler.clientUsername;
+ }
+
+ static boolean func_72531_a(NetLoginHandler par0NetLoginHandler, boolean par1) {
+ return par0NetLoginHandler.field_72544_i = par1;
+ }
+}
diff --git a/src/main/java/net/minecraft/src/Packet.java b/src/main/java/net/minecraft/src/Packet.java
index 7585f4f..75ed279 100644
--- a/src/main/java/net/minecraft/src/Packet.java
+++ b/src/main/java/net/minecraft/src/Packet.java
@@ -1,5 +1,6 @@
package net.minecraft.src;
+import net.lax1dude.eaglercraft.EaglerAdapter;
import net.minecraft.server.MinecraftServer;
import java.io.*;
@@ -25,7 +26,7 @@ public abstract class Packet
protected ILogAgent field_98193_m;
/** the system time in milliseconds when this packet was created. */
- public final long creationTimeMillis = MinecraftServer.getSystemTimeMillis();
+ public final long creationTimeMillis = EaglerAdapter.steadyTimeMillis();
public static long receivedID;
public static long receivedSize;
diff --git a/src/main/java/net/minecraft/src/Packet2ClientProtocol.java b/src/main/java/net/minecraft/src/Packet2ClientProtocol.java
index 5f86bea..aa86c77 100644
--- a/src/main/java/net/minecraft/src/Packet2ClientProtocol.java
+++ b/src/main/java/net/minecraft/src/Packet2ClientProtocol.java
@@ -4,74 +4,62 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
-public class Packet2ClientProtocol extends Packet
-{
+public class Packet2ClientProtocol extends Packet {
private int protocolVersion;
private String username;
private String serverHost;
- private int serverPort;
-
- public Packet2ClientProtocol() {}
-
- public Packet2ClientProtocol(int par1, String par2Str, String par3Str, int par4)
- {
- this.protocolVersion = par1;
- this.username = par2Str;
- this.serverHost = par3Str;
- this.serverPort = par4;
- }
+ private int viewDistance;
/**
* Abstract. Reads the raw packet data from the data stream.
*/
- public void readPacketData(DataInput par1DataInput) throws IOException
- {
- this.protocolVersion = par1DataInput.readByte();
- this.username = readString(par1DataInput, 16);
- this.serverHost = readString(par1DataInput, 255);
- this.serverPort = par1DataInput.readInt();
+ public void readPacketData(DataInput par1DataInputStream) throws IOException {
+ this.protocolVersion = par1DataInputStream.readByte();
+ this.username = readString(par1DataInputStream, 16);
+ this.serverHost = readString(par1DataInputStream, 255);
+ this.viewDistance = par1DataInputStream.readInt();
}
/**
* Abstract. Writes the raw packet data to the data stream.
*/
- public void writePacketData(DataOutput par1DataOutput) throws IOException
- {
- par1DataOutput.writeByte(this.protocolVersion);
- writeString(this.username, par1DataOutput);
- writeString(this.serverHost, par1DataOutput);
- par1DataOutput.writeInt(this.serverPort);
+ public void writePacketData(DataOutput par1DataOutputStream) throws IOException {
+ par1DataOutputStream.writeByte(this.protocolVersion);
+ writeString(this.username, par1DataOutputStream);
+ writeString(this.serverHost, par1DataOutputStream);
+ par1DataOutputStream.writeInt(this.viewDistance);
}
/**
* Passes this Packet on to the NetHandler for processing.
*/
- public void processPacket(NetHandler par1NetHandler)
- {
+ public void processPacket(NetHandler par1NetHandler) {
par1NetHandler.handleClientProtocol(this);
}
/**
* Abstract. Return the size of the packet (not counting the header).
*/
- public int getPacketSize()
- {
+ public int getPacketSize() {
return 3 + 2 * this.username.length();
}
/**
* Returns the protocol version.
*/
- public int getProtocolVersion()
- {
+ public int getProtocolVersion() {
return this.protocolVersion;
}
/**
* Returns the username.
*/
- public String getUsername()
- {
+ public String getUsername() {
return this.username;
}
-}
+
+ public int getViewDistance() {
+ return this.viewDistance;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/minecraft/src/RegionFile.java b/src/main/java/net/minecraft/src/RegionFile.java
index 7fd0b73..c4a27a2 100644
--- a/src/main/java/net/minecraft/src/RegionFile.java
+++ b/src/main/java/net/minecraft/src/RegionFile.java
@@ -1,5 +1,7 @@
package net.minecraft.src;
+import net.lax1dude.eaglercraft.EaglerAdapter;
+import net.lax1dude.eaglercraft.sp.RandomAccessMemoryFile;
import net.minecraft.server.MinecraftServer;
import java.io.*;
@@ -11,8 +13,7 @@ import java.util.zip.InflaterInputStream;
public class RegionFile
{
private static final byte[] emptySector = new byte[4096];
- private final File fileName;
- private RandomAccessFile dataFile;
+ private RandomAccessMemoryFile dataFile;
private final int[] offsets = new int[1024];
private final int[] chunkTimestamps = new int[1024];
private ArrayList sectorFree;
@@ -21,22 +22,16 @@ public class RegionFile
private int sizeDelta;
private long lastModified;
- public RegionFile(File par1File)
+ public RegionFile(RandomAccessMemoryFile par1File)
{
- this.fileName = par1File;
this.sizeDelta = 0;
try
{
- if (par1File.exists())
- {
- this.lastModified = par1File.lastModified();
- }
-
- this.dataFile = new RandomAccessFile(par1File, "rw");
+ this.dataFile = par1File;
int var2;
- if (this.dataFile.length() < 4096L)
+ if (this.dataFile.getLength() < 4096L)
{
for (var2 = 0; var2 < 1024; ++var2)
{
@@ -51,15 +46,15 @@ public class RegionFile
this.sizeDelta += 8192;
}
- if ((this.dataFile.length() & 4095L) != 0L)
+ if ((this.dataFile.getLength() & 4095L) != 0L)
{
- for (var2 = 0; (long)var2 < (this.dataFile.length() & 4095L); ++var2)
+ for (var2 = 0; (long)var2 < (this.dataFile.getLength() & 4095L); ++var2)
{
this.dataFile.write(0);
}
}
- var2 = (int)this.dataFile.length() / 4096;
+ var2 = (int)this.dataFile.getLength() / 4096;
this.sectorFree = new ArrayList(var2);
int var3;
@@ -70,7 +65,7 @@ public class RegionFile
this.sectorFree.set(0, Boolean.valueOf(false));
this.sectorFree.set(1, Boolean.valueOf(false));
- this.dataFile.seek(0L);
+ this.dataFile.seek(0);
int var4;
for (var3 = 0; var3 < 1024; ++var3)
@@ -102,71 +97,50 @@ public class RegionFile
/**
* args: x, y - get uncompressed chunk stream from the region file
*/
- public synchronized DataInputStream getChunkDataInputStream(int par1, int par2)
- {
- if (this.outOfBounds(par1, par2))
- {
+ public synchronized DataInputStream getChunkDataInputStream(int par1, int par2) {
+ if (this.outOfBounds(par1, par2)) {
return null;
- }
- else
- {
- try
- {
+ } else {
+ try {
int var3 = this.getOffset(par1, par2);
- if (var3 == 0)
- {
+ if (var3 == 0) {
return null;
- }
- else
- {
+ } else {
int var4 = var3 >> 8;
int var5 = var3 & 255;
- if (var4 + var5 > this.sectorFree.size())
- {
+ if (var4 + var5 > this.sectorFree.size()) {
return null;
- }
- else
- {
- this.dataFile.seek((long)(var4 * 4096));
+ } else {
+ this.dataFile.seek(var4 * 4096);
int var6 = this.dataFile.readInt();
- if (var6 > 4096 * var5)
- {
+ if (var6 > 4096 * var5) {
return null;
- }
- else if (var6 <= 0)
- {
+ } else if (var6 <= 0) {
return null;
- }
- else
- {
+ } else {
byte var7 = this.dataFile.readByte();
byte[] var8;
- if (var7 == 1)
- {
+ if (var7 == 1) {
var8 = new byte[var6 - 1];
this.dataFile.read(var8);
- return new DataInputStream(new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(var8))));
- }
- else if (var7 == 2)
- {
+ return new DataInputStream(
+ new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(var8))));
+ } else if (var7 == 2) {
var8 = new byte[var6 - 1];
this.dataFile.read(var8);
- return new DataInputStream(new BufferedInputStream(new InflaterInputStream(new ByteArrayInputStream(var8))));
- }
- else
- {
+ return new DataInputStream(new BufferedInputStream(
+ new InflaterInputStream(new ByteArrayInputStream(var8))));
+ } else {
return null;
}
}
}
}
- }
- catch (IOException var9)
- {
+ } catch (IOException var9) {
return null;
}
}
@@ -256,7 +230,7 @@ public class RegionFile
}
else
{
- this.dataFile.seek(this.dataFile.length());
+ this.dataFile.seek(this.dataFile.getLength());
var6 = this.sectorFree.size();
for (var11 = 0; var11 < var8; ++var11)
@@ -271,7 +245,7 @@ public class RegionFile
}
}
- this.setChunkTimestamp(par1, par2, (int)(MinecraftServer.getSystemTimeMillis() / 1000L));
+ this.setChunkTimestamp(par1, par2, (int)(EaglerAdapter.steadyTimeMillis() / 1000L));
}
catch (IOException var12)
{
@@ -284,7 +258,7 @@ public class RegionFile
*/
private void write(int par1, byte[] par2ArrayOfByte, int par3) throws IOException
{
- this.dataFile.seek((long)(par1 * 4096));
+ this.dataFile.seek(par1 * 4096);
this.dataFile.writeInt(par3 + 1);
this.dataFile.writeByte(2);
this.dataFile.write(par2ArrayOfByte, 0, par3);
@@ -320,7 +294,7 @@ public class RegionFile
private void setOffset(int par1, int par2, int par3) throws IOException
{
this.offsets[par1 + par2 * 32] = par3;
- this.dataFile.seek((long)((par1 + par2 * 32) * 4));
+ this.dataFile.seek((par1 + par2 * 32) * 4);
this.dataFile.writeInt(par3);
}
@@ -330,18 +304,29 @@ public class RegionFile
private void setChunkTimestamp(int par1, int par2, int par3) throws IOException
{
this.chunkTimestamps[par1 + par2 * 32] = par3;
- this.dataFile.seek((long)(4096 + (par1 + par2 * 32) * 4));
+ this.dataFile.seek(4096 + (par1 + par2 * 32) * 4);
this.dataFile.writeInt(par3);
}
- /**
- * close this RegionFile and prevent further writes
- */
- public void close() throws IOException
- {
- if (this.dataFile != null)
- {
- this.dataFile.close();
+ public RandomAccessMemoryFile getFile() {
+ return dataFile;
+ }
+
+ class ChunkBuffer extends ByteArrayOutputStream {
+ private int chunkX;
+ private int chunkZ;
+
+ public ChunkBuffer(int x, int z) {
+ super(8096);
+ this.chunkX = x;
+ this.chunkZ = z;
+ }
+
+ /**+
+ * close this RegionFile and prevent further writes
+ */
+ public void close() throws IOException {
+ RegionFile.this.write(this.chunkX, this.chunkZ, this.buf, this.count);
}
}
}
diff --git a/src/main/java/net/minecraft/src/SaveHandler.java b/src/main/java/net/minecraft/src/SaveHandler.java
index 0130ae3..323d8df 100644
--- a/src/main/java/net/minecraft/src/SaveHandler.java
+++ b/src/main/java/net/minecraft/src/SaveHandler.java
@@ -1,5 +1,6 @@
package net.minecraft.src;
+import net.lax1dude.eaglercraft.EaglerAdapter;
import net.minecraft.server.MinecraftServer;
import java.io.*;
@@ -16,7 +17,7 @@ public class SaveHandler implements ISaveHandler, IPlayerFileData
/**
* The time in milliseconds when this field was initialized. Stored in the session lock file.
*/
- private final long initializationTime = MinecraftServer.getSystemTimeMillis();
+ private final long initializationTime = EaglerAdapter.steadyTimeMillis();
/** The directory name of the world */
private final String saveDirectoryName;
diff --git a/src/main/java/net/minecraft/src/ServerConfigurationManager.java b/src/main/java/net/minecraft/src/ServerConfigurationManager.java
index 92f2201..91ec832 100644
--- a/src/main/java/net/minecraft/src/ServerConfigurationManager.java
+++ b/src/main/java/net/minecraft/src/ServerConfigurationManager.java
@@ -254,6 +254,19 @@ public abstract class ServerConfigurationManager
/**
* checks ban-lists, then white-lists, then space for the server. Returns null on success, or an error message
*/
+ public String allowUserToConnect(String par2Str) {
+ if(this.playerEntityList.size() >= this.maxPlayers) {
+ return "The server is full!";
+ }else {
+ for(EntityPlayerMP pp : (List)this.playerEntityList) {
+ if(pp.username.equalsIgnoreCase(par2Str)) {
+ return "Someone with your username is already on this world";
+ }
+ }
+ return null;
+ }
+ }
+
public String allowUserToConnect(SocketAddress par1SocketAddress, String par2Str)
{
if (this.bannedPlayers.isBanned(par2Str))
diff --git a/src/main/java/net/minecraft/src/StringTranslate.java b/src/main/java/net/minecraft/src/StringTranslate.java
index e8c4e93..c688278 100644
--- a/src/main/java/net/minecraft/src/StringTranslate.java
+++ b/src/main/java/net/minecraft/src/StringTranslate.java
@@ -2,6 +2,7 @@ package net.minecraft.src;
import java.io.IOException;
import java.util.IllegalFormatException;
+import java.util.List;
import java.util.Properties;
import java.util.TreeMap;
@@ -25,6 +26,10 @@ public class StringTranslate {
this.loadLanguageList();
}
+ public static void init(List en_us) {
+ instance.loadLanguageList();
+ }
+
/**
* Return the StringTranslate singleton instance
*/
diff --git a/src/main/java/net/minecraft/src/World.java b/src/main/java/net/minecraft/src/World.java
index 705fed5..cc6eb24 100644
--- a/src/main/java/net/minecraft/src/World.java
+++ b/src/main/java/net/minecraft/src/World.java
@@ -1,5 +1,6 @@
package net.minecraft.src;
+import net.lax1dude.eaglercraft.EaglerAdapter;
import net.lax1dude.eaglercraft.EaglercraftRandom;
import net.minecraft.server.MinecraftServer;
@@ -4021,7 +4022,7 @@ public abstract class World implements IBlockAccess
{
if (this.getTotalWorldTime() % 600L == 0L)
{
- this.theCalendar.setTimeInMillis(MinecraftServer.getSystemTimeMillis());
+ this.theCalendar.setTimeInMillis(EaglerAdapter.steadyTimeMillis());
}
return this.theCalendar;
diff --git a/src/main/java/net/minecraft/src/WorldInfo.java b/src/main/java/net/minecraft/src/WorldInfo.java
index 50ff932..137ffba 100644
--- a/src/main/java/net/minecraft/src/WorldInfo.java
+++ b/src/main/java/net/minecraft/src/WorldInfo.java
@@ -1,5 +1,6 @@
package net.minecraft.src;
+import net.lax1dude.eaglercraft.EaglerAdapter;
import net.minecraft.server.MinecraftServer;
public class WorldInfo
@@ -251,7 +252,7 @@ public class WorldInfo
par1NBTTagCompound.setLong("Time", this.totalTime);
par1NBTTagCompound.setLong("DayTime", this.worldTime);
par1NBTTagCompound.setLong("SizeOnDisk", this.sizeOnDisk);
- par1NBTTagCompound.setLong("LastPlayed", MinecraftServer.getSystemTimeMillis());
+ par1NBTTagCompound.setLong("LastPlayed", EaglerAdapter.steadyTimeMillis());
par1NBTTagCompound.setString("LevelName", this.levelName);
par1NBTTagCompound.setInteger("version", this.saveVersion);
par1NBTTagCompound.setInteger("rainTime", this.rainTime);
diff --git a/src/main/java/net/minecraft/src/WorldServer.java b/src/main/java/net/minecraft/src/WorldServer.java
index 10d08ac..3ba86aa 100644
--- a/src/main/java/net/minecraft/src/WorldServer.java
+++ b/src/main/java/net/minecraft/src/WorldServer.java
@@ -1,6 +1,7 @@
package net.minecraft.src;
import net.lax1dude.eaglercraft.EaglercraftRandom;
+import net.lax1dude.eaglercraft.sp.SysUtil;
import net.minecraft.server.MinecraftServer;
import java.util.*;
@@ -45,6 +46,20 @@ public class WorldServer extends World
/** An IntHashMap of entity IDs (integers) to their Entity objects. */
private IntHashMap entityIdMap;
+ private int r = 0;
+ private int w = 0;
+ private int g = 0;
+ private int tu = 0;
+ private int lu = 0;
+
+ private int _r = 0;
+ private int _w = 0;
+ private int _g = 0;
+ private int _tu = 0;
+ private int _lu = 0;
+
+ private long rwgtuluTimer = 0l;
+
public WorldServer(MinecraftServer par1MinecraftServer, ISaveHandler par2ISaveHandler, String par3Str, int par4, WorldSettings par5WorldSettings, ILogAgent par7ILogAgent)
{
super(par2ISaveHandler, par3Str, par5WorldSettings, WorldProvider.getProviderForDimension(par4));
@@ -133,6 +148,42 @@ public class WorldServer extends World
this.villageSiegeObj.tick();
this.worldTeleporter.removeStalePortalLocations(this.getTotalWorldTime());
this.sendAndApplyBlockEvents();
+
+ _r += this.theChunkProviderServer.statR();
+ _w += this.theChunkProviderServer.statW();
+ _g += this.theChunkProviderServer.statG();
+ _lu += Chunk.totalBlockLightUpdates;
+ Chunk.totalBlockLightUpdates = 0;
+
+ long millis = SysUtil.steadyTimeMillis();
+ if(millis - rwgtuluTimer >= 1000l) {
+ rwgtuluTimer = millis;
+ r = _r; _r = 0;
+ w = _w; _w = 0;
+ g = _g; _g = 0;
+ tu = _tu; _tu = 0;
+ lu = _lu; _lu = 0;
+ }
+ }
+
+ public int getR() {
+ return r;
+ }
+
+ public int getW() {
+ return w;
+ }
+
+ public int getG() {
+ return g;
+ }
+
+ public int getTU() {
+ return tu;
+ }
+
+ public int getLU() {
+ return lu;
}
/**