diff --git a/cache/src/main/java/net/runelite/cache/HeightMapDumper.java b/cache/src/main/java/net/runelite/cache/HeightMapDumper.java index f6263518b0..8ea0612051 100644 --- a/cache/src/main/java/net/runelite/cache/HeightMapDumper.java +++ b/cache/src/main/java/net/runelite/cache/HeightMapDumper.java @@ -55,8 +55,8 @@ public class HeightMapDumper public void load() throws IOException { - regionLoader = new RegionLoader(); - regionLoader.loadRegions(store); + regionLoader = new RegionLoader(store); + regionLoader.loadRegions(); regionLoader.calculateBounds(); } diff --git a/cache/src/main/java/net/runelite/cache/MapImageDumper.java b/cache/src/main/java/net/runelite/cache/MapImageDumper.java index 10edf9f825..feab4403e0 100644 --- a/cache/src/main/java/net/runelite/cache/MapImageDumper.java +++ b/cache/src/main/java/net/runelite/cache/MapImageDumper.java @@ -163,7 +163,8 @@ public class MapImageDumper if (labelRegions) { graphics.setColor(Color.WHITE); - graphics.drawString(baseX + "," + baseY, drawBaseX * MAP_SCALE, drawBaseY * MAP_SCALE + graphics.getFontMetrics().getHeight()); + String str = baseX + "," + baseY + " (" + region.getRegionX() + "," + region.getRegionY() + ")"; + graphics.drawString(str, drawBaseX * MAP_SCALE, drawBaseY * MAP_SCALE + graphics.getFontMetrics().getHeight()); } if (outlineRegions) @@ -351,8 +352,8 @@ public class MapImageDumper private void loadRegions(Store store) throws IOException { - regionLoader = new RegionLoader(); - regionLoader.loadRegions(store); + regionLoader = new RegionLoader(store); + regionLoader.loadRegions(); regionLoader.calculateBounds(); logger.info("North most region: {}", regionLoader.getLowestY().getBaseY()); diff --git a/cache/src/main/java/net/runelite/cache/models/Vector3f.java b/cache/src/main/java/net/runelite/cache/models/Vector3f.java index ad34a3f065..a93e1cd1ff 100644 --- a/cache/src/main/java/net/runelite/cache/models/Vector3f.java +++ b/cache/src/main/java/net/runelite/cache/models/Vector3f.java @@ -5,4 +5,99 @@ public class Vector3f public float x; public float y; public float z; + + public Vector3f() + { + } + + public Vector3f(float x, float y, float z) + { + this.x = x; + this.y = y; + this.z = z; + } + + public Vector3f(Vector3f other) + { + this.x = other.x; + this.y = other.y; + this.z = other.z; + } + + @Override + public String toString() + { + return "Vector3f{" + "x=" + x + ", y=" + y + ", z=" + z + '}'; + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 23 * hash + Float.floatToIntBits(this.x); + hash = 23 * hash + Float.floatToIntBits(this.y); + hash = 23 * hash + Float.floatToIntBits(this.z); + return hash; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + { + return true; + } + if (obj == null) + { + return false; + } + if (getClass() != obj.getClass()) + { + return false; + } + final Vector3f other = (Vector3f) obj; + if (Float.floatToIntBits(this.x) != Float.floatToIntBits(other.x)) + { + return false; + } + if (Float.floatToIntBits(this.y) != Float.floatToIntBits(other.y)) + { + return false; + } + if (Float.floatToIntBits(this.z) != Float.floatToIntBits(other.z)) + { + return false; + } + return true; + } + + public float getX() + { + return x; + } + + public void setX(float x) + { + this.x = x; + } + + public float getY() + { + return y; + } + + public void setY(float y) + { + this.y = y; + } + + public float getZ() + { + return z; + } + + public void setZ(float z) + { + this.z = z; + } } diff --git a/cache/src/main/java/net/runelite/cache/region/Region.java b/cache/src/main/java/net/runelite/cache/region/Region.java index 31dbe22102..8b746ae33d 100644 --- a/cache/src/main/java/net/runelite/cache/region/Region.java +++ b/cache/src/main/java/net/runelite/cache/region/Region.java @@ -57,6 +57,13 @@ public class Region this.baseY = (id & 0xFF) << 6; } + public Region(int x, int y) + { + this.regionID = x << 8 | y; + this.baseX = x << 6; + this.baseY = y << 6; + } + public void loadTerrain(byte[] buf) { InputStream in = new InputStream(buf); @@ -202,4 +209,14 @@ public class Region { return locations; } + + public int getRegionX() + { + return baseX >> 6; + } + + public int getRegionY() + { + return baseY >> 6; + } } diff --git a/cache/src/main/java/net/runelite/cache/region/RegionLoader.java b/cache/src/main/java/net/runelite/cache/region/RegionLoader.java index 36f8e872e0..32982cc65e 100644 --- a/cache/src/main/java/net/runelite/cache/region/RegionLoader.java +++ b/cache/src/main/java/net/runelite/cache/region/RegionLoader.java @@ -46,60 +46,75 @@ public class RegionLoader private static final int MAX_REGION = 32768; + private final Store store; + private final Index index; + private final XteaKeyManager keyManager; + 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 + public RegionLoader(Store store) { - Index index = store.getIndex(IndexType.MAPS); - XteaKeyManager keyManager = index.getXteaManager(); + this.store = store; + index = store.getIndex(IndexType.MAPS); + keyManager = index.getXteaManager(); + } + public void loadRegions() throws IOException + { 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); + Region region = this.loadRegionFromArchive(i); + if (region != null) + regions.add(region); } } + public Region loadRegionFromArchive(int i) throws IOException + { + 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) + { + return null; + } + + 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); + } + } + + return region; + } + public void calculateBounds() { for (Region region : regions) diff --git a/model-viewer/src/main/java/net/runelite/modelviewer/Vector3f.java b/cache/src/test/java/net/runelite/cache/OverlayDumper.java similarity index 53% rename from model-viewer/src/main/java/net/runelite/modelviewer/Vector3f.java rename to cache/src/test/java/net/runelite/cache/OverlayDumper.java index f8c67fac49..438fd67b54 100644 --- a/model-viewer/src/main/java/net/runelite/modelviewer/Vector3f.java +++ b/cache/src/test/java/net/runelite/cache/OverlayDumper.java @@ -27,23 +27,59 @@ * (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; -package net.runelite.modelviewer; +import com.google.common.io.Files; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.io.IOException; +import java.nio.charset.Charset; +import net.runelite.cache.definitions.OverlayDefinition; +import net.runelite.cache.definitions.loaders.OverlayLoader; +import net.runelite.cache.fs.Archive; +import net.runelite.cache.fs.File; +import net.runelite.cache.fs.Index; +import net.runelite.cache.fs.Store; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public class Vector3f +public class OverlayDumper { - public float x, y, z; + private static final Logger logger = LoggerFactory.getLogger(OverlayDumper.class); - public Vector3f(float x, float y, float z) - { - this.x = x; - this.y = y; - this.z = z; - } + @Rule + public TemporaryFolder folder = StoreLocation.getTemporaryFolder(); - @Override - public String toString() + private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + @Test + public void extract() throws IOException { - return "Vector3f{" + "x=" + x + ", y=" + y + ", z=" + z + '}'; + java.io.File base = StoreLocation.LOCATION, + outDir = folder.newFile(); + + int count = 0; + + try (Store store = new Store(base)) + { + store.load(); + + Index index = store.getIndex(IndexType.CONFIGS); + Archive archive = index.getArchive(ConfigType.OVERLAY.getId()); + + for (File file : archive.getFiles()) + { + OverlayLoader loader = new OverlayLoader(); + OverlayDefinition overlay = loader.load(file.getFileId(), file.getContents()); + + Files.write(gson.toJson(overlay), new java.io.File(outDir, file.getFileId() + ".json"), Charset.defaultCharset()); + ++count; + } + } + + logger.info("Dumped {} overlays to {}", count, outDir); } } diff --git a/model-viewer/src/main/java/net/runelite/modelviewer/Camera.java b/model-viewer/src/main/java/net/runelite/modelviewer/Camera.java index 6aea1aaa0c..26c54dc82a 100644 --- a/model-viewer/src/main/java/net/runelite/modelviewer/Camera.java +++ b/model-viewer/src/main/java/net/runelite/modelviewer/Camera.java @@ -29,6 +29,7 @@ */ package net.runelite.modelviewer; +import net.runelite.cache.models.Vector3f; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; @@ -39,7 +40,7 @@ public class Camera { private static final float MAX_X = 89; - public float moveSpeed = 0.20f; + public float moveSpeed = 0.60f; private float mouseSensitivity = 0.05f; @@ -196,104 +197,14 @@ public class Camera return pos; } - public void setX(float x) - { - pos.x = x; - } - - public float getX() - { - return pos.x; - } - - public void addToX(float x) - { - pos.x += x; - } - - public void setY(float y) - { - pos.y = y; - } - - public float getY() - { - return pos.y; - } - - public void addToY(float y) - { - pos.y += y; - } - - public void setZ(float z) - { - pos.z = z; - } - - public float getZ() - { - return pos.z; - } - - public void addToZ(float z) - { - pos.z += z; - } - - public void setRotation(Vector3f rotation) - { - this.rotation = rotation; - } - public Vector3f getRotation() { return rotation; } - public void setRotationX(float x) + public void setRotation(Vector3f rotation) { - rotation.x = x; - } - - public float getRotationX() - { - return rotation.x; - } - - public void addToRotationX(float x) - { - rotation.x += x; - } - - public void setRotationY(float y) - { - rotation.y = y; - } - - public float getRotationY() - { - return rotation.y; - } - - public void addToRotationY(float y) - { - rotation.y += y; - } - - public void setRotationZ(float z) - { - rotation.z = z; - } - - public float getRotationZ() - { - return rotation.z; - } - - public void addToRotationZ(float z) - { - rotation.z += z; + this.rotation = rotation; } public void setMouseSensitivity(float mouseSensitivity) diff --git a/model-viewer/src/main/java/net/runelite/modelviewer/ModelViewer.java b/model-viewer/src/main/java/net/runelite/modelviewer/ModelViewer.java index 0456e0b502..e12c4dd605 100644 --- a/model-viewer/src/main/java/net/runelite/modelviewer/ModelViewer.java +++ b/model-viewer/src/main/java/net/runelite/modelviewer/ModelViewer.java @@ -33,43 +33,59 @@ import com.google.gson.Gson; import java.awt.Color; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStreamReader; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; import net.runelite.cache.definitions.ModelDefinition; import net.runelite.cache.definitions.NpcDefinition; +import net.runelite.cache.definitions.OverlayDefinition; +import net.runelite.cache.definitions.UnderlayDefinition; import net.runelite.cache.definitions.loaders.ModelLoader; import net.runelite.cache.models.Vector3f; import net.runelite.cache.models.VertexNormal; +import net.runelite.cache.region.Region; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Options; +import org.apache.commons.compress.utils.IOUtils; import org.lwjgl.opengl.Display; import org.lwjgl.opengl.DisplayMode; import org.lwjgl.opengl.GL11; -import static org.lwjgl.opengl.GL11.glRotatef; public class ModelViewer { + private static final int NUM_UNDERLAYS = 150; + private static final int NUM_OVERLAYS = 174; + + private static UnderlayDefinition[] underlays = new UnderlayDefinition[NUM_UNDERLAYS]; + private static OverlayDefinition[] overlays = new OverlayDefinition[NUM_OVERLAYS]; + public static void main(String[] args) throws Exception { Options options = new Options(); options.addOption(null, "npcdir", true, "npc directory"); options.addOption(null, "modeldir", true, "model directory"); + options.addOption(null, "mapdir", true, "maps directory"); + options.addOption(null, "npc", true, "npc to render"); options.addOption(null, "model", true, "model to render"); + options.addOption(null, "map", true, "map region to render"); CommandLineParser parser = new DefaultParser(); CommandLine cmd = parser.parse(options, args); String npcdir = cmd.getOptionValue("npcdir"); String modeldir = cmd.getOptionValue("modeldir"); + String mapdir = cmd.getOptionValue("mapdir"); NpcDefinition npcdef = null; List models = new ArrayList<>(); + Region region = null; if (cmd.hasOption("model")) { @@ -81,7 +97,7 @@ public class ModelViewer ModelDefinition md = loader.load(b); models.add(md); } - else if (cmd.hasOption("npc")) + if (cmd.hasOption("npc")) { String npc = cmd.getOptionValue("npc"); @@ -98,10 +114,23 @@ public class ModelViewer models.add(md); } } - else + if (cmd.hasOption("map")) { - System.out.println("Must specify model or npc"); - return; + String map = cmd.getOptionValue("map"); + String[] s = map.split(","); + + int x = Integer.parseInt(s[0]), y = Integer.parseInt(s[1]); + + region = new Region(x, y); + + try (FileInputStream fin = new FileInputStream(mapdir + "/m" + x + "_" + y + ".dat")) + { + byte[] b = IOUtils.toByteArray(fin); + region.loadTerrain(b); + } + + loadUnderlays(); + loadOverlays(); } Display.setDisplayMode(new DisplayMode(800, 600)); @@ -113,7 +142,7 @@ public class ModelViewer GL11.glLoadIdentity(); double aspect = 1; double near = 1; // near should be chosen as far into the scene as possible - double far = 1000; + double far = 10000; double fov = 1; // 1 gives you a 90° field of view. It's tan(fov_angle)/2. GL11.glFrustum(-aspect * near * fov, aspect * near * fov, -fov, fov, near, far); @@ -121,7 +150,6 @@ public class ModelViewer GL11.glCullFace(GL11.GL_BACK); GL11.glEnable(GL11.GL_CULL_FACE); - long last = 0; Camera camera = new Camera(); @@ -136,6 +164,8 @@ public class ModelViewer drawModel(npcdef, def); } + drawRegion(region); + Display.update(); Display.sync(50); // fps @@ -224,6 +254,129 @@ public class ModelViewer GL11.glEnd(); } + private static void drawRegion(Region region) + { + if (region == null) + { + return; + } + + GL11.glBegin(GL11.GL_TRIANGLES); + + for (int regionX = 0; regionX < Region.X; ++regionX) + { + for (int regionY = 0; regionY < Region.Y; ++regionY) + { + int x = regionX; + int y = regionY; + + int TILE_SCALE = 16; + x *= TILE_SCALE; + y *= TILE_SCALE; + + /* + Split into two triangles with verticies + x,y,z1 x+1,y,z2 x,y+1,z3 + x,y+1,z3 x+1,y,z2 x+1,y+1,z4 + + z1 = height + z2 = height of tile x+1 + z3 = height of tile y-1 + + in rs 0,0 (x,y) is the bottom left with + y increasing going further from you + + in opengl, 0,0 (x,z) is the bottom left + with z decreasing going further from you + + in rs, height is also negative + + so we do rs(x,y,z) -> opengl(x,-z,-y) + */ + int z1 = -region.getTileHeight(0, regionX, regionY); + int z2 = regionX + 1 < Region.X ? -region.getTileHeight(0, regionX + 1, regionY) : z1; + int z3 = regionY + 1 < Region.Y ? -region.getTileHeight(0, regionX, regionY + 1) : z1; + int z4 = regionX + 1 < Region.X && regionY + 1 < Region.Y ? -region.getTileHeight(0, regionX + 1, regionY + 1) : z1; + + // scale down height (I randomally picked this) + z1 /= 4; + z2 /= 4; + z3 /= 4; + z4 /= 4; + + int underlayId = region.getUnderlayId(0, regionX, regionY); + int overlayId = region.getOverlayId(0, regionX, regionY); + + Color color = null; + if (underlayId > 0) + { + UnderlayDefinition ud = underlays[underlayId - 1]; + color = new Color(ud.getColor()); + } + if (overlayId > 0) + { + OverlayDefinition od = overlays[overlayId - 1]; + color = new Color(od.getRgbColor()); + + if (od.getSecondaryRgbColor() > -1) + { + color = new Color(od.getSecondaryRgbColor()); + } + + if (od.getTexture() > -1) + { + // textures? + } + } + + if (color != null) + { + GL11.glColor3f((float) color.getRed() / 255f, (float) color.getGreen() / 255f, (float) color.getBlue() / 255f); + } + + GL11.glVertex3i(x, z1, -y); + GL11.glVertex3i(x + TILE_SCALE, z2, -y); + GL11.glVertex3i(x, z3, -(y + TILE_SCALE)); + + GL11.glVertex3i(x, z3, -(y + TILE_SCALE)); + GL11.glVertex3i(x + TILE_SCALE, z2, -y); + GL11.glVertex3i(x + TILE_SCALE, z4, -(y + TILE_SCALE)); + } + } + + GL11.glEnd(); + } + + private static void loadUnderlays() throws IOException + { + for (int i = 0; i < NUM_UNDERLAYS; ++i) + { + try (FileInputStream fin = new FileInputStream("underlays/" + i + ".json")) + { + UnderlayDefinition underlay = new Gson().fromJson(new InputStreamReader(fin), UnderlayDefinition.class); + underlays[i] = underlay; + } + catch (FileNotFoundException ex) + { + } + } + } + + private static void loadOverlays() throws IOException + { + for (int i = 0; i < NUM_UNDERLAYS; ++i) + { + try (FileInputStream fin = new FileInputStream("overlays/" + i + ".json")) + { + OverlayDefinition overlay = new Gson().fromJson(new InputStreamReader(fin), OverlayDefinition.class); + overlays[i] = overlay; + } + catch (FileNotFoundException ex) + { + } + } + } + // found these two functions here https://www.rune-server.org/runescape-development/rs2-client/tools/589900-rs2-hsb-color-picker.html public static int RGB_to_RS2HSB(int red, int green, int blue) {