Repository: runelite/runelite
Pull request #17855 (2024-05-17 23:03:02)

Make use of CommandExecuted case insensitive


Patch:
diff --git a/cache/src/main/java/net/runelite/cache/definitions/WorldMapDefinition.java b/cache/src/main/java/net/runelite/cache/definitions/WorldMapDefinition.java
new file mode 100644
index 000000000..9f0f80c75
--- /dev/null
+++ b/cache/src/main/java/net/runelite/cache/definitions/WorldMapDefinition.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2017, Adam <Adam@sigterm.info>
+ * 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.
+ *
+ * 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 net.runelite.cache.definitions;
+
+import java.util.List;
+import lombok.Data;
+import net.runelite.cache.region.Position;
+
+@Data
+public class WorldMapDefinition
+{
+	public String safeName;
+	public String name;
+	public int emptyTileColor;
+	public int backgroundColor;
+	public int defaultZoom;
+	public int fileId;
+	public boolean isSurface;
+	public List<WorldMapTypeBase> regionList;
+	public Position position;
+}
diff --git a/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapLoader.java b/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapLoader.java
new file mode 100644
index 000000000..118476a4c
--- /dev/null
+++ b/cache/src/main/java/net/runelite/cache/definitions/loaders/WorldMapLoader.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2017, Adam <Adam@sigterm.info>
+ * 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.
+ *
+ * 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 net.runelite.cache.definitions.loaders;
+
+import java.util.LinkedList;
+import net.runelite.cache.definitions.WorldMapDefinition;
+import net.runelite.cache.definitions.WorldMapType0;
+import net.runelite.cache.definitions.WorldMapType1;
+import net.runelite.cache.definitions.WorldMapType2;
+import net.runelite.cache.definitions.WorldMapType3;
+import net.runelite.cache.definitions.WorldMapTypeBase;
+import net.runelite.cache.io.InputStream;
+import net.runelite.cache.region.Position;
+
+public class WorldMapLoader
+{
+	public WorldMapDefinition load(byte[] b, int fileId)
+	{
+		WorldMapDefinition def = new WorldMapDefinition();
+		InputStream in = new InputStream(b);
+
+		def.fileId = fileId;
+		def.safeName = in.readString();
+		def.name = in.readString();
+
+		int packedPos = in.readInt();
+		def.position = Position.fromPacked(packedPos);
+
+		def.emptyTileColor = in.readInt();
+		def.backgroundColor = in.readInt();
+		in.readUnsignedByte();
+		def.isSurface = in.readUnsignedByte() == 1;
+		def.defaultZoom = in.readUnsignedByte();
+		int var3 = in.readUnsignedByte();
+		def.regionList = new LinkedList<>();
+
+		for (int var4 = 0; var4 < var3; ++var4)
+		{
+			def.regionList.add(this.loadType(in));
+		}
+
+		return def;
+	}
+
+	private WorldMapTypeBase loadType(InputStream var1)
+	{
+		int var2 = var1.readUnsignedByte();
+		WorldMapTypeBase base;
+		switch (var2)
+		{
+			case 0:
+				// type 1
+				base = load1(var1);
+				break;
+			case 1:
+				// type 2
+				base = load2(var1);
+				break;
+			case 2:
+				// type 3
+				base = load3(var1);
+				break;
+			case 3:
+				// type 0
+				base = load0(var1);
+				break;
+			default:
+				throw new IllegalStateException();
+		}
+		return base;
+	}
+
+	private WorldMapTypeBase load0(InputStream in)
+	{
+		WorldMapType0 wm = new WorldMapType0();
+
+		wm.plane = in.readUnsignedByte();
+		wm.numberOfPlanes = in.readUnsignedByte();
+		wm.xLow = in.readUnsignedShort();
+		wm.chunk_xLow = in.readUnsignedByte();
+		wm.yLow = in.readUnsignedShort();
+		wm.chunk_yLow = in.readUnsignedByte();
+		wm.xHigh = in.readUnsignedShort();
+		wm.chunk_xHigh = in.readUnsignedByte();
+		wm.yHigh = in.readUnsignedShort();
+		wm.chunk_yHigh = in.readUnsignedByte();
+
+		return wm;
+	}
+
+	private WorldMapTypeBase load1(InputStream in)
+	{
+		WorldMapType1 wm = new WorldMapType1();
+
+		wm.plane = in.readUnsignedByte();
+		wm.numberOfPlanes = in.readUnsignedByte();
+		wm.xLowerLeft = in.readUnsignedShort();
+		wm.yLowerLeft = in.readUnsignedShort();
+		wm.xLowerRight = in.readUnsignedShort();
+		wm.yUpperLeft = in.readUnsignedShort();
+		wm.xUpperLeft = in.readUnsignedShort();
+		wm.yLowerRight = in.readUnsignedShort();
+		wm.xUpperRight = in.readUnsignedShort();
+		wm.yUpperRight = in.readUnsignedShort();
+
+		return wm;
+	}
+
+	private WorldMapTypeBase load2(InputStream in)
+	{
+		WorldMapType2 wm = new WorldMapType2();
+
+		wm.plane = in.readUnsignedByte();
+		wm.numberOfPlanes = in.readUnsignedByte();
+		wm.xLow = in.readUnsignedShort();
+		wm.yLow = in.readUnsignedShort();
+		wm.xHigh = in.readUnsignedShort();
+		wm.yHigh = in.readUnsignedShort();
+
+		return wm;
+	}
+
+	private WorldMapTypeBase load3(InputStream in)
+	{
+		WorldMapType3 wm = new WorldMapType3();
+
+		wm.oldPlane = in.readUnsignedByte();
+		wm.numberOfPlanes = in.readUnsignedByte();
+		wm.oldX = in.readUnsignedShort();
+		wm.chunk_oldXLow = in.readUnsignedByte();
+		wm.chunk_oldXHigh = in.readUnsignedByte();
+		wm.oldY = in.readUnsignedShort();
+		wm.chunk_oldYLow = in.readUnsignedByte();
+		wm.chunk_oldYHigh = in.readUnsignedByte();
+		wm.newX = in.readUnsignedShort();
+		wm.chunk_newXLow = in.readUnsignedByte();
+		wm.chunk_newXHigh = in.readUnsignedByte();
+		wm.newY = in.readUnsignedShort();
+		wm.chunk_newYLow = in.readUnsignedByte();
+		wm.chunk_newYHigh = in.readUnsignedByte();
+
+		return wm;
+	}
+}
diff --git a/runelite-api/src/main/java/net/runelite/api/WorldType.java b/runelite-api/src/main/java/net/runelite/api/WorldType.java
index 5cd26c4b6..b4ca3b3dc 100644
--- a/runelite-api/src/main/java/net/runelite/api/WorldType.java
+++ b/runelite-api/src/main/java/net/runelite/api/WorldType.java
@@ -70,6 +70,8 @@ public enum WorldType
 	 * Beta world.
 	 */
 	BETA_WORLD(1 << 16),
