Bug fix
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
package me.unlegitdqrk.fakeminecraftserver;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import me.unlegitdqrk.fakeminecraftserver.streams.MojewInputStream;
|
||||
import me.unlegitdqrk.fakeminecraftserver.streams.MojewOutputStream;
|
||||
|
||||
|
||||
/**
|
||||
* @author UnlegitDqrk
|
||||
*/
|
||||
|
||||
public class BasePacketHandler extends ChannelInboundHandlerAdapter {
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
MojewInputStream inputStream = new MojewInputStream((ByteBuf) msg);
|
||||
|
||||
// handshake
|
||||
int length = inputStream.readInt();
|
||||
int id = inputStream.readInt();
|
||||
|
||||
if (id == 0) {
|
||||
// status request
|
||||
try {
|
||||
int version = inputStream.readInt();
|
||||
String address = inputStream.readUTF();
|
||||
int port = inputStream.readUnsignedShort();
|
||||
int state = inputStream.readInt();
|
||||
System.out.println("Received request: " + length + ", " + id + ", " + version + ", " + address + ", " + port + ", " + state);
|
||||
} catch (Exception ignored) {
|
||||
// status request packet is sent inconsistently, so we ignore it
|
||||
}
|
||||
|
||||
inputStream.close();
|
||||
|
||||
// status response
|
||||
String response = gson.toJson(FakeMinecraftServer.response).replace(ChatConverter.ESCAPE + "", "\\u00A7"); // Mojew's parser needs this escaped (classic)
|
||||
|
||||
if (FakeMinecraftServer.response.favicon == null)
|
||||
System.out.println("Sending response: " + response);
|
||||
else
|
||||
System.out.println("Sent response with image data.");
|
||||
|
||||
MojewOutputStream outputStream = new MojewOutputStream(Unpooled.buffer());
|
||||
MojewOutputStream dataOutputStream = new MojewOutputStream(Unpooled.buffer());
|
||||
dataOutputStream.writeInt(0);
|
||||
dataOutputStream.writeUTF(response);
|
||||
dataOutputStream.close();
|
||||
outputStream.writeInt(dataOutputStream.writtenBytes());
|
||||
outputStream.write(dataOutputStream.getData());
|
||||
outputStream.close();
|
||||
ctx.writeAndFlush(outputStream.buffer());
|
||||
} else if (id == 1) {
|
||||
// ping request
|
||||
long time = inputStream.readLong();
|
||||
System.out.println("Received ping packet: " + length + ", " + id + ", " + time);
|
||||
|
||||
// ping response
|
||||
MojewOutputStream outputStream = new MojewOutputStream(Unpooled.buffer());
|
||||
MojewOutputStream dataOutputStream = new MojewOutputStream(Unpooled.buffer());
|
||||
dataOutputStream.writeInt(1);
|
||||
dataOutputStream.writeLong(time);
|
||||
dataOutputStream.close();
|
||||
outputStream.writeInt(dataOutputStream.writtenBytes());
|
||||
outputStream.write(dataOutputStream.getData());
|
||||
outputStream.close();
|
||||
ctx.writeAndFlush(outputStream.buffer());
|
||||
} else if (id == 2) {
|
||||
// TODO: Fixing kick message
|
||||
|
||||
// login attempt
|
||||
System.out.println("Received login packet: " + length + ", " + id);
|
||||
|
||||
// login response
|
||||
MojewOutputStream outputStream = new MojewOutputStream(Unpooled.buffer());
|
||||
MojewOutputStream dataOutputStream = new MojewOutputStream(Unpooled.buffer());
|
||||
dataOutputStream.writeInt(2);
|
||||
dataOutputStream.writeUTF("{text:"+ ChatConverter.replaceColors(FakeMinecraftServer.KICK_MESSAGE) +", color: white}");
|
||||
dataOutputStream.close();
|
||||
outputStream.writeInt(dataOutputStream.writtenBytes());
|
||||
outputStream.write(dataOutputStream.getData());
|
||||
outputStream.close();
|
||||
ctx.writeAndFlush(outputStream.buffer());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
cause.printStackTrace();
|
||||
ctx.close();
|
||||
}
|
||||
}
|
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
Copyright (c) 2012, md_5. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
The name of the author may not be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
You may not use the software for commercial software hosting services without
|
||||
written permission from the author.
|
||||
|
||||
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 OWNER 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.
|
||||
*/
|
||||
|
||||
package me.unlegitdqrk.fakeminecraftserver;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author UnlegitDqrk
|
||||
*/
|
||||
|
||||
public class ChatConverter {
|
||||
private static final char COLOR_CHAR = '\u00A7';
|
||||
private static final Pattern url = Pattern.compile("^(?:(https?)://)?([-\\w_\\.]{2,}\\.[a-z]{2,4})(/\\S*)?$");
|
||||
public static final char ESCAPE = '\u00A7';
|
||||
|
||||
public static String replaceColors(String text) {
|
||||
char[] chrarray = text.toCharArray();
|
||||
for (int index = 0; index < chrarray.length; index++) {
|
||||
char chr = chrarray[index];
|
||||
if (chr != '&')
|
||||
continue;
|
||||
if ((index + 1) == chrarray.length)
|
||||
break;
|
||||
char forward = chrarray[index + 1];
|
||||
if ((forward >= '0' && forward <= '9') || (forward >= 'a' && forward <= 'f') || (forward >= 'k' && forward <= 'r'))
|
||||
chrarray[index] = ESCAPE;
|
||||
}
|
||||
return new String(chrarray);
|
||||
}
|
||||
|
||||
public static Message toJSONChat(String txt) {
|
||||
Message msg = new Message();
|
||||
ArrayList<Message> parts = new ArrayList<>();
|
||||
StringBuilder buf = new StringBuilder();
|
||||
Matcher matcher = url.matcher(txt);
|
||||
for (int i = 0; i < txt.length(); i++) {
|
||||
char c = txt.charAt(i);
|
||||
if (c != COLOR_CHAR) {
|
||||
int pos = txt.indexOf(' ', i);
|
||||
if (pos == -1) pos = txt.length();
|
||||
if (matcher.region(i, pos).find()) { //Web link handling
|
||||
msg.text = buf.toString();
|
||||
buf = new StringBuilder();
|
||||
parts.add(msg);
|
||||
Message old = msg;
|
||||
msg = new Message(old);
|
||||
msg.underlined = true;
|
||||
msg.clickEvent = new ClickEvent();
|
||||
msg.clickEvent.action = "open_url";
|
||||
String urlString = txt.substring(i, pos);
|
||||
msg.text = msg.clickEvent.value = urlString;
|
||||
parts.add(msg);
|
||||
i += pos - i - 1;
|
||||
msg = new Message(old);
|
||||
continue;
|
||||
}
|
||||
buf.append(c);
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
c = txt.charAt(i);
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c += 32;
|
||||
}
|
||||
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || c == 'r') {
|
||||
msg.text = buf.toString();
|
||||
buf = new StringBuilder();
|
||||
parts.add(msg);
|
||||
msg = new Message();
|
||||
if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
|
||||
msg.color = Color.fromCode(Character.toString(c));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
switch (c) {
|
||||
case 'k':
|
||||
msg.obfuscated = true;
|
||||
break;
|
||||
case 'l':
|
||||
msg.bold = true;
|
||||
break;
|
||||
case 'm':
|
||||
msg.strikethrough = true;
|
||||
break;
|
||||
case 'n':
|
||||
msg.underlined = true;
|
||||
break;
|
||||
case 'o':
|
||||
msg.italic = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
msg.text = buf.toString();
|
||||
parts.add(msg);
|
||||
if (parts.get(0).text.equals(""))
|
||||
parts.remove(0); // occasionally empty text?
|
||||
Message base = parts.remove(0);
|
||||
if (parts.size() != 0)
|
||||
base.extra = new ArrayList<>(parts);
|
||||
return base;
|
||||
}
|
||||
}
|
||||
|
||||
class ClickEvent {
|
||||
public String action;
|
||||
public String value;
|
||||
}
|
||||
|
||||
enum Color {
|
||||
@SerializedName("black")
|
||||
BLACK("0"),
|
||||
@SerializedName("dark_blue")
|
||||
DARK_BLUE("1"),
|
||||
@SerializedName("dark_green")
|
||||
DARK_GREEN("2"),
|
||||
@SerializedName("dark_aqua")
|
||||
DARK_AQUA("3"),
|
||||
@SerializedName("dark_red")
|
||||
DARK_RED("4"),
|
||||
@SerializedName("purple")
|
||||
DARK_PURPLE("5"),
|
||||
@SerializedName("gold")
|
||||
GOLD("6"),
|
||||
@SerializedName("gray")
|
||||
GRAY("7"),
|
||||
@SerializedName("dark_gray")
|
||||
DARK_GRAY("8"),
|
||||
@SerializedName("blue")
|
||||
BLUE("9"),
|
||||
@SerializedName("green")
|
||||
GREEN("a"),
|
||||
@SerializedName("aqua")
|
||||
AQUA("b"),
|
||||
@SerializedName("red")
|
||||
RED("c"),
|
||||
@SerializedName("light_purple")
|
||||
LIGHT_PURPLE("d"),
|
||||
@SerializedName("yellow")
|
||||
YELLOW("e"),
|
||||
@SerializedName("white")
|
||||
WHITE("f");
|
||||
|
||||
public String code;
|
||||
|
||||
Color(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
|
||||
private static HashMap<String, Color> codeMap = new HashMap<>();
|
||||
|
||||
public static Color fromCode(String code) {
|
||||
return codeMap.get(code);
|
||||
}
|
||||
|
||||
static {
|
||||
for (Color color : values()) {
|
||||
codeMap.put(color.code, color);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package me.unlegitdqrk.fakeminecraftserver;
|
||||
|
||||
import me.unlegitdqrk.fakeminecraftserver.data.StatusResponse;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import static javax.imageio.ImageIO.read;
|
||||
import static javax.imageio.ImageIO.write;
|
||||
|
||||
/**
|
||||
* @author UnlegitDqrk
|
||||
*/
|
||||
|
||||
public class FakeMinecraftServer {
|
||||
public static StatusResponse response = null;
|
||||
|
||||
public static final int START_PORT = 25565;
|
||||
|
||||
public static final String ENGINE = "classic";
|
||||
|
||||
public static final int MAX_PLAYERS = 100;
|
||||
public static final int ONLINE_PLAYERS = 10;
|
||||
|
||||
public static final int PROTOCOL_VERSION = 47;
|
||||
public static final String PROTOCOL_TEXT = "1.8.8";
|
||||
|
||||
public static final String MOTD_LINE1 = "&4Ein fake Minecraftserver";
|
||||
public static final String MOTD_LINE2 = "&cDeveloped by UnlegitDqrk";
|
||||
|
||||
public static final String KICK_MESSAGE = "&4Just a Fakeserver!\n&aDeveloped by UnlegitDqrk";
|
||||
public static final List<String> SAMPLES = Arrays.asList("&4Fakeserver", "&5by", "&aUnlegitDqrk");
|
||||
|
||||
public static final File SERVER_ICON = new File("server-icon.png");
|
||||
|
||||
public static void start() throws Exception {
|
||||
String serverIconAsString = null;
|
||||
|
||||
if (SERVER_ICON.exists()) {
|
||||
BufferedImage image = read(SERVER_ICON);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
write(image, "png", baos);
|
||||
serverIconAsString = "data:image/png;base64," + DatatypeConverter.printBase64Binary(baos.toByteArray());
|
||||
} else System.err.println("Starting fake server without icon!");
|
||||
|
||||
String motd = MOTD_LINE1 + "\n" + MOTD_LINE2;
|
||||
|
||||
Message description;
|
||||
|
||||
if (ENGINE.equalsIgnoreCase("json"))
|
||||
description = ChatConverter.toJSONChat(ChatConverter.replaceColors(motd).replace("\\n", "\n"));
|
||||
else {
|
||||
description = new Message();
|
||||
description.text = ChatConverter.replaceColors(motd).replace("\\n", "\n");
|
||||
}
|
||||
|
||||
response = new StatusResponse(PROTOCOL_TEXT, PROTOCOL_VERSION,
|
||||
MAX_PLAYERS, ONLINE_PLAYERS,
|
||||
description, serverIconAsString);
|
||||
|
||||
new SLPServer(START_PORT).run();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package me.unlegitdqrk.fakeminecraftserver;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author UnlegitDqrk
|
||||
*/
|
||||
|
||||
public class Message {
|
||||
public String text;
|
||||
|
||||
public boolean bold;
|
||||
public boolean italic;
|
||||
public boolean underlined;
|
||||
public boolean strikethrough;
|
||||
public boolean obfuscated;
|
||||
public List<Message> extra;
|
||||
|
||||
public Color color;
|
||||
|
||||
public ClickEvent clickEvent;
|
||||
|
||||
public Message() {
|
||||
|
||||
}
|
||||
|
||||
public Message(Message old) {
|
||||
this.bold = old.bold;
|
||||
this.italic = old.italic;
|
||||
this.underlined = old.underlined;
|
||||
this.strikethrough = old.strikethrough;
|
||||
this.color = old.color;
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package me.unlegitdqrk.fakeminecraftserver;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
|
||||
/**
|
||||
* @author UnlegitDqrk
|
||||
*/
|
||||
|
||||
public class SLPServer {
|
||||
private int port;
|
||||
|
||||
public SLPServer(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public void run() throws Exception {
|
||||
EventLoopGroup bossGroup = new NioEventLoopGroup();
|
||||
EventLoopGroup workerGroup = new NioEventLoopGroup();
|
||||
|
||||
try {
|
||||
ServerBootstrap serverBootstrap = new ServerBootstrap();
|
||||
serverBootstrap.group(bossGroup, workerGroup)
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
public void initChannel(Channel channel) {
|
||||
channel.pipeline().addLast(new BasePacketHandler());
|
||||
}
|
||||
}).option(ChannelOption.SO_BACKLOG, 128)
|
||||
.childOption(ChannelOption.SO_KEEPALIVE, true);
|
||||
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
|
||||
channelFuture.channel().closeFuture().sync();
|
||||
} finally {
|
||||
workerGroup.shutdownGracefully();
|
||||
bossGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
package me.unlegitdqrk.fakeminecraftserver.data;
|
||||
|
||||
import me.unlegitdqrk.fakeminecraftserver.ChatConverter;
|
||||
import me.unlegitdqrk.fakeminecraftserver.FakeMinecraftServer;
|
||||
import me.unlegitdqrk.fakeminecraftserver.Message;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author UnlegitDqrk
|
||||
*/
|
||||
|
||||
public class StatusResponse {
|
||||
Version version;
|
||||
Players players;
|
||||
Message description;
|
||||
|
||||
public String favicon;
|
||||
|
||||
public StatusResponse(String name, int protocol, int max, int online, Message description,String favicon) {
|
||||
this.version = new Version(name, protocol);
|
||||
this.players = new Players(max, online);
|
||||
this.description = description;
|
||||
this.favicon = favicon;
|
||||
}
|
||||
|
||||
public class Version {
|
||||
String name;
|
||||
int protocol;
|
||||
|
||||
public Version(String name, int protocol) {
|
||||
this.name = name;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
}
|
||||
|
||||
class Players {
|
||||
int max;
|
||||
int online;
|
||||
List<Sample> sample;
|
||||
|
||||
Players(int max, int online) {
|
||||
this.max = max;
|
||||
this.online = online;
|
||||
this.sample = new ArrayList<>();
|
||||
|
||||
for (String sample : FakeMinecraftServer.SAMPLES) {
|
||||
this.sample.add(new Sample(ChatConverter.replaceColors(sample)));
|
||||
}
|
||||
}
|
||||
|
||||
class Sample {
|
||||
String name;
|
||||
String id;
|
||||
|
||||
Sample(String name) {
|
||||
this.name = name;
|
||||
this.id = UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package me.unlegitdqrk.fakeminecraftserver.streams;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @author UnlegitDqrk
|
||||
*/
|
||||
|
||||
public class MojewInputStream extends ByteBufInputStream {
|
||||
public MojewInputStream(ByteBuf buffer) {
|
||||
super(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readUTF() throws IOException {
|
||||
byte[] input = new byte[readInt()];
|
||||
readFully(input);
|
||||
return new String(input, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() throws IOException {
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
while (true) {
|
||||
int k = readByte();
|
||||
i |= (k & 0x7F) << j++ * 7;
|
||||
if (j > 5) throw new RuntimeException("VarInt too big");
|
||||
if ((k & 0x80) != 128) break;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package me.unlegitdqrk.fakeminecraftserver.streams;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author UnlegitDqrk
|
||||
*/
|
||||
|
||||
public class MojewOutputStream extends ByteBufOutputStream {
|
||||
public MojewOutputStream(ByteBuf buffer) {
|
||||
super(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeUTF(String s) throws IOException {
|
||||
byte[] data = s.getBytes(StandardCharsets.UTF_8);
|
||||
writeInt(s.length());
|
||||
write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeInt(int paramInt) throws IOException {
|
||||
while (true) {
|
||||
if ((paramInt & 0xFFFFFF80) == 0) {
|
||||
writeByte(paramInt);
|
||||
return;
|
||||
}
|
||||
writeByte(paramInt & 0x7F | 0x80);
|
||||
paramInt >>>= 7;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return Arrays.copyOfRange(buffer().array(), 0, writtenBytes());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user