package com.loohp.limbo; import java.awt.GraphicsEnvironment; import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.file.Files; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.loohp.limbo.Commands.CommandSender; import com.loohp.limbo.Events.EventsManager; import com.loohp.limbo.File.ServerProperties; import com.loohp.limbo.GUI.GUI; import com.loohp.limbo.Location.Location; import com.loohp.limbo.Metrics.Metrics; import com.loohp.limbo.Permissions.PermissionsManager; import com.loohp.limbo.Player.Player; import com.loohp.limbo.Plugins.LimboPlugin; import com.loohp.limbo.Plugins.PluginManager; import com.loohp.limbo.Server.ServerConnection; import com.loohp.limbo.Server.Packets.Packet; import com.loohp.limbo.Server.Packets.PacketIn; import com.loohp.limbo.Server.Packets.PacketOut; import com.loohp.limbo.Utils.CustomStringUtils; import com.loohp.limbo.Utils.ImageUtils; import com.loohp.limbo.Utils.NetworkUtils; import com.loohp.limbo.World.DimensionRegistry; import com.loohp.limbo.World.Environment; import com.loohp.limbo.World.Schematic; import com.loohp.limbo.World.World; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.chat.ComponentSerializer; import net.querz.nbt.io.NBTUtil; import net.querz.nbt.tag.CompoundTag; public class Limbo { private static Limbo instance; public static boolean noGui = false; public static void main(String args[]) throws IOException, ParseException, NumberFormatException, ClassNotFoundException, InterruptedException { for (String flag : args) { if (flag.equals("--nogui")) { noGui = true; } else { System.out.println("Accepted flags:"); System.out.println(" --nogui "); System.out.println(); System.out.println("Press [enter] to quit"); System.exit(0); } } if (GraphicsEnvironment.isHeadless()) { noGui = true; } if (!noGui) { System.out.println("Launching Server GUI.. Add \"--nogui\" in launch arguments to disable"); Thread t1 = new Thread(new Runnable() { @Override public void run() { GUI.main(); } }); t1.start(); } new Limbo(); } public static Limbo getInstance() { return instance; } //=========================== public final String serverImplementationVersion = "1.16.3"; public final int serverImplmentationProtocol = 753; private ServerConnection server; private Console console; private List worlds = new ArrayList<>(); private Map playersByName = new HashMap<>(); private Map playersByUUID = new HashMap<>(); private ServerProperties properties; private PluginManager pluginManager; private EventsManager eventsManager; private PermissionsManager permissionManager; private File pluginFolder; private File internalDataFolder; private DimensionRegistry dimensionRegistry; private Metrics metrics; public AtomicInteger entityIdCount = new AtomicInteger(); @SuppressWarnings("deprecation") private Unsafe unsafe = new Unsafe(); @SuppressWarnings("unchecked") public Limbo() throws IOException, ParseException, NumberFormatException, ClassNotFoundException, InterruptedException { instance = this; if (!noGui) { while (!GUI.loadFinish) { TimeUnit.MILLISECONDS.sleep(500); } console = new Console(null, System.out, System.err); } else { console = new Console(System.in, System.out, System.err); } console.sendMessage("Loading Limbo Version " + new BufferedReader(new InputStreamReader(Limbo.class.getClassLoader().getResourceAsStream("META-INF/MANIFEST.MF"))).lines().filter(each -> each.startsWith("Limbo-Version:")).findFirst().orElse("Limbo-Version: unknown").substring(14).trim()); String spName = "server.properties"; File sp = new File(spName); if (!sp.exists()) { try (InputStream in = getClass().getClassLoader().getResourceAsStream(spName)) { Files.copy(in, sp.toPath()); } catch (IOException e) { e.printStackTrace(); } } properties = new ServerProperties(sp); if (!properties.isBungeecord()) { console.sendMessage("If you are using bungeecord, consider turning that on in the settings!"); } else { console.sendMessage("Starting Limbo server in bungeecord mode!"); } internalDataFolder = new File("internal_data"); if (!internalDataFolder.exists()) { internalDataFolder.mkdirs(); } String mappingName = "mapping.json"; File mappingFile = new File(internalDataFolder, mappingName); if (!mappingFile.exists()) { try (InputStream in = getClass().getClassLoader().getResourceAsStream(mappingName)) { Files.copy(in, mappingFile.toPath()); } catch (IOException e) { e.printStackTrace(); } } console.sendMessage("Loading packet id mappings from mapping.json ..."); JSONObject json = (JSONObject) new JSONParser().parse(new FileReader(mappingFile)); String classPrefix = Packet.class.getName().substring(0, Packet.class.getName().lastIndexOf(".") + 1); int mappingsCount = 0; Map> HandshakeIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("HandshakeIn")).keySet()) { int packetId = Integer.decode((String) key); HandshakeIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("HandshakeIn")).get(key))); } Packet.setHandshakeIn(HandshakeIn); mappingsCount += HandshakeIn.size(); Map> StatusIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("StatusIn")).keySet()) { int packetId = Integer.decode((String) key); StatusIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("StatusIn")).get(key))); } Packet.setStatusIn(StatusIn); mappingsCount += StatusIn.size(); Map, Integer> StatusOut = new HashMap<>(); for (Object key : ((JSONObject) json.get("StatusOut")).keySet()) { Class packetClass = (Class) Class.forName(classPrefix + (String) key); StatusOut.put(packetClass, Integer.decode((String) ((JSONObject) json.get("StatusOut")).get(key))); } Packet.setStatusOut(StatusOut); mappingsCount += StatusOut.size(); Map> LoginIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("LoginIn")).keySet()) { int packetId = Integer.decode((String) key); LoginIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("LoginIn")).get(key))); } Packet.setLoginIn(LoginIn); mappingsCount += LoginIn.size(); Map, Integer> LoginOut = new HashMap<>(); for (Object key : ((JSONObject) json.get("LoginOut")).keySet()) { Class packetClass = (Class) Class.forName(classPrefix + (String) key); LoginOut.put(packetClass, Integer.decode((String) ((JSONObject) json.get("LoginOut")).get(key))); } Packet.setLoginOut(LoginOut); mappingsCount += LoginOut.size(); Map> PlayIn = new HashMap<>(); for (Object key : ((JSONObject) json.get("PlayIn")).keySet()) { int packetId = Integer.decode((String) key); PlayIn.put(packetId, (Class) Class.forName(classPrefix + (String) ((JSONObject) json.get("PlayIn")).get(key))); } Packet.setPlayIn(PlayIn); mappingsCount += PlayIn.size(); Map, Integer> PlayOut = new HashMap<>(); for (Object key : ((JSONObject) json.get("PlayOut")).keySet()) { Class packetClass = (Class) Class.forName(classPrefix + (String) key); PlayOut.put(packetClass, Integer.decode((String) ((JSONObject) json.get("PlayOut")).get(key))); } Packet.setPlayOut(PlayOut); mappingsCount += PlayOut.size(); console.sendMessage("Loaded all " + mappingsCount + " packet id mappings!"); dimensionRegistry = new DimensionRegistry(); worlds.add(loadDefaultWorld()); Location spawn = properties.getWorldSpawn(); properties.setWorldSpawn(new Location(getWorld(properties.getLevelName().getKey()), spawn.getX(), spawn.getY(), spawn.getZ(), spawn.getYaw(), spawn.getPitch())); if (!NetworkUtils.available(properties.getServerPort())) { console.sendMessage(""); console.sendMessage("*****FAILED TO BIND PORT [" + properties.getServerPort() + "]*****"); console.sendMessage("*****PORT ALREADY IN USE*****"); console.sendMessage("*****PERHAPS ANOTHER INSTANCE OF THE SERVER IS ALREADY RUNNING?*****"); console.sendMessage(""); System.exit(2); } String permissionName = "permission.yml"; File permissionFile = new File(permissionName); if (!permissionFile.exists()) { try (InputStream in = getClass().getClassLoader().getResourceAsStream(permissionName)) { Files.copy(in, permissionFile.toPath()); } catch (IOException e) { e.printStackTrace(); } } permissionManager = new PermissionsManager(); permissionManager.loadDefaultPermissionFile(permissionFile); eventsManager = new EventsManager(); pluginFolder = new File("plugins"); pluginFolder.mkdirs(); File defaultCommandsJar = new File(pluginFolder, "LimboDefaultCmd.jar"); defaultCommandsJar.delete(); console.sendMessage("Downloading limbo default commands module from github..."); ReadableByteChannel rbc = Channels.newChannel(new URL("https://github.com/LOOHP/Limbo/raw/master/modules/LimboDefaultCmd.jar").openStream()); FileOutputStream fos = new FileOutputStream(defaultCommandsJar); fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); fos.close(); pluginManager = new PluginManager(pluginFolder); for (LimboPlugin plugin : Limbo.getInstance().getPluginManager().getPlugins()) { console.sendMessage("Enabling plugin " + plugin.getName() + " " + plugin.getInfo().getVersion()); plugin.onEnable(); } server = new ServerConnection(properties.getServerIp(), properties.getServerPort()); metrics = new Metrics(); console.run(); } @Deprecated public Unsafe getUnsafe() { return unsafe; } public DimensionRegistry getDimensionRegistry() { return dimensionRegistry; } public String getServerImplementationVersion() { return serverImplementationVersion; } public int getServerImplmentationProtocol() { return serverImplmentationProtocol; } public PermissionsManager getPermissionsManager() { return permissionManager; } public File getInternalDataFolder() { return internalDataFolder; } public EventsManager getEventsManager() { return eventsManager; } public File getPluginFolder() { return pluginFolder; } public PluginManager getPluginManager() { return pluginManager; } private World loadDefaultWorld() throws IOException { console.sendMessage("Loading world " + properties.getLevelName() + " with the schematic file " + properties.getSchemFileName() + " ..."); File schem = new File(properties.getSchemFileName()); if (!schem.exists()) { console.sendMessage("Schemetic file " + properties.getSchemFileName() + " for world " + properties.getLevelName() + " not found!"); console.sendMessage("Server will exit!"); System.exit(1); return null; } World world = Schematic.toWorld(properties.getLevelName().getKey(), Environment.fromNamespacedKey(properties.getLevelDimension()), (CompoundTag) NBTUtil.read(schem).getTag()); console.sendMessage("Loaded world " + properties.getLevelName() + "!"); return world; } public ServerProperties getServerProperties() { return properties; } public ServerConnection getServerConnection() { return server; } public Console getConsole() { return console; } public Metrics getMetrics() { return metrics; } public Set getPlayers() { return new HashSet<>(playersByUUID.values()); } public Player getPlayer(String name) { return playersByName.get(name); } public Player getPlayer(UUID uuid) { return playersByUUID.get(uuid); } public void addPlayer(Player player) { playersByName.put(player.getName(), player); playersByUUID.put(player.getUUID(), player); } public void removePlayer(Player player) { playersByName.remove(player.getName()); playersByUUID.remove(player.getUUID()); } public List getWorlds() { return new ArrayList<>(worlds); } public World getWorld(String name) { for (World world : worlds) { if (world.getName().equalsIgnoreCase(name)) { return world; } } return null; } @SuppressWarnings("unchecked") public String buildServerListResponseJson(String version, int protocol, BaseComponent[] motd, int maxPlayers, int playersOnline, BufferedImage favicon) throws IOException { JSONObject json = new JSONObject(); JSONObject versionJson = new JSONObject(); versionJson.put("name", version); versionJson.put("protocol", protocol); json.put("version", versionJson); JSONObject playersJson = new JSONObject(); playersJson.put("max", maxPlayers); playersJson.put("online", playersOnline); json.put("players", playersJson); json.put("description", "%MOTD%"); if (favicon != null) { if (favicon.getWidth() == 64 && favicon.getHeight() == 64) { String base64 = "data:image/png;base64," + ImageUtils.imgToBase64String(favicon, "png"); json.put("favicon", base64); } else { console.sendMessage("Server List Favicon must be 64 x 64 in size!"); } } JSONObject modInfoJson = new JSONObject(); modInfoJson.put("type", "FML"); modInfoJson.put("modList", new JSONArray()); json.put("modinfo", modInfoJson); TreeMap treeMap = new TreeMap(String.CASE_INSENSITIVE_ORDER); treeMap.putAll(json); Gson g = new GsonBuilder().create(); return g.toJson(treeMap).replace("\"%MOTD%\"", ComponentSerializer.toString(motd)); } public String buildLegacyPingResponse(String version, BaseComponent[] motd, int maxPlayers, int playersOnline) { String begin = "§1"; return String.join("\00", begin, "127", version, String.join("", Arrays.asList(motd).stream().map(each -> each.toLegacyText()).collect(Collectors.toList())), String.valueOf(playersOnline), String.valueOf(maxPlayers)); } public void stopServer() { console.sendMessage("Stopping Server..."); for (LimboPlugin plugin : Limbo.getInstance().getPluginManager().getPlugins()) { console.sendMessage("Disabling plugin " + plugin.getName() + " " + plugin.getInfo().getVersion()); plugin.onDisable(); } for (Player player : getPlayers()) { player.disconnect("Server closed"); } while (!getPlayers().isEmpty()) { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } console.sendMessage("Server closed"); console.logs.close(); System.exit(0); } public int getNextEntityId() { if (entityIdCount.get() == Integer.MAX_VALUE) { return entityIdCount.getAndSet(0); } else { return entityIdCount.getAndIncrement(); } } public void dispatchCommand(CommandSender sender, String str) { String[] command; if (str.startsWith("/")) { command = CustomStringUtils.splitStringToArgs(str.substring(1)); } else { command = CustomStringUtils.splitStringToArgs(str); } dispatchCommand(sender, command); } public void dispatchCommand(CommandSender sender, String... args) { try { Limbo.getInstance().getPluginManager().fireExecutors(sender, args); } catch (Exception e) { e.printStackTrace(); } } }