+	LEGACY_ONLY(1 << 22),
+	EOC_ONLY(1 << 23),
 	/**
 	 * Beta worlds without profiles that are saved.
 	 */
diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml
index c73318eca..12b876a52 100644
--- a/runelite-client/pom.xml
+++ b/runelite-client/pom.xml
@@ -253,7 +253,7 @@
 		<dependency>
 			<groupId>net.runelite.arn</groupId>
 			<artifactId>http-api</artifactId>
-			<version>1.2.12</version>
+			<version>1.2.16</version>
 		</dependency>
 		<dependency>
 			<groupId>net.runelite</groupId>
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java
index c93c0dd24..b5da6850a 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/chatfilter/ChatFilterPlugin.java
@@ -216,6 +216,7 @@ public class ChatFilterPlugin extends Plugin
 				break;
 			case GAMEMESSAGE:
 			case ENGINE:
+			case FRIENDSCHATNOTIFICATION:
 			case ITEM_EXAMINE:
 			case NPC_EXAMINE:
 			case OBJECT_EXAMINE:
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java
index ea4704136..c19d7e5a0 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/ClueScrollPlugin.java
@@ -693,7 +693,7 @@ public class ClueScrollPlugin extends Plugin
 	@Subscribe
 	public void onCommandExecuted(CommandExecuted commandExecuted)
 	{
-		if (developerMode && commandExecuted.getCommand().equals("clue"))
+		if (developerMode && commandExecuted.getCommand().equalsIgnoreCase("clue"))
 		{
 			var text = String.join(" ", commandExecuted.getArguments());
 
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java
index 15b972265..62c214c82 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java
@@ -316,7 +316,7 @@ public class DevToolsPlugin extends Plugin
 	{
 		String[] args = commandExecuted.getArguments();
 
-		switch (commandExecuted.getCommand())
+		switch (commandExecuted.getCommand().toLowerCase())
 		{
 			case "logger":
 			{
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java
index 2021b9fe0..6f0ab3708 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java
@@ -95,7 +95,6 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 	static final int MAX_TRIANGLE = 6144;
 	static final int SMALL_TRIANGLE_COUNT = 512;
 	private static final int FLAG_SCENE_BUFFER = Integer.MIN_VALUE;
-	private static final int DEFAULT_DISTANCE = 25;
 	static final int MAX_DISTANCE = 184;
 	static final int MAX_FOG_DEPTH = 100;
 	static final int SCENE_OFFSET = (Constants.EXTENDED_SCENE_SIZE - Constants.SCENE_SIZE) / 2; // offset for sxy -> msxy
@@ -1845,8 +1844,7 @@ public class GpuPlugin extends Plugin implements DrawCallbacks
 
 	private int getDrawDistance()
 	{
-		final int limit = computeMode != ComputeMode.NONE ? MAX_DISTANCE : DEFAULT_DISTANCE;
-		return Ints.constrainToRange(config.drawDistance(), 0, limit);
+		return Ints.constrainToRange(config.drawDistance(), 0, MAX_DISTANCE);
 	}
 
 	private void updateBuffer(@Nonnull GLBuffer glBuffer, int target, @Nonnull IntBuffer data, int usage, long clFlags)
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java
index aa373ab98..5a13eaebf 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/loottracker/LootTrackerPlugin.java
@@ -635,7 +635,7 @@ public class LootTrackerPlugin extends Plugin
 		final LootTrackerItem[] entries = buildEntries(stack(items));
 		SwingUtilities.invokeLater(() -> panel.add(name, type, combatLevel, entries, amount));
 
-		LootRecord lootRecord = new LootRecord(name, type, metadata, toGameItems(items), Instant.now(), getLootWorldId());
+		LootRecord lootRecord = new LootRecord(name, type, metadata, toGameItems(items), Instant.now(), getLootWorldId(), amount, null);
 		synchronized (queuedLoots)
 		{
 			queuedLoots.add(lootRecord);
@@ -1319,7 +1319,7 @@ public class LootTrackerPlugin extends Plugin
 		{
 			ConfigLoot key = new ConfigLoot(record.getType(), record.getEventId());
 			ConfigLoot loot = map.computeIfAbsent(key, k -> key);
-			loot.kills++;
+			loot.kills += record.getAmount();
 			for (GameItem item : record.getDrops())
 			{
 				loot.add(item.getId(), item.getQty());
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java
index eb4078752..118d76e20 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/party/PartyPlugin.java
@@ -608,7 +608,7 @@ public class PartyPlugin extends Plugin
 	@Subscribe
 	public void onCommandExecuted(CommandExecuted commandExecuted)
 	{
-		if (!developerMode || !commandExecuted.getCommand().equals("partyinfo"))
+		if (!developerMode || !commandExecuted.getCommand().equalsIgnoreCase("partyinfo"))
 		{
 			return;
 		}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java
index f519ad516..3cf59bdc1 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/slayer/SlayerPlugin.java
@@ -275,7 +275,7 @@ public class SlayerPlugin extends Plugin
 	@Subscribe
 	public void onCommandExecuted(CommandExecuted commandExecuted)
 	{
-		if (developerMode && commandExecuted.getCommand().equals("task"))
+		if (developerMode && commandExecuted.getCommand().equalsIgnoreCase("task"))
 		{
 			var task = String.join(" ", commandExecuted.getArguments());
 			setTask(task, 42, 42);
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounterPlugin.java
index b0c1793be..bada231f7 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounterPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/specialcounter/SpecialCounterPlugin.java
@@ -396,7 +396,7 @@ public class SpecialCounterPlugin extends Plugin
 	@Subscribe
 	public void onCommandExecuted(CommandExecuted commandExecuted)
 	{
-		if (developerMode && commandExecuted.getCommand().equals("spec"))
+		if (developerMode && commandExecuted.getCommand().equalsIgnoreCase("spec"))
 		{
 			playerInfoDrops.add(createSpecInfoDrop(SpecialWeapon.BANDOS_GODSWORD, 42, client.getLocalPlayer().getId()));
 		}
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java
index a64c47a83..9f6f81e6f 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/timetracking/TimeTrackingPlugin.java
@@ -203,7 +203,7 @@ public class TimeTrackingPlugin extends Plugin
 	@Subscribe
 	public void onCommandExecuted(CommandExecuted commandExecuted)
 	{
-		if (commandExecuted.getCommand().equals("resetfarmtick"))
+		if (commandExecuted.getCommand().equalsIgnoreCase("resetfarmtick"))
 		{
 			configManager.unsetRSProfileConfiguration(CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET_PRECISION);
 			configManager.unsetRSProfileConfiguration(CONFIG_GROUP, TimeTrackingConfig.FARM_TICK_OFFSET);
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java
index c8781d2af..b39543a0b 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java
@@ -294,7 +294,7 @@ public class WorldHopperPlugin extends Plugin
 	@Subscribe
 	public void onCommandExecuted(CommandExecuted commandExecuted)
 	{
-		if ("hop".equals(commandExecuted.getCommand()))
+		if ("hop".equalsIgnoreCase(commandExecuted.getCommand()))
 		{
 			int worldNumber;
 			try
diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java
index aa63a28e8..ec4ea6088 100644
--- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java
+++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java
@@ -269,7 +269,8 @@ class WorldTableRow extends JPanel
 		{
 			activityField.setForeground(SEASONAL_WORLD);
 		}
-		else if (types.contains(WorldType.NOSAVE_MODE) || types.contains(WorldType.BETA_WORLD))
+		else if (types.contains(WorldType.NOSAVE_MODE) || types.contains(WorldType.BETA_WORLD)
+			|| types.contains(WorldType.LEGACY_ONLY)) // sometimes used for arbitrarily restricted worlds
 		{
 			activityField.setForeground(BETA_WORLD);
 		}
diff --git a/runelite-client/src/main/java/net/runelite/client/util/WorldUtil.java b/runelite-client/src/main/java/net/runelite/client/util/WorldUtil.java
index 0841fe07b..bdb1abdd3 100644
--- a/runelite-client/src/main/java/net/runelite/client/util/WorldUtil.java
+++ b/runelite-client/src/main/java/net/runelite/client/util/WorldUtil.java
@@ -73,6 +73,10 @@ public class WorldUtil
 				return WorldType.LAST_MAN_STANDING;
 			case BETA_WORLD:
 				return WorldType.BETA_WORLD;
+			case LEGACY_ONLY:
+				return WorldType.LEGACY_ONLY;
+			case EOC_ONLY:
+				return WorldType.EOC_ONLY;
 			case NOSAVE_MODE:
 				return WorldType.NOSAVE_MODE;
 			case TOURNAMENT:
diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/regions/regions.txt b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/regions/regions.txt
index 68de1a27b..640669501 100644
--- a/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/regions/regions.txt
+++ b/runelite-client/src/main/resources/net/runelite/client/plugins/gpu/regions/regions.txt
@@ -238,4 +238,8 @@ r 45 71
 
 // pyramid plunder
 n
-r 30 69
\ No newline at end of file
+r 30 69
+
+// waterbirth dungeon
+n
+R 28 68 30 68
\ No newline at end of file


Test patch:
diff --git a/cache/src/test/java/net/runelite/cache/WorldMapDumperTest.java b/cache/src/test/java/net/runelite/cache/WorldMapDumperTest.java
new file mode 100644
index 000000000..a3f25a3e6
--- /dev/null
+++ b/cache/src/test/java/net/runelite/cache/WorldMapDumperTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2017, Adam <Adam@sigterm.info>
+ * 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.
+ *
+ * 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 net.runelite.cache;
+
+import com.google.common.io.Files;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import net.runelite.cache.definitions.WorldMapDefinition;
+import net.runelite.cache.definitions.loaders.WorldMapLoader;
+import net.runelite.cache.fs.Archive;
+import net.runelite.cache.fs.ArchiveFiles;
+import net.runelite.cache.fs.FSFile;
+import net.runelite.cache.fs.Index;
+import net.runelite.cache.fs.Storage;
+import net.runelite.cache.fs.Store;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WorldMapDumperTest
+{
+	private static final Logger logger = LoggerFactory.getLogger(WorldMapDumperTest.class);
+
+	@Rule
+	public TemporaryFolder folder = StoreLocation.getTemporaryFolder();
+
+	private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
+
+	@Test
+	@Ignore
+	public void extract() throws IOException
+	{
+		File base = StoreLocation.LOCATION,
+			outDir = folder.newFolder();
+
+		int count = 0;
+
+		try (Store store = new Store(base))
+		{
+			store.load();
+
+			Storage storage = store.getStorage();
+			Index index = store.getIndex(IndexType.WORLDMAP);
+			Archive archive = index.findArchiveByName("details");
+
+			byte[] archiveData = storage.loadArchive(archive);
+			ArchiveFiles files = archive.getFiles(archiveData);
+
+			for (FSFile file : files.getFiles())
+			{
+				WorldMapLoader loader = new WorldMapLoader();
+				WorldMapDefinition def = loader.load(file.getContents(), file.getFileId());
+
+				Files.asCharSink(new File(outDir, file.getFileId() + ".json"), Charset.defaultCharset()).write(gson.toJson(def));
+				++count;
+			}
+		}
+
+		logger.info("Dumped {} world map data to {}", count, outDir);
+	}
+}