diff --git a/pom.xml b/pom.xml index 09b43e7e..9edecc2a 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,14 @@ 9 + + + + jitpack.io + https://jitpack.io + + + @@ -70,14 +78,13 @@ 3.7.02 + - se.llbit + com.github.llbit jo-nbt - 1.3.0 + 1baae2f49a - - net.sourceforge.argparse4j @@ -98,6 +105,28 @@ 3.10 + + + dnsjava + dnsjava + 3.3.0 + + + + + org.slf4j + slf4j-api + 2.0.0-alpha1 + + + + + org.slf4j + slf4j-simple + 2.0.0-alpha0 + + + diff --git a/src/main/java/Launcher.java b/src/main/java/Launcher.java index e3b4ff04..ec1a85b3 100644 --- a/src/main/java/Launcher.java +++ b/src/main/java/Launcher.java @@ -74,6 +74,10 @@ private static Namespace getArguments(String[] args) { .setDefault(true) .type(boolean.class) .help("When false, set world type to a superflat void to prevent new chunks from being added."); + parser.addArgument("--enable-srv-lookup").dest("enable-srv-lookup") + .setDefault(true) + .type(boolean.class) + .help("When true, checks for true address using DNS service records"); Namespace ns = null; try { diff --git a/src/main/java/game/Game.java b/src/main/java/game/Game.java index e2e51d9a..93cf4138 100644 --- a/src/main/java/game/Game.java +++ b/src/main/java/game/Game.java @@ -51,6 +51,7 @@ public abstract class Game { private static EncryptionManager encryptionManager; private static CompressionManager compressionManager; private static boolean enableWorldGen; + private static boolean enableSrvLookup; public static int getDataVersion() { return dataVersion; @@ -105,6 +106,7 @@ public static void init(Namespace args) { GuiManager.showGui(); } enableWorldGen = args.getBoolean("enable-world-gen"); + enableSrvLookup = args.getBoolean("enable-srv-lookup"); versionHandler = VersionHandler.createVersionHandler(); } @@ -239,4 +241,8 @@ public static int getRenderDistance() { public static boolean isWorldGenEnabled() { return enableWorldGen; } + + public static boolean isSrvLookupEnabled() { + return enableSrvLookup; + } } diff --git a/src/main/java/game/UnsupportedMinecraftVersionException.java b/src/main/java/game/UnsupportedMinecraftVersionException.java new file mode 100644 index 00000000..47b88ca4 --- /dev/null +++ b/src/main/java/game/UnsupportedMinecraftVersionException.java @@ -0,0 +1,7 @@ +package game; + +public class UnsupportedMinecraftVersionException extends RuntimeException { + public UnsupportedMinecraftVersionException(String message) { + super(message); + } +} diff --git a/src/main/java/game/data/WorldManager.java b/src/main/java/game/data/WorldManager.java index 14e520ff..2d9812a3 100644 --- a/src/main/java/game/data/WorldManager.java +++ b/src/main/java/game/data/WorldManager.java @@ -12,6 +12,7 @@ import game.data.region.McaFile; import game.data.region.Region; import gui.GuiManager; +import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import proxy.CompressionManager; import se.llbit.nbt.ByteTag; @@ -37,6 +38,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Objects; @@ -66,6 +68,10 @@ public class WorldManager extends Thread { private static boolean writeChunks; + private static boolean isPaused; + + private static boolean isSaving; + private static ContainerManager containerManager; private WorldManager() { @@ -140,9 +146,8 @@ private static void saveLevelData() throws IOException { File levelDat = Paths.get(Game.getExportDirectory(), "level.dat").toFile(); // if there is no level.dat yet, make one from the default - // TODO: fix memory leak caused by duplicates in the NBT tags InputStream fileInput; - if (false && levelDat.isFile()) { + if (levelDat.isFile()) { fileInput = new FileInputStream(levelDat); } else { fileInput = WorldManager.class.getClassLoader().getResourceAsStream("level.dat"); @@ -339,10 +344,18 @@ public void run() { /** * Save the world. Will tell all regions to save their chunks. */ - private static void save() { + public static void save() { if (!writeChunks) { return; } + + // make sure we can't have two saving calls at once (due to save & exit) + if (isSaving) { + return; + } + isSaving = true; + + if (!regions.isEmpty()) { // convert the values to an array first to prevent blocking any threads Region[] r = regions.values().toArray(new Region[regions.size()]); @@ -368,6 +381,8 @@ private static void save() { // remove empty regions regions.entrySet().removeIf(el -> el.getValue().isEmpty()); + + isSaving = false; } public static ContainerManager getContainerManager() { @@ -377,6 +392,34 @@ public static ContainerManager getContainerManager() { return containerManager; } + public static void pauseSaving() { + isPaused = true; + } + + public static void resumeSaving() { + isPaused = false; + } + + public static void deleteAllExisting() { + regions = new HashMap<>(); + ChunkFactory.getInstance().clear(); + + try { + File dir = Paths.get(Game.getExportDirectory(), Game.getDimension().getPath(), "region").toFile(); + + if (dir.isDirectory()) { + FileUtils.cleanDirectory(dir); + } + } catch (IOException ex) { + System.out.println("Could not delete region files. Reason: " + ex.getMessage()); + } + + GuiManager.clearChunks(); + } + + public static boolean isPaused() { + return isPaused; + } } diff --git a/src/main/java/game/data/chunk/ChunkFactory.java b/src/main/java/game/data/chunk/ChunkFactory.java index 9d4a56a4..69b5710d 100644 --- a/src/main/java/game/data/chunk/ChunkFactory.java +++ b/src/main/java/game/data/chunk/ChunkFactory.java @@ -48,6 +48,10 @@ public static ChunkFactory getInstance() { } private ChunkFactory() { + clear(); + } + + public void clear() { this.tileEntities = new ConcurrentHashMap<>(); this.chunkEntities = new ConcurrentHashMap<>(); this.entities = new ConcurrentHashMap<>(); @@ -126,6 +130,11 @@ public void updateTileEntity(Coordinate3D position, SpecificTag entityData) { * Need a non-static method to do this as we cannot otherwise call notify */ public synchronized void addChunk(DataTypeProvider provider) { + // if the world manager is currently paused, discard this chunk + if (WorldManager.isPaused()) { + return; + } + unparsedChunks.add(new ChunkParserPair(provider, Game.getDimension())); notify(); } diff --git a/src/main/java/game/data/region/McaFile.java b/src/main/java/game/data/region/McaFile.java index 467d6980..622edfa7 100644 --- a/src/main/java/game/data/region/McaFile.java +++ b/src/main/java/game/data/region/McaFile.java @@ -24,8 +24,8 @@ public class McaFile { public final static int SECTOR_SIZE = 4096; private Map chunkMap; - private Path filePath; - private Coordinate2D regionLocation; + private final Path filePath; + private final Coordinate2D regionLocation; /** * Parse MCA from a given file location. diff --git a/src/main/java/game/data/registries/RegistryLoader.java b/src/main/java/game/data/registries/RegistryLoader.java index 05cf4d7c..44dadac7 100644 --- a/src/main/java/game/data/registries/RegistryLoader.java +++ b/src/main/java/game/data/registries/RegistryLoader.java @@ -2,6 +2,7 @@ import com.google.gson.Gson; +import game.UnsupportedMinecraftVersionException; import game.data.chunk.entity.EntityNames; import game.data.chunk.palette.GlobalPalette; import game.data.container.ItemRegistry; @@ -125,8 +126,13 @@ private void generateReports() throws IOException, InterruptedException { */ private void moveReports() throws IOException { ensureExists(destinationPath); - Files.move(REGISTRIES_GENERATED_PATH, registryPath, StandardCopyOption.REPLACE_EXISTING); - Files.move(BLOCKS_GENERATED_PATH, blocksPath, StandardCopyOption.REPLACE_EXISTING); + + if (versionSupportsGenerators()) { + Files.move(REGISTRIES_GENERATED_PATH, registryPath, StandardCopyOption.REPLACE_EXISTING); + } + if (versionSupportsBlockGenerator()) { + Files.move(BLOCKS_GENERATED_PATH, blocksPath, StandardCopyOption.REPLACE_EXISTING); + } } /** @@ -150,36 +156,48 @@ private void ensureExists(Path folder) { } public EntityNames generateEntityNames() throws IOException { - if (version.equals("1.12.2")) { + if (versionSupportsGenerators()) { + return EntityNames.fromRegistry(new FileInputStream(registryPath.toFile())); + } else if (version.equals("1.12.2")) { return EntityNames.fromJson("1.12.2"); + } else if (version.equals("1.13.2")) { + return EntityNames.fromJson("1.13.2"); } else { - return EntityNames.fromRegistry(new FileInputStream(registryPath.toFile())); + throw new UnsupportedMinecraftVersionException(version); } } public GlobalPalette generateGlobalPalette() throws IOException { - if (version.equals("1.12.2")) { - return new GlobalPalette("1.12.2"); - } else { + if (versionSupportsBlockGenerator()) { return new GlobalPalette(new FileInputStream(blocksPath.toFile())); + } else { + return new GlobalPalette("1.12.2"); } } public MenuRegistry generateMenuRegistry() throws IOException { - if (version.equals("1.12.2")) { - return new MenuRegistry(); - } else { + if (versionSupportsGenerators()) { return MenuRegistry.fromRegistry(new FileInputStream(registryPath.toFile())); + } else { + return new MenuRegistry(); } } public ItemRegistry generateItemRegistry() throws IOException { - if (version.equals("1.12.2")) { - return new ItemRegistry(); - } else { + if (versionSupportsGenerators()) { return ItemRegistry.fromRegistry(new FileInputStream(registryPath.toFile())); + } else { + return new ItemRegistry(); } } + + public boolean versionSupportsBlockGenerator() { + return !version.startsWith("1.12"); + } + + public boolean versionSupportsGenerators() { + return versionSupportsBlockGenerator() && !version.startsWith("1.13"); + } } class VersionMap extends HashMap { } diff --git a/src/main/java/gui/CanvasHandler.java b/src/main/java/gui/CanvasHandler.java index b6f5301d..cb0db4d2 100644 --- a/src/main/java/gui/CanvasHandler.java +++ b/src/main/java/gui/CanvasHandler.java @@ -57,6 +57,13 @@ public class CanvasHandler extends JPanel implements ActionListener { bindScroll(); } + public void clearChunks() { + chunkMap.clear(); + drawableChunks.clear(); + + hasChanged = true; + } + private void bindScroll() { this.addMouseWheelListener(mouseWheelEvent -> { this.renderDistance = this.renderDistance + (mouseWheelEvent.getWheelRotation() * 2); diff --git a/src/main/java/gui/GuiManager.java b/src/main/java/gui/GuiManager.java index bcf296f6..c6f47c72 100644 --- a/src/main/java/gui/GuiManager.java +++ b/src/main/java/gui/GuiManager.java @@ -75,5 +75,11 @@ public static void drawExistingChunks(List existing) { existing.forEach(chunkGraphicsHandler::setChunkExists); } } + + public static void clearChunks() { + if (chunkGraphicsHandler != null) { + chunkGraphicsHandler.clearChunks(); + } + } } diff --git a/src/main/java/gui/RightClickMenu.java b/src/main/java/gui/RightClickMenu.java index ea9f725b..b6efccb5 100644 --- a/src/main/java/gui/RightClickMenu.java +++ b/src/main/java/gui/RightClickMenu.java @@ -5,12 +5,44 @@ import java.awt.event.ActionEvent; import java.io.IOException; import javax.swing.AbstractAction; +import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; public class RightClickMenu extends JPopupMenu { + final static String PROMPT_PAUSE = "Pause chunk saving"; + final static String PROMPT_RESUME = "Resume chunk saving"; + public RightClickMenu(CanvasHandler handler) { + JMenuItem togglePause = new JMenuItem(PROMPT_PAUSE); + + togglePause.addActionListener(new AbstractAction("Pause chunk saving") { + @Override + public void actionPerformed(ActionEvent e) { + if (WorldManager.isPaused()) { + System.out.println("Resuming"); + WorldManager.resumeSaving(); + togglePause.setText(PROMPT_PAUSE); + } else { + System.out.println("Pausing"); + WorldManager.pauseSaving(); + togglePause.setText(PROMPT_RESUME); + } + } + }); + add(togglePause); + + add(new JMenuItem(new AbstractAction("Delete all downloaded chunks") { + @Override + public void actionPerformed(ActionEvent e) { + WorldManager.deleteAllExisting(); + } + })); + + + add(new Separator()); + add(new JMenuItem(new AbstractAction("Save overview to file") { @Override public void actionPerformed(ActionEvent e) { @@ -30,9 +62,10 @@ public void actionPerformed(ActionEvent e) { } })); - add(new JMenuItem(new AbstractAction("Exit") { + add(new JMenuItem(new AbstractAction("Save & Exit") { @Override public void actionPerformed(ActionEvent e) { + WorldManager.save(); System.exit(0); } })); diff --git a/src/main/java/proxy/ProxyServer.java b/src/main/java/proxy/ProxyServer.java index 4e89d114..e974e418 100644 --- a/src/main/java/proxy/ProxyServer.java +++ b/src/main/java/proxy/ProxyServer.java @@ -2,6 +2,10 @@ import game.Game; import game.NetworkMode; +import org.xbill.DNS.Lookup; +import org.xbill.DNS.Record; +import org.xbill.DNS.SRVRecord; +import org.xbill.DNS.Type; import packets.DataReader; import java.io.InputStream; @@ -48,6 +52,10 @@ public void runServer(DataReader onServerBoundPacket, DataReader onClientBoundPa @Override public void run() { + if (Game.isSrvLookupEnabled()) { + performSrvLookup(); + } + String friendlyHost = host + (portRemote == DEFAULT_PORT ? "" : ":" + portRemote); System.out.println("Starting proxy for " + friendlyHost + ". Make sure to connect to localhost:" + portLocal + " instead of the regular server address."); @@ -75,8 +83,7 @@ public void run() { // If the server cannot connect, close client connection attempt(() -> server.set(new Socket(host, portRemote)), (ex) -> { - System.out.println("Cannot connect to " + friendlyHost + ". The server may be down or on a different address."); - + System.out.println("Cannot connect to " + friendlyHost + ". The server may be down or on a different address. (" + ex.getClass().getCanonicalName() + ")"); attempt(client.get()::close); }); @@ -130,6 +137,26 @@ public void run() { } } + /** + * Checks for DNS service records of the form _minecraft._tcp.example.com. If they exist, we will replace the + * current host and port with the ones found there. + */ + private void performSrvLookup() { + attempt(() -> { + Record[] records = new Lookup("_minecraft._tcp." + host, Type.SRV).run(); + + // no records were found + if (records == null || records.length == 0) { + return; + } + + // if there's multiple records, we'll just take the first one + SRVRecord srvRecord = (SRVRecord) records[0]; + portRemote = srvRecord.getPort(); + host = srvRecord.getTarget().toString(true); + }); + } + /** * Simple method to make exception handling cleaner. */ diff --git a/src/main/resources/entities-1.13.2.json b/src/main/resources/entities-1.13.2.json new file mode 100644 index 00000000..0ee09ec9 --- /dev/null +++ b/src/main/resources/entities-1.13.2.json @@ -0,0 +1,99 @@ +{ + "entities": { + "0": "minecraft:area_effect_cloud", + "1": "minecraft:armor_stand", + "2": "minecraft:arrow", + "3": "minecraft:bat", + "4": "minecraft:blaze", + "5": "minecraft:boat", + "6": "minecraft:cave_spider", + "7": "minecraft:chicken", + "8": "minecraft:cod", + "9": "minecraft:cow", + "10": "minecraft:creeper", + "11": "minecraft:donkey", + "12": "minecraft:dolphin", + "13": "minecraft:dragon_fireball", + "14": "minecraft:drowned", + "15": "minecraft:elder_guardian", + "16": "minecraft:end_crystal", + "17": "minecraft:ender_dragon", + "18": "minecraft:enderman", + "19": "minecraft:endermite", + "20": "minecraft:evoker_fangs", + "21": "minecraft:evoker", + "22": "minecraft:experience_orb", + "23": "minecraft:eye_of_ender", + "24": "minecraft:falling_block", + "25": "minecraft:fireworks_rocket", + "26": "minecraft:ghast", + "27": "minecraft:giant", + "28": "minecraft:guardian", + "29": "minecraft:horse", + "30": "minecraft:husk", + "31": "minecraft:illusioner", + "32": "minecraft:item", + "33": "minecraft:item_frame", + "34": "minecraft:fireball", + "35": "minecraft:leash_knot", + "36": "minecraft:llama", + "37": "minecraft:llama_spit", + "38": "minecraft:magma_cube", + "39": "minecraft:minecart", + "40": "minecraft:chest_minecart", + "41": "minecraft:commandblock_minecart", + "42": "minecraft:furnace_minecart", + "43": "minecraft:hopper_minecart", + "44": "minecraft:spawner_minecart", + "45": "minecraft:tnt_minecart", + "46": "minecraft:mule", + "47": "minecraft:mooshroom", + "48": "minecraft:ocelot", + "49": "minecraft:painting", + "50": "minecraft:parrot", + "51": "minecraft:pig", + "52": "minecraft:pufferfish", + "53": "minecraft:zombie_pigman", + "54": "minecraft:polar_bear", + "55": "minecraft:tnt", + "56": "minecraft:rabbit", + "57": "minecraft:salmon", + "58": "minecraft:sheep", + "59": "minecraft:shulker", + "60": "minecraft:shulker_bullet", + "61": "minecraft:silverfish", + "62": "minecraft:skeleton", + "63": "minecraft:skeleton_horse", + "64": "minecraft:slime", + "65": "minecraft:small_fireball", + "66": "minecraft:snow_golem", + "67": "minecraft:snowball", + "68": "minecraft:spectral_arrow", + "69": "minecraft:spider", + "70": "minecraft:squid", + "71": "minecraft:stray", + "72": "minecraft:tropical_fish", + "73": "minecraft:turtle", + "74": "minecraft:egg", + "75": "minecraft:ender_pearl", + "76": "minecraft:experience_bottle", + "77": "minecraft:potion", + "78": "minecraft:vex", + "79": "minecraft:villager", + "80": "minecraft:iron_golem}", + "81": "minecraft:vindicator", + "82": "minecraft:witch", + "83": "minecraft:wither", + "84": "minecraft:wither_skeleton", + "85": "minecraft:wither_skull", + "86": "minecraft:wolf", + "87": "minecraft:zombie", + "88": "minecraft:zombie_horse", + "89": "minecraft:zombie_villager", + "90": "minecraft:phantom", + "91": "minecraft:lightning_bolt", + "92": "minecraft:player", + "93": "minecraft:fishing_bobber", + "94": "minecraft:trident" + } +} \ No newline at end of file diff --git a/src/main/resources/server.json b/src/main/resources/server.json index 740fe014..7f5572fd 100644 --- a/src/main/resources/server.json +++ b/src/main/resources/server.json @@ -12,5 +12,6 @@ "1.15.2": "https://launcher.mojang.com/v1/objects/bb2b6b1aefcd70dfd1892149ac3a215f6c636b07/server.jar", "1.16.0": "https://launcher.mojang.com/v1/objects/a0d03225615ba897619220e256a266cb33a44b6b/server.jar", "1.16.1": "https://launcher.mojang.com/v1/objects/a412fd69db1f81db3f511c1463fd304675244077/server.jar", - "1.16.2": "https://launcher.mojang.com/v1/objects/c5f6fb23c3876461d46ec380421e42b289789530/server.jar" + "1.16.2": "https://launcher.mojang.com/v1/objects/c5f6fb23c3876461d46ec380421e42b289789530/server.jar", + "1.16.3": "https://launcher.mojang.com/v1/objects/f02f4473dbf152c23d7d484952121db0b36698cb/server.jar" } \ No newline at end of file diff --git a/src/main/resources/version.json b/src/main/resources/version.json index 50a52451..385b0710 100644 --- a/src/main/resources/version.json +++ b/src/main/resources/version.json @@ -132,7 +132,7 @@ "dataVersion": 2578, "clientbound": { "0x20": "chunk_data", - "0x1E": "chunk_unload", + "0x1C": "chunk_unload", "0x34": "player_position_look", "0x2B": "player_vehicle_move", "0x23": "chunk_update_light",