diff --git a/cache/src/main/java/net/runelite/cache/HeightMapDumper.java b/cache/src/main/java/net/runelite/cache/HeightMapDumper.java new file mode 100644 index 0000000000..85d8745fb0 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/HeightMapDumper.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2016, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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 net.runelite.cache; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.IOException; +import net.runelite.cache.fs.Store; +import net.runelite.cache.region.Region; +import net.runelite.cache.region.RegionLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HeightMapDumper +{ + private static final Logger logger = LoggerFactory.getLogger(HeightMapDumper.class); + + private static final int MAP_SCALE = 1; + + private final Store store; + private RegionLoader regionLoader; + + public HeightMapDumper(Store store) + { + this.store = store; + } + + public void load() throws IOException + { + regionLoader = new RegionLoader(); + regionLoader.loadRegions(store); + regionLoader.calculateBounds(); + } + + public BufferedImage drawHeightMap(int z) + { + int minX = regionLoader.getLowestX().getBaseX(); + int minY = regionLoader.getLowestY().getBaseY(); + + int maxX = regionLoader.getHighestX().getBaseX() + Region.X; + int maxY = regionLoader.getHighestY().getBaseY() + Region.Y; + + int dimX = maxX - minX; + int dimY = maxY - minY; + + dimX *= MAP_SCALE; + dimY *= MAP_SCALE; + + logger.info("Map image dimensions: {}px x {}px, {}px per map square ({} MB)", dimX, dimY, MAP_SCALE, (dimX * dimY / 1024 / 1024)); + + BufferedImage image = new BufferedImage(dimX, dimY, BufferedImage.TYPE_INT_RGB); + draw(image, z); + return image; + } + + private void draw(BufferedImage image, int z) + { + int max = Integer.MIN_VALUE; + int min = Integer.MAX_VALUE; + + for (Region region : regionLoader.getRegions()) + { + int baseX = region.getBaseX(); + int baseY = region.getBaseY(); + + // to pixel X + int drawBaseX = baseX - regionLoader.getLowestX().getBaseX(); + + // to pixel Y. top most y is 0, but the top most + // region has the greatest y, so invert + int drawBaseY = regionLoader.getHighestY().getBaseY() - baseY; + + for (int x = 0; x < Region.X; ++x) + { + int drawX = drawBaseX + x; + + for (int y = 0; y < Region.Y; ++y) + { + int drawY = drawBaseY + (Region.Y - 1 - y); + + int height = region.getTileHeight(z, x, y); + if (height > max) + { + max = height; + } + if (height < min) + { + min = height; + } + + int rgb = toColor(height); + + drawMapSquare(image, drawX, drawY, rgb); + } + } + } + System.out.println("max " + max); + System.out.println("min " + min); + } + + private int toColor(int height) + { + // height seems to be between -2040 and 0, inclusive + height = -height; + // Convert to between 0 and 1 + float color = (float) height / 2040f; + + assert color >= 0.0f && color <= 1.0f; + + return new Color(color, color, color).getRGB(); + } + + private void drawMapSquare(BufferedImage image, int x, int y, int rgb) + { + x *= MAP_SCALE; + y *= MAP_SCALE; + + for (int i = 0; i < MAP_SCALE; ++i) + { + for (int j = 0; j < MAP_SCALE; ++j) + { + image.setRGB(x + i, y + j, rgb); + } + } + } +} diff --git a/cache/src/main/java/net/runelite/cache/MapImageDumper.java b/cache/src/main/java/net/runelite/cache/MapImageDumper.java index 67f47c0a33..10edf9f825 100644 --- a/cache/src/main/java/net/runelite/cache/MapImageDumper.java +++ b/cache/src/main/java/net/runelite/cache/MapImageDumper.java @@ -57,8 +57,8 @@ import net.runelite.cache.fs.Store; import net.runelite.cache.io.InputStream; import net.runelite.cache.region.Location; import net.runelite.cache.region.Region; +import net.runelite.cache.region.RegionLoader; import net.runelite.cache.util.Djb2; -import net.runelite.cache.util.XteaKeyManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,7 +66,6 @@ public class MapImageDumper { private static final Logger logger = LoggerFactory.getLogger(MapImageDumper.class); - private static final int MAX_REGION = 32768; private static final int MAP_SCALE = 2; // this squared is the number of pixels per map square private static final int MAPICON_MAX_WIDTH = 5; // scale minimap icons down to this size so they fit.. private static final int MAPICON_MAX_HEIGHT = 6; @@ -82,9 +81,7 @@ public class MapImageDumper private final Map objects = new HashMap<>(); private final Map mapFunctions = new HashMap<>(); // quest, water, etc - private final List regions = new ArrayList<>(); - private Region lowestX = null, lowestY = null; - private Region highestX = null, highestY = null; + private RegionLoader regionLoader; private boolean labelRegions; private boolean outlineRegions; @@ -107,11 +104,11 @@ public class MapImageDumper public BufferedImage drawMap(int z) throws IOException { - int minX = lowestX.getBaseX(); - int minY = lowestY.getBaseY(); + int minX = regionLoader.getLowestX().getBaseX(); + int minY = regionLoader.getLowestY().getBaseY(); - int maxX = highestX.getBaseX() + Region.X; - int maxY = highestY.getBaseY() + Region.Y; + int maxX = regionLoader.getHighestX().getBaseX() + Region.X; + int maxY = regionLoader.getHighestY().getBaseY() + Region.Y; int dimX = maxX - minX; int dimY = maxY - minY; @@ -127,17 +124,17 @@ public class MapImageDumper drawOverlay(image, z); // objects - for (Region region : regions) + for (Region region : regionLoader.getRegions()) { int baseX = region.getBaseX(); int baseY = region.getBaseY(); // to pixel X - int drawBaseX = baseX - lowestX.getBaseX(); + int drawBaseX = baseX - regionLoader.getLowestX().getBaseX(); // to pixel Y. top most y is 0, but the top most - // region has the greaters y, so invert - int drawBaseY = highestY.getBaseY() - baseY; + // region has the greatest y, so invert + int drawBaseY = regionLoader.getHighestY().getBaseY() - baseY; Graphics2D graphics = image.createGraphics(); @@ -147,17 +144,17 @@ public class MapImageDumper } // map icons - for (Region region : regions) + for (Region region : regionLoader.getRegions()) { int baseX = region.getBaseX(); int baseY = region.getBaseY(); // to pixel X - int drawBaseX = baseX - lowestX.getBaseX(); + int drawBaseX = baseX - regionLoader.getLowestX().getBaseX(); // to pixel Y. top most y is 0, but the top most - // region has the greaters y, so invert - int drawBaseY = highestY.getBaseY() - baseY; + // region has the greatest y, so invert + int drawBaseY = regionLoader.getHighestY().getBaseY() - baseY; Graphics2D graphics = image.createGraphics(); @@ -184,17 +181,17 @@ public class MapImageDumper private void drawUnderlay(BufferedImage image, int z) { // pass 1 - for (Region region : regions) + for (Region region : regionLoader.getRegions()) { int baseX = region.getBaseX(); int baseY = region.getBaseY(); // to pixel X - int drawBaseX = baseX - lowestX.getBaseX(); + int drawBaseX = baseX - regionLoader.getLowestX().getBaseX(); // to pixel Y. top most y is 0, but the top most - // region has the greaters y, so invert - int drawBaseY = highestY.getBaseY() - baseY; + // region has the greatest y, so invert + int drawBaseY = regionLoader.getHighestY().getBaseY() - baseY; for (int x = 0; x < Region.X; ++x) { @@ -231,17 +228,17 @@ public class MapImageDumper private void drawOverlay(BufferedImage image, int z) { - for (Region region : regions) + for (Region region : regionLoader.getRegions()) { int baseX = region.getBaseX(); int baseY = region.getBaseY(); // to pixel X - int drawBaseX = baseX - lowestX.getBaseX(); + int drawBaseX = baseX - regionLoader.getLowestX().getBaseX(); // to pixel Y. top most y is 0, but the top most - // region has the greaters y, so invert - int drawBaseY = highestY.getBaseY() - baseY; + // region has the greatest y, so invert + int drawBaseY = regionLoader.getHighestY().getBaseY() - baseY; for (int x = 0; x < Region.X; ++x) { @@ -354,83 +351,14 @@ public class MapImageDumper private void loadRegions(Store store) throws IOException { - Index index = store.getIndex(IndexType.MAPS); - XteaKeyManager keyManager = index.getXteaManager(); + regionLoader = new RegionLoader(); + regionLoader.loadRegions(store); + regionLoader.calculateBounds(); - for (int i = 0; i < MAX_REGION; ++i) - { - int x = i >> 8; - int y = i & 0xFF; - - Archive map = index.findArchiveByName("m" + x + "_" + y); - Archive land = index.findArchiveByName("l" + x + "_" + y); - - assert (map == null) == (land == null); - - if (map == null || land == null) - { - continue; - } - - assert map.getFiles().size() == 1; - assert land.getFiles().size() == 1; - - map.decompressAndLoad(null); - - byte[] data = map.getFiles().get(0).getContents(); - - Region region = new Region(i); - region.loadTerrain(data); - - int[] keys = keyManager.getKeys(i); - if (keys != null) - { - try - { - land.decompressAndLoad(keys); - - data = land.getFiles().get(0).getContents(); - region.loadLocations(data); - } - catch (IOException ex) - { - logger.debug("Can't decrypt region " + i, ex); - } - } - - regions.add(region); - - if (lowestX == null || region.getBaseX() < lowestX.getBaseX()) - { - lowestX = region; - } - - if (highestX == null || region.getBaseX() > highestX.getBaseX()) - { - highestX = region; - } - - if (lowestY == null || region.getBaseY() < lowestY.getBaseY()) - { - lowestY = region; - } - - if (highestY == null || region.getBaseY() > highestY.getBaseY()) - { - highestY = region; - } - } - - assert lowestX != null; - assert lowestY != null; - - assert highestX != null; - assert highestY != null; - - logger.info("North most region: {}", lowestY.getBaseY()); - logger.info("South most region: {}", highestY.getBaseY()); - logger.info("West most region: {}", lowestX.getBaseX()); - logger.info("East most region: {}", highestX.getBaseX()); + logger.info("North most region: {}", regionLoader.getLowestY().getBaseY()); + logger.info("South most region: {}", regionLoader.getHighestY().getBaseY()); + logger.info("West most region: {}", regionLoader.getLowestX().getBaseX()); + logger.info("East most region: {}", regionLoader.getHighestX().getBaseX()); } private void loadUnderlays(Store store) diff --git a/cache/src/main/java/net/runelite/cache/region/RegionLoader.java b/cache/src/main/java/net/runelite/cache/region/RegionLoader.java new file mode 100644 index 0000000000..36f8e872e0 --- /dev/null +++ b/cache/src/main/java/net/runelite/cache/region/RegionLoader.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2016, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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 net.runelite.cache.region; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import net.runelite.cache.IndexType; +import net.runelite.cache.fs.Archive; +import net.runelite.cache.fs.Index; +import net.runelite.cache.fs.Store; +import net.runelite.cache.util.XteaKeyManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RegionLoader +{ + private static final Logger logger = LoggerFactory.getLogger(RegionLoader.class); + + private static final int MAX_REGION = 32768; + + private final List regions = new ArrayList<>(); + private Region lowestX = null, lowestY = null; + private Region highestX = null, highestY = null; + + public void loadRegions(Store store) throws IOException + { + Index index = store.getIndex(IndexType.MAPS); + XteaKeyManager keyManager = index.getXteaManager(); + + for (int i = 0; i < MAX_REGION; ++i) + { + int x = i >> 8; + int y = i & 0xFF; + + Archive map = index.findArchiveByName("m" + x + "_" + y); + Archive land = index.findArchiveByName("l" + x + "_" + y); + + assert (map == null) == (land == null); + + if (map == null || land == null) + { + continue; + } + + assert map.getFiles().size() == 1; + assert land.getFiles().size() == 1; + + map.decompressAndLoad(null); + + byte[] data = map.getFiles().get(0).getContents(); + + Region region = new Region(i); + region.loadTerrain(data); + + int[] keys = keyManager.getKeys(i); + if (keys != null) + { + try + { + land.decompressAndLoad(keys); + + data = land.getFiles().get(0).getContents(); + region.loadLocations(data); + } + catch (IOException ex) + { + logger.debug("Can't decrypt region " + i, ex); + } + } + + regions.add(region); + } + } + + public void calculateBounds() + { + for (Region region : regions) + { + if (lowestX == null || region.getBaseX() < lowestX.getBaseX()) + { + lowestX = region; + } + + if (highestX == null || region.getBaseX() > highestX.getBaseX()) + { + highestX = region; + } + + if (lowestY == null || region.getBaseY() < lowestY.getBaseY()) + { + lowestY = region; + } + + if (highestY == null || region.getBaseY() > highestY.getBaseY()) + { + highestY = region; + } + } + } + + public List getRegions() + { + return regions; + } + + public Region getLowestX() + { + return lowestX; + } + + public Region getLowestY() + { + return lowestY; + } + + public Region getHighestX() + { + return highestX; + } + + public Region getHighestY() + { + return highestY; + } +} diff --git a/cache/src/test/java/net/runelite/cache/HeightMapDumperTest.java b/cache/src/test/java/net/runelite/cache/HeightMapDumperTest.java new file mode 100644 index 0000000000..29770202ba --- /dev/null +++ b/cache/src/test/java/net/runelite/cache/HeightMapDumperTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2016, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam + * 4. Neither the name of the Adam nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam ''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 Adam 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 net.runelite.cache; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import javax.imageio.ImageIO; +import net.runelite.cache.fs.Store; +import org.junit.Rule; +import org.junit.rules.TemporaryFolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HeightMapDumperTest +{ + private static final Logger logger = LoggerFactory.getLogger(HeightMapDumperTest.class); + + @Rule + public TemporaryFolder folder = StoreLocation.getTemporaryFolder(); + + //@Test + public void extract() throws IOException + { + File base = StoreLocation.LOCATION, + outDir = folder.newFolder(); + + try (Store store = new Store(base)) + { + store.load(); + + HeightMapDumper dumper = new HeightMapDumper(store); + dumper.load(); + + BufferedImage image = dumper.drawHeightMap(0); + + File imageFile = new File(outDir, "heightmap-0.png"); + + ImageIO.write(image, "png", imageFile); + logger.info("Wrote image {}", imageFile); + } + } +}