diff --git a/README.md b/README.md index 5ceb249..fd9a53a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ This is the unfinished port of the WebDisplays mod for Minecraft 1.12.2. The tex * minePad management: check GuiContainer.draggedStack for minePad * Change mod name to WD2 * Put a limit on screen resolution +* Auto-find rotation for BOTTOM & TOP screens ### Config elements * Site blacklist diff --git a/libs/mcef-1.12.2-0.9-api.jar b/libs/mcef-1.12.2-0.9-api.jar index 476f59c..60ffc72 100644 Binary files a/libs/mcef-1.12.2-0.9-api.jar and b/libs/mcef-1.12.2-0.9-api.jar differ diff --git a/src/main/java/net/montoyo/wd/miniserv/OutgoingPacket.java b/src/main/java/net/montoyo/wd/miniserv/OutgoingPacket.java new file mode 100644 index 0000000..6d605c4 --- /dev/null +++ b/src/main/java/net/montoyo/wd/miniserv/OutgoingPacket.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.miniserv; + +import java.io.*; +import java.util.function.Consumer; + +public final class OutgoingPacket { + + private ByteArrayOutputStream baos; + private DataOutputStream dos; + private Consumer onFinish; + + public OutgoingPacket() { + baos = new ByteArrayOutputStream(); + dos = new DataOutputStream(baos); + } + + public final void writeInt(int i) { + try { + dos.writeInt(i); + } catch(IOException ex) {} + } + + public final void writeByte(int b) { + try { + dos.writeByte(b); + } catch(IOException ex) {} + } + + public final void writeShort(int s) { + try { + dos.writeShort(s); + } catch(IOException ex) {} + } + + public final void writeBoolean(boolean b) { + try { + dos.writeBoolean(b); + } catch(IOException ex) {} + } + + public final void writeBytes(byte[] data) { + try { + dos.write(data); + } catch(IOException ex) {} + } + + public final void writeBytes(byte[] data, int offset, int size) { + try { + dos.write(data, offset, size); + } catch(IOException ex) {} + } + + public final void writeString(String str) { + byte[] bytes; + try { + bytes = str.getBytes("UTF-8"); + } catch(UnsupportedEncodingException ex) { + return; //Meh, shouldn't happen + } + + try { + dos.writeShort(bytes.length); + dos.write(bytes); + } catch(IOException ex) {} + } + + public final int writeStream(InputStream is, int max) throws IOException { + final int origMax = max; + final byte[] buf = new byte[8192]; + + while(max > 0) { + int read = is.read(buf, 0, (max < buf.length) ? max : buf.length); + if(read <= 0) + return origMax - max; + + dos.write(buf, 0, read); + max -= read; + } + + return origMax; + } + + public final byte[] finish() { + if(onFinish != null) + onFinish.accept(this); + + byte[] bytes = baos.toByteArray(); + baos = null; + dos = null; + + return bytes; + } + + public final void setOnFinishAction(Consumer action) { + onFinish = action; + } + +} diff --git a/src/main/java/net/montoyo/wd/miniserv/PacketID.java b/src/main/java/net/montoyo/wd/miniserv/PacketID.java new file mode 100644 index 0000000..fa0e966 --- /dev/null +++ b/src/main/java/net/montoyo/wd/miniserv/PacketID.java @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.miniserv; + +public enum PacketID { + + PING, //C->S and S->C + BEGIN_FILE_UPLOAD, //C->S + FILE_PART, //C->S and S->C + GET_FILE; //C->S + + public static PacketID fromInt(int i) { + PacketID[] values = values(); + return (i < 0 || i >= values.length) ? null : values[i]; + } + +} diff --git a/src/main/java/net/montoyo/wd/miniserv/PacketReader.java b/src/main/java/net/montoyo/wd/miniserv/PacketReader.java new file mode 100644 index 0000000..d77b4d6 --- /dev/null +++ b/src/main/java/net/montoyo/wd/miniserv/PacketReader.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.miniserv; + +import net.montoyo.wd.utilities.Log; + +import java.nio.ByteBuffer; + +public final class PacketReader { + + private final byte[] sizeArray = new byte[4]; + private byte[] packetData; + private int pos = 0; + private boolean needSize = true; + + public final boolean readFrom(ByteBuffer buf) { + if(needSize) { + //Read packet size + if(readByteArray(sizeArray, buf)) { + int packetSize = (sizeArray[0] << 24) | (sizeArray[1] << 16) | (sizeArray[2] << 8) | sizeArray[3]; + needSize = false; + pos = 0; + + if(packetSize < 5 || packetSize > 65536) { + Log.warning("Got invalid packet from client of size %d, things won't go well...", packetSize); + return true; //Abort packet reading + } + + packetData = new byte[packetSize]; + } else + return false; + } + + return readByteArray(packetData, buf); + } + + private boolean readByteArray(byte[] dst, ByteBuffer src) { + int remaining = dst.length - pos; + int read = (src.remaining() >= remaining) ? remaining : src.remaining(); + + src.get(dst, pos, read); + pos += read; + + return (pos >= dst.length); + } + + public final byte[] getPacketData() { + return packetData; + } + + public final void reset() { + packetData = null; + pos = 0; + needSize = true; + } + +} diff --git a/src/main/java/net/montoyo/wd/miniserv/PacketWriter.java b/src/main/java/net/montoyo/wd/miniserv/PacketWriter.java new file mode 100644 index 0000000..40d31dc --- /dev/null +++ b/src/main/java/net/montoyo/wd/miniserv/PacketWriter.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.miniserv; + +import java.nio.ByteBuffer; + +public final class PacketWriter { + + private final byte[] sizeBytes = new byte[4]; + private byte[] packet; + private int pos = 0; + private boolean needToWriteSize = true; + + public final boolean writeTo(ByteBuffer bb) { + if(packet == null) + return true; //Next packet please + + if(needToWriteSize) { + if(writeByteArray(bb, sizeBytes)) { + needToWriteSize = false; + pos = 0; + } else + return false; + } + + if(writeByteArray(bb, packet)) { + packet = null; + return true; + } else + return false; + } + + private boolean writeByteArray(ByteBuffer dst, byte[] src) { + int remaining = src.length - pos; + int written = (dst.remaining() >= remaining) ? remaining : dst.remaining(); + dst.put(src, pos, written); + + pos += written; + return (pos >= src.length); + } + + public final void reset(byte[] pkt) { + final int len = pkt.length + 4; + sizeBytes[0] = (byte) ((len >> 24) & 0xFF); + sizeBytes[1] = (byte) ((len >> 16) & 0xFF); + sizeBytes[2] = (byte) ((len >> 8) & 0xFF); + sizeBytes[3] = (byte) ( len & 0xFF); + + packet = pkt; + pos = 0; + needToWriteSize = true; + } + +} diff --git a/src/main/java/net/montoyo/wd/miniserv/server/Server.java b/src/main/java/net/montoyo/wd/miniserv/server/Server.java new file mode 100644 index 0000000..fb747e5 --- /dev/null +++ b/src/main/java/net/montoyo/wd/miniserv/server/Server.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.miniserv.server; + +import net.montoyo.wd.utilities.Log; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.HashMap; + +public class Server extends Thread { + + private ServerSocketChannel server; + private Selector selector; + private int port = 25566; + private final ArrayList clientList = new ArrayList<>(); + private final HashMap clientMap = new HashMap<>(); + private final ByteBuffer readBuffer = ByteBuffer.allocateDirect(8192); + + public Server() { + setDaemon(true); + } + + @Override + public void start() { + try { + server = ServerSocketChannel.open(); + server.configureBlocking(false); + server.socket().bind(new InetSocketAddress(port)); + + selector = Selector.open(); + server.register(selector, SelectionKey.OP_ACCEPT); + } catch(Throwable t) { + t.printStackTrace(); + return; + } + + super.start(); + } + + @Override + public void run() { + boolean running = true; + + while(running) { + try { + loopUnsafe(); + } catch(Throwable t) { + t.printStackTrace(); + running = false; + } + } + } + + private void loopUnsafe() throws Throwable { + selector.select(); + + for(SelectionKey key: selector.selectedKeys()) { + if(key.isAcceptable()) { + SocketChannel chan; + + try { + chan = server.accept(); + } catch(Throwable t) { + Log.warningEx("Could not accept client", t); + chan = null; + } + + if(chan != null) { + chan.configureBlocking(false); + chan.register(selector, SelectionKey.OP_READ); + + ServerClient toAdd = new ServerClient(chan, selector); + clientMap.put(chan, toAdd); + clientList.add(toAdd); + } + } else if(key.isReadable()) { + ServerClient cli = clientMap.get(key.channel()); + + if(cli == null) + Log.warning("Received read info from unknown client"); + else { + try { + readBuffer.clear(); + int read = cli.getChannel().read(readBuffer); + + if(read < 0) + cli.setShouldRemove(); //End of stream + else { + readBuffer.position(0); + readBuffer.limit(read); + cli.readyRead(readBuffer); + } + } catch(Throwable t) { + Log.warningEx("Could not read data from client", t); + cli.setShouldRemove(); + } + } + } else if(key.isWritable()) { + ServerClient cli = clientMap.get(key.channel()); + + if(cli == null) + Log.warning("Received write info from unknown client"); + else { + try { + cli.readyWrite(); + } catch(Throwable t) { + Log.warningEx("Could not write data to client", t); + cli.setShouldRemove(); + } + } + } + } + + for(int i = clientList.size() - 1; i >= 0; i--) { + ServerClient cli = clientList.get(i); + + if(cli.shouldRemove()) + removeClient(cli); + } + } + + private void removeClient(ServerClient cli) { + clientMap.remove(cli.getChannel()); + clientList.remove(cli); + + try { + cli.getChannel().close(); + } catch(Throwable t) {} + } + +} diff --git a/src/main/java/net/montoyo/wd/miniserv/server/ServerClient.java b/src/main/java/net/montoyo/wd/miniserv/server/ServerClient.java new file mode 100644 index 0000000..f4ebc93 --- /dev/null +++ b/src/main/java/net/montoyo/wd/miniserv/server/ServerClient.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.miniserv.server; + +import net.montoyo.wd.miniserv.OutgoingPacket; +import net.montoyo.wd.miniserv.PacketID; +import net.montoyo.wd.miniserv.PacketReader; +import net.montoyo.wd.miniserv.PacketWriter; +import net.montoyo.wd.utilities.Log; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.ArrayDeque; + +public class ServerClient { + + private final SocketChannel socket; + private final Selector selector; + private SelectionKey writeKey; + private boolean remove; + private final ByteBuffer sendBuffer = ByteBuffer.allocateDirect(8192); + private final ArrayDeque sendQueue = new ArrayDeque<>(); + private final PacketReader packetReader = new PacketReader(); + private final PacketWriter packetWriter = new PacketWriter(); + + ServerClient(SocketChannel s, Selector ss) { + socket = s; + selector = ss; + sendBuffer.limit(0); //Set empty + } + + void readyRead(ByteBuffer bb) { + while(bb.remaining() > 0) { + if(packetReader.readFrom(bb)) { //End of packet + byte[] pkt = packetReader.getPacketData(); + if(pkt != null) { + try { + handlePacket(pkt); + } catch(IOException ex) { + Log.warningEx("IOException while trying to handle packet", ex); + } + } + + packetReader.reset(); + } + } + } + + private void handlePacket(byte[] pkt) throws IOException { + DataInputStream dis = new DataInputStream(new ByteArrayInputStream(pkt)); + PacketID pid = PacketID.fromInt(dis.readByte()); + + if(pid == null) { + Log.warning("Caught packet with invalid ID from client"); + return; + } + + OutgoingPacket response = null; + + switch(pid) { + case PING: + response = new OutgoingPacket(); + response.writeByte(PacketID.PING.ordinal()); + break; + + case BEGIN_FILE_UPLOAD: + break; + + case FILE_PART: + break; + + case GET_FILE: + break; + } + + if(response != null) + sendPacket(response); + } + + void readyWrite() throws Throwable { + if(sendBuffer.remaining() > 0 || fillSendBuffer()) { + if(socket.write(sendBuffer) < 0) + remove = true; + } else if(writeKey != null) { + writeKey.cancel(); + writeKey = null; + } + } + + void setShouldRemove() { + remove = true; + } + + public boolean shouldRemove() { + return remove; + } + + SocketChannel getChannel() { + return socket; + } + + private boolean fillSendBuffer() { + sendBuffer.clear(); + + do { + if(packetWriter.writeTo(sendBuffer)) { + OutgoingPacket pkt = sendQueue.poll(); + if(pkt == null) + return sendBuffer.remaining() > 0; + + packetWriter.reset(pkt.finish()); + } + } while(sendBuffer.remaining() > 0); + + return true; + } + + public void sendPacket(OutgoingPacket pkt) { + sendQueue.offer(pkt); + + if(writeKey == null) { + try { + writeKey = socket.register(selector, SelectionKey.OP_WRITE); + } catch(ClosedChannelException ex) { + Log.warningEx("Couldn't send packet", ex); + } + } + } + +}