Merge remote-tracking branch 'runelite/master'

This commit is contained in:
Owain van Brakel
2022-05-11 11:41:54 +02:00
53 changed files with 1979 additions and 601 deletions

View File

@@ -30,6 +30,7 @@ import java.io.IOException;
import net.runelite.cache.fs.Store;
import net.runelite.cache.region.Region;
import net.runelite.cache.region.RegionLoader;
import net.runelite.cache.util.KeyProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -48,9 +49,9 @@ public class HeightMapDumper
this.store = store;
}
public void load() throws IOException
public void load(KeyProvider keyProvider) throws IOException
{
regionLoader = new RegionLoader(store);
regionLoader = new RegionLoader(store, keyProvider);
regionLoader.loadRegions();
regionLoader.calculateBounds();
}

View File

@@ -27,23 +27,26 @@ package net.runelite.cache;
public enum IndexType
{
FRAMES(0),
FRAMEMAPS(1),
ANIMATIONS(0),
SKELETONS(1),
CONFIGS(2),
INTERFACES(3),
SOUNDEFFECTS(4),
MAPS(5),
TRACK1(6),
MUSIC_TRACKS(6),
MODELS(7),
SPRITES(8),
TEXTURES(9),
BINARY(10),
TRACK2(11),
MUSIC_JINGLES(11),
CLIENTSCRIPT(12),
FONTS(13),
VORBIS(14),
INSTRUMENTS(15),
WORLDMAP(16);
MUSIC_SAMPLES(14),
MUSIC_PATCHES(15),
WORLDMAP_OLD(16), // looks unused
WORLDMAP_GEOGRAPHY(18),
WORLDMAP(19),
WORLDMAP_GROUND(20);
private int id;

View File

@@ -26,13 +26,20 @@ package net.runelite.cache;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import net.runelite.cache.definitions.AreaDefinition;
import net.runelite.cache.definitions.ObjectDefinition;
import net.runelite.cache.definitions.OverlayDefinition;
@@ -47,39 +54,44 @@ 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 net.runelite.cache.item.ColorPalette;
import net.runelite.cache.item.RSTextureProvider;
import net.runelite.cache.models.JagexColor;
import net.runelite.cache.region.Location;
import net.runelite.cache.region.Position;
import net.runelite.cache.region.Region;
import net.runelite.cache.region.RegionLoader;
import net.runelite.cache.util.Djb2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.runelite.cache.util.BigBufferedImage;
import net.runelite.cache.util.KeyProvider;
import net.runelite.cache.util.XteaKeyManager;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
@Slf4j
@Accessors(chain = true)
public class MapImageDumper
{
private static final Logger logger = LoggerFactory.getLogger(MapImageDumper.class);
private static final int MAP_SCALE = 4; // 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;
private static final int BLEND = 5; // number of surrounding tiles for ground blending
private static int[] colorPalette = new ColorPalette(0.9d, 0, 512).getColorPalette();
private static int[] colorPalette = JagexColor.createPalette(JagexColor.BRIGHTNESS_MIN);
private static int[][] TILE_SHAPE_2D = new int[][]{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1}, {1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1}, {0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0}, {1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1}, {1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1}};
private static int[][] TILE_ROTATION_2D = new int[][]{{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, {12, 8, 4, 0, 13, 9, 5, 1, 14, 10, 6, 2, 15, 11, 7, 3}, {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, {3, 7, 11, 15, 2, 6, 10, 14, 1, 5, 9, 13, 0, 4, 8, 12}};
private final int wallColor = (238 + (int) (Math.random() * 20.0D) - 10 << 16) + (238 + (int) (Math.random() * 20.0D) - 10 << 8) + (238 + (int) (Math.random() * 20.0D) - 10);
private final int doorColor = 238 + (int) (Math.random() * 20.0D) - 10 << 16;
private final int wallColor = (238 + (int) (random() * 20.0D) - 10 << 16) + (238 + (int) (random() * 20.0D) - 10 << 8) + (238 + (int) (random() * 20.0D) - 10);
private final int doorColor = 238 + (int) (random() * 20.0D) - 10 << 16;
private final Store store;
private final Map<Integer, UnderlayDefinition> underlays = new HashMap<>();
private final Map<Integer, OverlayDefinition> overlays = new HashMap<>();
private final Map<Integer, Image> scaledMapIcons = new HashMap<>();
private SpriteDefinition[] mapDecorations;
private RegionLoader regionLoader;
private final RegionLoader regionLoader;
private final AreaManager areas;
private final SpriteManager sprites;
private RSTextureProvider rsTextureProvider;
@@ -93,15 +105,106 @@ public class MapImageDumper
@Setter
private boolean outlineRegions;
public MapImageDumper(Store store)
@Getter
@Setter
private boolean renderMap = true;
@Getter
@Setter
private boolean renderObjects = true;
@Getter
@Setter
private boolean renderIcons = true;
@Getter
@Setter
private boolean transparency = false;
@Getter
@Setter
private boolean lowMemory = true;
public MapImageDumper(Store store, KeyProvider keyProvider)
{
this.store = store;
this.areas = new AreaManager(store);
this.sprites = new SpriteManager(store);
objectManager = new ObjectManager(store);
this(store, new RegionLoader(store, keyProvider));
}
public void load() throws IOException
public MapImageDumper(Store store, RegionLoader regionLoader)
{
this.store = store;
this.regionLoader = regionLoader;
this.areas = new AreaManager(store);
this.sprites = new SpriteManager(store);
this.objectManager = new ObjectManager(store);
}
public static void main(String[] args) throws IOException
{
Options options = new Options();
options.addOption(Option.builder().longOpt("cachedir").hasArg().required().build());
options.addOption(Option.builder().longOpt("xteapath").hasArg().required().build());
options.addOption(Option.builder().longOpt("outputdir").hasArg().required().build());
CommandLineParser parser = new DefaultParser();
CommandLine cmd;
try
{
cmd = parser.parse(options, args);
}
catch (ParseException ex)
{
System.err.println("Error parsing command line options: " + ex.getMessage());
System.exit(-1);
return;
}
final String cacheDirectory = cmd.getOptionValue("cachedir");
final String xteaJSONPath = cmd.getOptionValue("xteapath");
final String outputDirectory = cmd.getOptionValue("outputdir");
XteaKeyManager xteaKeyManager = new XteaKeyManager();
try (FileInputStream fin = new FileInputStream(xteaJSONPath))
{
xteaKeyManager.loadKeys(fin);
}
File base = new File(cacheDirectory);
File outDir = new File(outputDirectory);
outDir.mkdirs();
try (Store store = new Store(base))
{
store.load();
MapImageDumper dumper = new MapImageDumper(store, xteaKeyManager);
dumper.load();
for (int i = 0; i < Region.Z; ++i)
{
BufferedImage image = dumper.drawMap(i);
File imageFile = new File(outDir, "img-" + i + ".png");
ImageIO.write(image, "png", imageFile);
log.info("Wrote image {}", imageFile);
}
}
}
protected double random()
{
// the client would use a random value here, but we prefer determinism
return 0.5;
}
public MapImageDumper setBrightness(double brightness)
{
colorPalette = JagexColor.createPalette(brightness);
return this;
}
public MapImageDumper load() throws IOException
{
loadUnderlays(store);
loadOverlays(store);
@@ -111,10 +214,12 @@ public class MapImageDumper
textureManager.load();
rsTextureProvider = new RSTextureProvider(textureManager, sprites);
loadRegions(store);
loadRegions();
areas.load();
sprites.load();
loadSprites();
return this;
}
public BufferedImage drawMap(int z)
@@ -131,11 +236,19 @@ public class MapImageDumper
int pixelsX = dimX * MAP_SCALE;
int pixelsY = dimY * MAP_SCALE;
logger.info("Map image dimensions: {}px x {}px, {}px per map square ({} MB). Max memory: {}mb", pixelsX, pixelsY,
log.info("Map image dimensions: {}px x {}px, {}px per map square ({} MB). Max memory: {}mb", pixelsX, pixelsY,
MAP_SCALE, (pixelsX * pixelsY * 3 / 1024 / 1024),
Runtime.getRuntime().maxMemory() / 1024L / 1024L);
BufferedImage image = new BufferedImage(pixelsX, pixelsY, BufferedImage.TYPE_INT_RGB);
BufferedImage image;
if (lowMemory)
{
image = BigBufferedImage.create(pixelsX, pixelsY, transparency ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
}
else
{
image = new BufferedImage(pixelsX, pixelsY, transparency ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
}
drawMap(image, z);
drawObjects(image, z);
@@ -144,15 +257,35 @@ public class MapImageDumper
return image;
}
private void drawNeighborObjects(BufferedImage image, int rx, int ry, int dx, int dy, int z)
{
Region neighbor = regionLoader.findRegionForRegionCoordinates(rx + dx, ry + dy);
if (neighbor == null)
{
return;
}
drawObjects(image, Region.X * dx, Region.Y * -dy, neighbor, z);
}
public BufferedImage drawRegion(Region region, int z)
{
int pixelsX = Region.X * MAP_SCALE;
int pixelsY = Region.Y * MAP_SCALE;
BufferedImage image = new BufferedImage(pixelsX, pixelsY, BufferedImage.TYPE_INT_RGB);
BufferedImage image = new BufferedImage(pixelsX, pixelsY, transparency ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
drawMap(image, 0, 0, z, region);
drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), -1, -1, z);
drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), -1, 0, z);
drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), -1, 1, z);
drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), 0, -1, z);
drawObjects(image, 0, 0, region, z);
drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), 0, 1, z);
drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), 1, -1, z);
drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), 1, 0, z);
drawNeighborObjects(image, region.getRegionX(), region.getRegionY(), 1, 1, z);
drawMapIcons(image, 0, 0, region, z);
return image;
@@ -160,31 +293,41 @@ public class MapImageDumper
private void drawMap(BufferedImage image, int drawBaseX, int drawBaseY, int z, Region region)
{
int[][] map = new int[Region.X * MAP_SCALE][Region.Y * MAP_SCALE];
drawMap(map, region, z);
int[][] above = null;
if (z < 3)
if (!renderMap)
{
above = new int[Region.X * MAP_SCALE][Region.Y * MAP_SCALE];
drawMap(above, region, z + 1);
return;
}
int[][][] map = new int[4][][];
for (int x = 0; x < Region.X; ++x)
{
for (int y = 0; y < Region.Y; ++y)
{
boolean isBridge = (region.getTileSetting(1, x, Region.Y - y - 1) & 2) != 0;
int tileSetting = region.getTileSetting(z, x, Region.Y - y - 1);
if (!isBridge && ((tileSetting & 24) == 0))
int tileZ = z + (isBridge ? 1 : 0);
if (tileZ >= Region.Z)
{
drawTile(image, map, drawBaseX, drawBaseY, x, y);
continue;
}
if (z < 3 && isBridge) // client also has a check for &8 != 0 here
int tileSetting = region.getTileSetting(z, x, Region.Y - y - 1);
if ((tileSetting & 24) == 0)
{
drawTile(image, above, drawBaseX, drawBaseY, x, y);
if (z == 0 && isBridge)
{
drawTile(image, map, region, drawBaseX, drawBaseY, 0, x, y);
}
drawTile(image, map, region, drawBaseX, drawBaseY, tileZ, x, y);
}
if (tileZ < 3)
{
int upTileSetting = region.getTileSetting(z + 1, x, Region.Y - y - 1);
if ((upTileSetting & 8) != 0)
{
drawTile(image, map, region, drawBaseX, drawBaseY, tileZ + 1, x, y);
}
}
}
}
@@ -208,15 +351,27 @@ public class MapImageDumper
}
}
private void drawTile(BufferedImage to, int[][] pixels, int drawBaseX, int drawBaseY, int x, int y)
private void drawTile(BufferedImage to, int[][][] planes, Region region, int drawBaseX, int drawBaseY, int z, int x, int y)
{
int[][] pixels = planes[z];
if (pixels == null)
{
pixels = planes[z] = new int[Region.X * MAP_SCALE][Region.Y * MAP_SCALE];
drawMap(pixels, region, z);
}
for (int i = 0; i < MAP_SCALE; ++i)
{
for (int j = 0; j < MAP_SCALE; ++j)
{
to.setRGB(drawBaseX * MAP_SCALE + x * MAP_SCALE + i,
drawBaseY * MAP_SCALE + y * MAP_SCALE + j,
pixels[x * MAP_SCALE + i][y * MAP_SCALE + j]);
int argb = pixels[x * MAP_SCALE + i][y * MAP_SCALE + j];
if (argb != 0)
{
to.setRGB(drawBaseX * MAP_SCALE + x * MAP_SCALE + i,
drawBaseY * MAP_SCALE + y * MAP_SCALE + j,
argb);
}
}
}
}
@@ -345,11 +500,11 @@ public class MapImageDumper
if (underlayHsl != -1)
{
int var0 = method1792(underlayHsl, 96);
underlayRgb = colorPalette[var0];
underlayRgb = colorPalette[var0] | 0xFF000000;
}
int shape, rotation;
Integer overlayRgb = null;
int overlayRgb = 0;
if (overlayId == 0)
{
shape = rotation = 0;
@@ -361,28 +516,27 @@ public class MapImageDumper
OverlayDefinition overlayDefinition = findOverlay(overlayId - 1);
int overlayTexture = overlayDefinition.getTexture();
int rgb;
int hsl;
if (overlayTexture >= 0)
{
rgb = rsTextureProvider.getAverageTextureRGB(overlayTexture);
hsl = rsTextureProvider.getAverageTextureRGB(overlayTexture);
}
else if (overlayDefinition.getRgbColor() == 0xFF_00FF)
{
rgb = -2;
hsl = -2;
}
else
{
// randomness added here
int overlayHsl = packHsl(overlayDefinition.getHue(), overlayDefinition.getSaturation(), overlayDefinition.getLightness());
rgb = overlayHsl;
hsl = overlayHsl;
}
overlayRgb = 0;
if (rgb != -2)
if (hsl != -2)
{
int var0 = adjustHSLListness0(rgb, 96);
overlayRgb = colorPalette[var0];
int var0 = adjustHSLListness0(hsl, 96);
overlayRgb = colorPalette[var0] | 0xFF000000;
}
if (overlayDefinition.getSecondaryRgbColor() != -1)
@@ -390,9 +544,9 @@ public class MapImageDumper
int hue = overlayDefinition.getOtherHue();
int sat = overlayDefinition.getOtherSaturation();
int olight = overlayDefinition.getOtherLightness();
rgb = packHsl(hue, sat, olight);
int var0 = adjustHSLListness0(rgb, 96);
overlayRgb = colorPalette[var0];
hsl = packHsl(hue, sat, olight);
int var0 = adjustHSLListness0(hsl, 96);
overlayRgb = colorPalette[var0] | 0xFF000000;
}
}
@@ -486,208 +640,221 @@ public class MapImageDumper
private void drawObjects(BufferedImage image, int drawBaseX, int drawBaseY, Region region, int z)
{
Graphics2D graphics = image.createGraphics();
for (Location location : region.getLocations())
if (!renderObjects)
{
return;
}
int rotation = location.getOrientation();
int type = location.getType();
int localX = location.getPosition().getX() - region.getBaseX();
int localY = location.getPosition().getY() - region.getBaseY();
boolean isBridge = (region.getTileSetting(1, localX, localY) & 2) != 0;
if (location.getPosition().getZ() == z + 1)
List<Location> planeLocs = new ArrayList<>();
List<Location> pushDownLocs = new ArrayList<>();
List<List<Location>> layers = Arrays.asList(planeLocs, pushDownLocs);
for (int localX = 0; localX < Region.X; localX++)
{
int regionX = localX + region.getBaseX();
for (int localY = 0; localY < Region.Y; localY++)
{
if (!isBridge)
{
continue;
}
}
else if (location.getPosition().getZ() == z)
{
if (isBridge)
{
continue;
}
int regionY = localY + region.getBaseY();
if ((region.getTileSetting(z, localX, localY) & 24) != 0)
{
continue;
}
}
else
{
continue;
}
planeLocs.clear();
pushDownLocs.clear();
boolean isBridge = (region.getTileSetting(1, localX, localY) & 2) != 0;
int tileZ = z + (isBridge ? 1 : 0);
ObjectDefinition object = findObject(location.getId());
int drawX = (drawBaseX + localX) * MAP_SCALE;
int drawY = (drawBaseY + (Region.Y - 1 - localY)) * MAP_SCALE;
if (type >= 0 && type <= 3)
{
// this is a wall
int hash = (localY << 7) + localX + (location.getId() << 14) + 0x4000_0000;
if (object.getWallOrDoor() == 0)
for (Location loc : region.getLocations())
{
hash -= Integer.MIN_VALUE;
}
int rgb = wallColor;
if (hash > 0)
{
rgb = doorColor;
}
if (object.getMapSceneID() != -1)
{
Image spriteImage = scaledMapIcons.get(object.getMapSceneID());
graphics.drawImage(spriteImage, drawX * MAP_SCALE, drawY * MAP_SCALE, null);
}
else
{
if (type == 0 || type == 2)
Position pos = loc.getPosition();
if (pos.getX() != regionX || pos.getY() != regionY)
{
if (rotation == 0)
continue;
}
if (pos.getZ() == tileZ && (region.getTileSetting(z, localX, localY) & 24) == 0)
{
planeLocs.add(loc);
}
else if (z < 3 && pos.getZ() == tileZ + 1 && (region.getTileSetting(z + 1, localX, localY) & 8) != 0)
{
pushDownLocs.add(loc);
}
}
for (List<Location> locs : layers)
{
for (Location location : locs)
{
int type = location.getType();
if (type >= 0 && type <= 3)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
image.setRGB(drawX + 0, drawY + 1, rgb);
image.setRGB(drawX + 0, drawY + 2, rgb);
image.setRGB(drawX + 0, drawY + 3, rgb);
}
else if (rotation == 1)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
image.setRGB(drawX + 1, drawY + 0, rgb);
image.setRGB(drawX + 2, drawY + 0, rgb);
image.setRGB(drawX + 3, drawY + 0, rgb);
}
else if (rotation == 2)
{
image.setRGB(drawX + 3, drawY + 0, rgb);
image.setRGB(drawX + 3, drawY + 1, rgb);
image.setRGB(drawX + 3, drawY + 2, rgb);
image.setRGB(drawX + 3, drawY + 3, rgb);
}
else if (rotation == 3)
{
image.setRGB(drawX + 0, drawY + 3, rgb);
image.setRGB(drawX + 1, drawY + 3, rgb);
image.setRGB(drawX + 2, drawY + 3, rgb);
image.setRGB(drawX + 3, drawY + 3, rgb);
int rotation = location.getOrientation();
ObjectDefinition object = findObject(location.getId());
int drawX = (drawBaseX + localX) * MAP_SCALE;
int drawY = (drawBaseY + (Region.Y - object.getSizeY() - localY)) * MAP_SCALE;
int rgb = wallColor;
if (object.getWallOrDoor() != 0)
{
rgb = doorColor;
}
rgb |= 0xFF000000;
if (object.getMapSceneID() != -1)
{
blitMapDecoration(image, drawX, drawY, object);
}
else if (drawX >= 0 && drawY >= 0 && drawX < image.getWidth() && drawY < image.getHeight())
{
if (type == 0 || type == 2)
{
if (rotation == 0)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
image.setRGB(drawX + 0, drawY + 1, rgb);
image.setRGB(drawX + 0, drawY + 2, rgb);
image.setRGB(drawX + 0, drawY + 3, rgb);
}
else if (rotation == 1)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
image.setRGB(drawX + 1, drawY + 0, rgb);
image.setRGB(drawX + 2, drawY + 0, rgb);
image.setRGB(drawX + 3, drawY + 0, rgb);
}
else if (rotation == 2)
{
image.setRGB(drawX + 3, drawY + 0, rgb);
image.setRGB(drawX + 3, drawY + 1, rgb);
image.setRGB(drawX + 3, drawY + 2, rgb);
image.setRGB(drawX + 3, drawY + 3, rgb);
}
else if (rotation == 3)
{
image.setRGB(drawX + 0, drawY + 3, rgb);
image.setRGB(drawX + 1, drawY + 3, rgb);
image.setRGB(drawX + 2, drawY + 3, rgb);
image.setRGB(drawX + 3, drawY + 3, rgb);
}
}
if (type == 3)
{
if (rotation == 0)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
}
else if (rotation == 1)
{
image.setRGB(drawX + 3, drawY + 0, rgb);
}
else if (rotation == 2)
{
image.setRGB(drawX + 3, drawY + 3, rgb);
}
else if (rotation == 3)
{
image.setRGB(drawX + 0, drawY + 3, rgb);
}
}
if (type == 2)
{
if (rotation == 3)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
image.setRGB(drawX + 0, drawY + 1, rgb);
image.setRGB(drawX + 0, drawY + 2, rgb);
image.setRGB(drawX + 0, drawY + 3, rgb);
}
else if (rotation == 0)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
image.setRGB(drawX + 1, drawY + 0, rgb);
image.setRGB(drawX + 2, drawY + 0, rgb);
image.setRGB(drawX + 3, drawY + 0, rgb);
}
else if (rotation == 1)
{
image.setRGB(drawX + 3, drawY + 0, rgb);
image.setRGB(drawX + 3, drawY + 1, rgb);
image.setRGB(drawX + 3, drawY + 2, rgb);
image.setRGB(drawX + 3, drawY + 3, rgb);
}
else if (rotation == 2)
{
image.setRGB(drawX + 0, drawY + 3, rgb);
image.setRGB(drawX + 1, drawY + 3, rgb);
image.setRGB(drawX + 2, drawY + 3, rgb);
image.setRGB(drawX + 3, drawY + 3, rgb);
}
}
}
}
}
if (type == 3)
for (Location location : locs)
{
if (rotation == 0)
int type = location.getType();
if (type == 9)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
}
else if (rotation == 1)
{
image.setRGB(drawX + 3, drawY + 0, rgb);
}
else if (rotation == 2)
{
image.setRGB(drawX + 3, drawY + 3, rgb);
}
else if (rotation == 3)
{
image.setRGB(drawX + 0, drawY + 3, rgb);
int rotation = location.getOrientation();
ObjectDefinition object = findObject(location.getId());
int drawX = (drawBaseX + localX) * MAP_SCALE;
int drawY = (drawBaseY + (Region.Y - object.getSizeY() - localY)) * MAP_SCALE;
if (object.getMapSceneID() != -1)
{
blitMapDecoration(image, drawX, drawY, object);
continue;
}
if (drawX >= 0 && drawY >= 0 && drawX < image.getWidth() && drawY < image.getHeight())
{
int rgb = 0xFFEE_EEEE;
if (object.getWallOrDoor() != 0)
{
rgb = 0xFFEE_0000;
}
if (rotation != 0 && rotation != 2)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
image.setRGB(drawX + 1, drawY + 1, rgb);
image.setRGB(drawX + 2, drawY + 2, rgb);
image.setRGB(drawX + 3, drawY + 3, rgb);
}
else
{
image.setRGB(drawX + 0, drawY + 3, rgb);
image.setRGB(drawX + 1, drawY + 2, rgb);
image.setRGB(drawX + 2, drawY + 1, rgb);
image.setRGB(drawX + 3, drawY + 0, rgb);
}
}
}
}
if (type == 2)
for (Location location : locs)
{
if (rotation == 3)
int type = location.getType();
if (type == 22 || (type >= 9 && type <= 11))
{
image.setRGB(drawX + 0, drawY + 0, rgb);
image.setRGB(drawX + 0, drawY + 1, rgb);
image.setRGB(drawX + 0, drawY + 2, rgb);
image.setRGB(drawX + 0, drawY + 3, rgb);
}
else if (rotation == 0)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
image.setRGB(drawX + 1, drawY + 0, rgb);
image.setRGB(drawX + 2, drawY + 0, rgb);
image.setRGB(drawX + 3, drawY + 0, rgb);
}
else if (rotation == 1)
{
image.setRGB(drawX + 3, drawY + 0, rgb);
image.setRGB(drawX + 3, drawY + 1, rgb);
image.setRGB(drawX + 3, drawY + 2, rgb);
image.setRGB(drawX + 3, drawY + 3, rgb);
}
else if (rotation == 2)
{
image.setRGB(drawX + 0, drawY + 3, rgb);
image.setRGB(drawX + 1, drawY + 3, rgb);
image.setRGB(drawX + 2, drawY + 3, rgb);
image.setRGB(drawX + 3, drawY + 3, rgb);
ObjectDefinition object = findObject(location.getId());
int drawX = (drawBaseX + localX) * MAP_SCALE;
int drawY = (drawBaseY + (Region.Y - object.getSizeY() - localY)) * MAP_SCALE;
if (object.getMapSceneID() != -1)
{
blitMapDecoration(image, drawX, drawY, object);
}
}
}
}
}
else if (type == 9)
{
if (object.getMapSceneID() != -1)
{
Image spriteImage = scaledMapIcons.get(object.getMapSceneID());
graphics.drawImage(spriteImage, drawX, drawY, null);
continue;
}
int hash = (localY << 7) + localX + (location.getId() << 14) + 0x4000_0000;
if (object.getWallOrDoor() == 0)
{
hash -= Integer.MIN_VALUE;
}
if ((hash >> 29 & 3) != 2)
{
continue;
}
int rgb = 0xEE_EEEE;
if (hash > 0)
{
rgb = 0xEE_0000;
}
if (rotation != 0 && rotation != 2)
{
image.setRGB(drawX + 0, drawY + 0, rgb);
image.setRGB(drawX + 1, drawY + 1, rgb);
image.setRGB(drawX + 2, drawY + 2, rgb);
image.setRGB(drawX + 3, drawY + 3, rgb);
}
else
{
image.setRGB(drawX + 0, drawY + 3, rgb);
image.setRGB(drawX + 1, drawY + 2, rgb);
image.setRGB(drawX + 2, drawY + 1, rgb);
image.setRGB(drawX + 3, drawY + 0, rgb);
}
}
else if (type == 22 || (type >= 9 && type <= 11))
{
// ground object
if (object.getMapSceneID() != -1)
{
Image spriteImage = scaledMapIcons.get(object.getMapSceneID());
graphics.drawImage(spriteImage, drawX, drawY, null);
}
}
}
graphics.dispose();
}
private void drawObjects(BufferedImage image, int z)
@@ -715,7 +882,7 @@ public class MapImageDumper
Graphics2D graphics = image.createGraphics();
drawMapIcons(graphics, region, z, drawBaseX, drawBaseY);
drawMapIcons(image, region, z, drawBaseX, drawBaseY);
if (labelRegions)
{
@@ -854,12 +1021,22 @@ public class MapImageDumper
}
}
private void drawMapIcons(Graphics2D graphics, Region region, int z, int drawBaseX, int drawBaseY)
private void drawMapIcons(BufferedImage img, Region region, int z, int drawBaseX, int drawBaseY)
{
if (!renderIcons)
{
return;
}
for (Location location : region.getLocations())
{
int localX = location.getPosition().getX() - region.getBaseX();
int localY = location.getPosition().getY() - region.getBaseY();
boolean isBridge = (region.getTileSetting(1, localX, localY) & 2) != 0;
int tileZ = z + (isBridge ? 1 : 0);
int localZ = location.getPosition().getZ();
if (z != 0 && localZ != z)
if (z != 0 && localZ != tileZ)
{
// draw all icons on z=0
continue;
@@ -869,9 +1046,6 @@ public class MapImageDumper
assert od != null;
int localX = location.getPosition().getX() - region.getBaseX();
int localY = location.getPosition().getY() - region.getBaseY();
int drawX = drawBaseX + localX;
int drawY = drawBaseY + (Region.Y - 1 - localY);
@@ -880,27 +1054,26 @@ public class MapImageDumper
AreaDefinition area = areas.getArea(od.getMapAreaId());
assert area != null;
int spriteId = area.spriteId;
SpriteDefinition sprite = sprites.findSprite(spriteId, 0);
SpriteDefinition sprite = sprites.findSprite(area.spriteId, 0);
assert sprite != null;
BufferedImage iconImage = sprites.getSpriteImage(sprite);
graphics.drawImage(iconImage, drawX * MAP_SCALE, drawY * MAP_SCALE, null);
blitIcon(img,
2 + (drawX * MAP_SCALE) - (sprite.getMaxWidth() / 2),
2 + (drawY * MAP_SCALE) - (sprite.getMaxHeight() / 2),
sprite);
}
}
}
private void loadRegions(Store store) throws IOException
private void loadRegions() throws IOException
{
regionLoader = new RegionLoader(store);
regionLoader.loadRegions();
regionLoader.calculateBounds();
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());
log.debug("North most region: {}", regionLoader.getLowestY().getBaseY());
log.debug("South most region: {}", regionLoader.getHighestY().getBaseY());
log.debug("West most region: {}", regionLoader.getLowestX().getBaseX());
log.debug("East most region: {}", regionLoader.getHighestX().getBaseX());
}
private void loadUnderlays(Store store) throws IOException
@@ -953,35 +1126,42 @@ public class MapImageDumper
{
Storage storage = store.getStorage();
Index index = store.getIndex(IndexType.SPRITES);
final int mapsceneHash = Djb2.hash("mapscene");
Archive a = index.findArchiveByName("mapscene");
byte[] contents = a.decompress(storage.loadArchive(a));
for (Archive a : index.getArchives())
SpriteLoader loader = new SpriteLoader();
mapDecorations = loader.load(a.getArchiveId(), contents);
}
private void blitMapDecoration(BufferedImage dst, int x, int y, ObjectDefinition object)
{
SpriteDefinition sprite = mapDecorations[object.getMapSceneID()];
int ox = (object.getSizeX() * MAP_SCALE - sprite.getWidth()) / 2;
int oy = (object.getSizeY() * MAP_SCALE - sprite.getHeight()) / 2;
blitIcon(dst, x + ox, y + oy, sprite);
}
private void blitIcon(BufferedImage dst, int x, int y, SpriteDefinition sprite)
{
x += sprite.getOffsetX();
y += sprite.getOffsetY();
int ymin = Math.max(0, -y);
int ymax = Math.min(sprite.getHeight(), dst.getHeight() - y);
int xmin = Math.max(0, -x);
int xmax = Math.min(sprite.getWidth(), dst.getWidth() - x);
for (int yo = ymin; yo < ymax; yo++)
{
byte[] contents = a.decompress(storage.loadArchive(a));
SpriteLoader loader = new SpriteLoader();
SpriteDefinition[] sprites = loader.load(a.getArchiveId(), contents);
for (SpriteDefinition sprite : sprites)
for (int xo = xmin; xo < xmax; xo++)
{
if (sprite.getHeight() <= 0 || sprite.getWidth() <= 0)
int rgb = sprite.getPixels()[xo + (yo * sprite.getWidth())];
if (rgb != 0)
{
continue;
}
if (a.getNameHash() == mapsceneHash)
{
BufferedImage spriteImage = new BufferedImage(sprite.getWidth(), sprite.getHeight(), BufferedImage.TYPE_INT_ARGB);
spriteImage.setRGB(0, 0, sprite.getWidth(), sprite.getHeight(), sprite.getPixels(), 0, sprite.getWidth());
// scale image down so it fits
Image scaledImage = spriteImage.getScaledInstance(MAPICON_MAX_WIDTH, MAPICON_MAX_HEIGHT, 0);
assert scaledMapIcons.containsKey(sprite.getFrame()) == false;
scaledMapIcons.put(sprite.getFrame(), scaledImage);
dst.setRGB(x + xo, y + yo, rgb | 0xFF000000);
}
}
}
}
}

View File

@@ -1,161 +0,0 @@
/*
* Copyright (c) 2018, 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.item;
import lombok.Getter;
public class ColorPalette
{
@Getter
private final int[] colorPalette;
public ColorPalette(double brightness, int var2, int var3)
{
colorPalette = buildColorPalettee(brightness, var2, var3);
}
private int[] buildColorPalettee(double brightness, int var2, int var3)
{
int[] colorPalette = new int[65536];
int var4 = var2 * 128;
for (int var5 = var2; var5 < var3; ++var5)
{
double var6 = (double) (var5 >> 3) / 64.0D + 0.0078125D;
double var8 = (double) (var5 & 7) / 8.0D + 0.0625D;
for (int var10 = 0; var10 < 128; ++var10)
{
double var11 = (double) var10 / 128.0D;
double var13 = var11;
double var15 = var11;
double var17 = var11;
if (var8 != 0.0D)
{
double var19;
if (var11 < 0.5D)
{
var19 = var11 * (1.0D + var8);
}
else
{
var19 = var11 + var8 - var11 * var8;
}
double var21 = 2.0D * var11 - var19;
double var23 = var6 + 0.3333333333333333D;
if (var23 > 1.0D)
{
--var23;
}
double var27 = var6 - 0.3333333333333333D;
if (var27 < 0.0D)
{
++var27;
}
if (6.0D * var23 < 1.0D)
{
var13 = var21 + (var19 - var21) * 6.0D * var23;
}
else if (2.0D * var23 < 1.0D)
{
var13 = var19;
}
else if (3.0D * var23 < 2.0D)
{
var13 = var21 + (var19 - var21) * (0.6666666666666666D - var23) * 6.0D;
}
else
{
var13 = var21;
}
if (6.0D * var6 < 1.0D)
{
var15 = var21 + (var19 - var21) * 6.0D * var6;
}
else if (2.0D * var6 < 1.0D)
{
var15 = var19;
}
else if (3.0D * var6 < 2.0D)
{
var15 = var21 + (var19 - var21) * (0.6666666666666666D - var6) * 6.0D;
}
else
{
var15 = var21;
}
if (6.0D * var27 < 1.0D)
{
var17 = var21 + (var19 - var21) * 6.0D * var27;
}
else if (2.0D * var27 < 1.0D)
{
var17 = var19;
}
else if (3.0D * var27 < 2.0D)
{
var17 = var21 + (var19 - var21) * (0.6666666666666666D - var27) * 6.0D;
}
else
{
var17 = var21;
}
}
int var29 = (int) (var13 * 256.0D);
int var20 = (int) (var15 * 256.0D);
int var30 = (int) (var17 * 256.0D);
int var22 = var30 + (var20 << 8) + (var29 << 16);
var22 = adjustRGB(var22, brightness);
if (var22 == 0)
{
var22 = 1;
}
colorPalette[var4++] = var22;
}
}
return colorPalette;
}
private static int adjustRGB(int var0, double var1)
{
double var3 = (double) (var0 >> 16) / 256.0D;
double var5 = (double) (var0 >> 8 & 255) / 256.0D;
double var7 = (double) (var0 & 255) / 256.0D;
var3 = Math.pow(var3, var1);
var5 = Math.pow(var5, var1);
var7 = Math.pow(var7, var1);
int var9 = (int) (var3 * 256.0D);
int var10 = (int) (var5 * 256.0D);
int var11 = (int) (var7 * 256.0D);
return var11 + (var10 << 8) + (var9 << 16);
}
}

View File

@@ -24,6 +24,8 @@
*/
package net.runelite.cache.item;
import net.runelite.cache.models.JagexColor;
class Graphics3D extends Rasterizer2D
{
private static final double UNIT = Math.PI / 1024d; // How much of the circle each unit of SINE/COSINE is
@@ -132,9 +134,9 @@ class Graphics3D extends Rasterizer2D
Rasterizer3D_clipMidY2 = Rasterizer3D_clipHeight - centerY;
}
public final void setBrightness(double var0)
public final void setBrightness(double brightness)
{
colorPalette = new ColorPalette(var0, 0, 512).getColorPalette();
colorPalette = JagexColor.createPalette(brightness);
}
final void rasterGouraud(int var0, int var1, int var2, int var3, int var4, int var5, int var6, int var7, int var8)

View File

@@ -33,6 +33,7 @@ import net.runelite.cache.definitions.providers.ModelProvider;
import net.runelite.cache.definitions.providers.SpriteProvider;
import net.runelite.cache.definitions.providers.TextureProvider;
import net.runelite.cache.models.FaceNormal;
import net.runelite.cache.models.JagexColor;
import net.runelite.cache.models.VertexNormal;
public class ItemSpriteFactory
@@ -111,7 +112,7 @@ public class ItemSpriteFactory
Sprite spritePixels = new Sprite(36, 32);
Graphics3D graphics = new Graphics3D(rsTextureProvider);
graphics.setBrightness(0.6d);
graphics.setBrightness(JagexColor.BRIGHTNESS_MAX);
graphics.setRasterBuffer(spritePixels.pixels, 36, 32);
graphics.reset();
graphics.setRasterClipping();

View File

@@ -29,7 +29,7 @@ public final class JagexColor
public static final double BRIGHTNESS_MAX = .6;
public static final double BRIGHTNESS_HIGH = .7;
public static final double BRIGHTNESS_LOW = .8;
public static final double BRIGTHNESS_MIN = .9;
public static final double BRIGHTNESS_MIN = .9;
private static final double HUE_OFFSET = (.5 / 64.D);
private static final double SATURATION_OFFSET = (.5 / 8.D);
@@ -133,4 +133,14 @@ public final class JagexColor
| ((int) (g * 256.0D) << 8)
| (int) (b * 256.0D);
}
public static int[] createPalette(double brightness)
{
int[] colorPalette = new int[65536];
for (int i = 0; i < colorPalette.length; i++)
{
colorPalette[i] = HSLtoRGB((short) i, brightness);
}
return colorPalette;
}
}

View File

@@ -31,7 +31,7 @@ import net.runelite.cache.definitions.TextureDefinition;
public class ObjExporter
{
private static final double BRIGHTNESS = JagexColor.BRIGTHNESS_MIN;
private static final double BRIGHTNESS = JagexColor.BRIGHTNESS_MIN;
private final TextureManager textureManager;
private final ModelDefinition model;

View File

@@ -28,6 +28,7 @@ import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import net.runelite.cache.IndexType;
import net.runelite.cache.definitions.LocationsDefinition;
import net.runelite.cache.definitions.MapDefinition;
@@ -37,39 +38,44 @@ import net.runelite.cache.fs.Archive;
import net.runelite.cache.fs.Index;
import net.runelite.cache.fs.Storage;
import net.runelite.cache.fs.Store;
import net.runelite.cache.util.XteaKeyManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.runelite.cache.util.KeyProvider;
@Slf4j
public class RegionLoader
{
private static final Logger logger = LoggerFactory.getLogger(RegionLoader.class);
private static final int MAX_REGION = 32768;
private final Store store;
private final Index index;
private final XteaKeyManager keyManager;
private final KeyProvider keyProvider;
private final Map<Integer, Region> regions = new HashMap<>();
private Region lowestX = null, lowestY = null;
private Region highestX = null, highestY = null;
public RegionLoader(Store store)
public RegionLoader(Store store, KeyProvider keyProvider)
{
this.store = store;
index = store.getIndex(IndexType.MAPS);
keyManager = new XteaKeyManager();
this.keyProvider = keyProvider;
}
public void loadRegions() throws IOException
{
if (!this.regions.isEmpty())
{
return;
}
for (int i = 0; i < MAX_REGION; ++i)
{
Region region = this.loadRegionFromArchive(i);
if (region != null)
try
{
regions.put(i, region);
this.loadRegionFromArchive(i);
}
catch (IOException ex)
{
log.debug("Can't decrypt region " + i, ex);
}
}
}
@@ -97,25 +103,31 @@ public class RegionLoader
Region region = new Region(i);
region.loadTerrain(mapDef);
Integer[] keysTmp = keyManager.getKeys(i);
if (keysTmp != null)
int[] keys = keyProvider.getKey(i);
if (keys != null)
{
int[] keys = {keysTmp[0], keysTmp[1], keysTmp[2], keysTmp[3]};
try
{
data = land.decompress(storage.loadArchive(land), keys);
LocationsDefinition locDef = new LocationsLoader().load(x, y, data);
region.loadLocations(locDef);
}
catch (IOException ex)
{
logger.debug("Can't decrypt region " + i, ex);
}
data = land.decompress(storage.loadArchive(land), keys);
LocationsDefinition locDef = new LocationsLoader().load(x, y, data);
region.loadLocations(locDef);
}
regions.put(i, region);
return region;
}
public Region loadRegion(int id, MapDefinition map, LocationsDefinition locs)
{
Region r = new Region(id);
r.loadTerrain(map);
if (locs != null)
{
r.loadLocations(locs);
}
regions.put(id, r);
return r;
}
public void calculateBounds()
{
for (Region region : regions.values())
@@ -154,6 +166,11 @@ public class RegionLoader
return regions.get((x << 8) | y);
}
public Region findRegionForRegionCoordinates(int x, int y)
{
return regions.get((x << 8) | y);
}
public Region getLowestX()
{
return lowestX;
@@ -173,4 +190,4 @@ public class RegionLoader
{
return highestY;
}
}
}

View File

@@ -0,0 +1,346 @@
package net.runelite.cache.util;
/*
* This class is part of MCFS (Mission Control - Flight Software) a development
* of Team Puli Space, official Google Lunar XPRIZE contestant.
* This class is released under Creative Commons CC0.
* @author Zsolt Pocze, Dimitry Polivaev
* Please like us on facebook, and/or join our Small Step Club.
* http://www.pulispace.com
* https://www.facebook.com/pulispace
* http://nyomdmegteis.hu/en/
*/
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.image.BandedSampleModel;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BigBufferedImage extends BufferedImage
{
private static final String TMP_DIR = System.getProperty("java.io.tmpdir");
private static final int MAX_PIXELS_IN_MEMORY = 1024 * 1024;
public static BufferedImage create(int width, int height, int imageType)
{
if (width * height > MAX_PIXELS_IN_MEMORY)
{
try
{
final File tempDir = new File(TMP_DIR);
return createBigBufferedImage(tempDir, width, height, imageType);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
else
{
return new BufferedImage(width, height, imageType);
}
}
public static BufferedImage create(File inputFile, int imageType) throws IOException
{
try (ImageInputStream stream = ImageIO.createImageInputStream(inputFile))
{
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
if (readers.hasNext())
{
try
{
ImageReader reader = readers.next();
reader.setInput(stream, true, true);
int width = reader.getWidth(reader.getMinIndex());
int height = reader.getHeight(reader.getMinIndex());
BufferedImage image = create(width, height, imageType);
int cores = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
int block = Math.min(MAX_PIXELS_IN_MEMORY / cores / width, (int) (Math.ceil(height / (double) cores)));
ExecutorService generalExecutor = Executors.newFixedThreadPool(cores);
List<Callable<ImagePartLoader>> partLoaders = new ArrayList<>();
for (int y = 0; y < height; y += block)
{
partLoaders.add(new ImagePartLoader(
y, width, Math.min(block, height - y), inputFile, image));
}
generalExecutor.invokeAll(partLoaders);
generalExecutor.shutdown();
return image;
}
catch (InterruptedException ex)
{
log.error(null, ex);
}
}
}
return null;
}
private static BufferedImage createBigBufferedImage(File tempDir, int width, int height, int imageType)
throws IOException
{
FileDataBuffer buffer = new FileDataBuffer(tempDir, width * height, 4);
ColorModel colorModel;
BandedSampleModel sampleModel;
switch (imageType)
{
case TYPE_INT_RGB:
colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
new int[]{8, 8, 8, 0},
false,
false,
ComponentColorModel.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE, width, height, 3);
break;
case TYPE_INT_ARGB:
colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
new int[]{8, 8, 8, 8},
true,
false,
ComponentColorModel.TRANSLUCENT,
DataBuffer.TYPE_BYTE);
sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE, width, height, 4);
break;
default:
throw new IllegalArgumentException("Unsupported image type: " + imageType);
}
SimpleRaster raster = new SimpleRaster(sampleModel, buffer, new Point(0, 0));
BigBufferedImage image = new BigBufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
return image;
}
private static class ImagePartLoader implements Callable<ImagePartLoader>
{
private final int y;
private final BufferedImage image;
private final Rectangle region;
private final File file;
public ImagePartLoader(int y, int width, int height, File file, BufferedImage image)
{
this.y = y;
this.image = image;
this.file = file;
region = new Rectangle(0, y, width, height);
}
@Override
public ImagePartLoader call() throws Exception
{
Thread.currentThread().setPriority((Thread.MIN_PRIORITY + Thread.NORM_PRIORITY) / 2);
try (ImageInputStream stream = ImageIO.createImageInputStream(file);)
{
Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
if (readers.hasNext())
{
ImageReader reader = readers.next();
reader.setInput(stream, true, true);
ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(region);
BufferedImage part = reader.read(0, param);
Raster source = part.getRaster();
WritableRaster target = image.getRaster();
target.setRect(0, y, source);
}
}
return ImagePartLoader.this;
}
}
private BigBufferedImage(ColorModel cm, SimpleRaster raster, boolean isRasterPremultiplied, Hashtable<?, ?> properties)
{
super(cm, raster, isRasterPremultiplied, properties);
}
public void dispose()
{
((SimpleRaster) getRaster()).dispose();
}
public static void dispose(RenderedImage image)
{
if (image instanceof BigBufferedImage)
{
((BigBufferedImage) image).dispose();
}
}
private static class SimpleRaster extends WritableRaster
{
public SimpleRaster(SampleModel sampleModel, FileDataBuffer dataBuffer, Point origin)
{
super(sampleModel, dataBuffer, origin);
}
public void dispose()
{
((FileDataBuffer) getDataBuffer()).dispose();
}
}
private static final class FileDataBufferDeleterHook extends Thread
{
static
{
Runtime.getRuntime().addShutdownHook(new FileDataBufferDeleterHook());
}
private static final HashSet<FileDataBuffer> undisposedBuffers = new HashSet<>();
@Override
public void run()
{
final FileDataBuffer[] buffers = undisposedBuffers.toArray(new FileDataBuffer[0]);
for (FileDataBuffer b : buffers)
{
b.disposeNow();
}
}
}
private static class FileDataBuffer extends DataBuffer
{
private final String id = "buffer-" + System.currentTimeMillis() + "-" + ((int) (Math.random() * 1000));
private File dir;
private String path;
private File[] files;
private RandomAccessFile[] accessFiles;
private MappedByteBuffer[] buffer;
public FileDataBuffer(File dir, int size) throws IOException
{
super(TYPE_BYTE, size);
this.dir = dir;
init();
}
public FileDataBuffer(File dir, int size, int numBanks) throws IOException
{
super(TYPE_BYTE, size, numBanks);
this.dir = dir;
init();
}
private void init() throws IOException
{
FileDataBufferDeleterHook.undisposedBuffers.add(this);
if (dir == null)
{
dir = new File(".");
}
if (!dir.exists())
{
throw new RuntimeException("FileDataBuffer constructor parameter dir does not exist: " + dir);
}
if (!dir.isDirectory())
{
throw new RuntimeException("FileDataBuffer constructor parameter dir is not a directory: " + dir);
}
path = dir.getPath() + "/" + id;
File subDir = new File(path);
subDir.mkdir();
buffer = new MappedByteBuffer[banks];
accessFiles = new RandomAccessFile[banks];
files = new File[banks];
for (int i = 0; i < banks; i++)
{
File file = files[i] = new File(path + "/bank" + i + ".dat");
final RandomAccessFile randomAccessFile = accessFiles[i] = new RandomAccessFile(file, "rw");
buffer[i] = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, getSize());
}
}
@Override
public int getElem(int bank, int i)
{
return buffer[bank].get(i) & 0xff;
}
@Override
public void setElem(int bank, int i, int val)
{
buffer[bank].put(i, (byte) val);
}
@Override
protected void finalize() throws Throwable
{
dispose();
}
public void dispose()
{
new Thread()
{
@Override
public void run()
{
disposeNow();
}
}.start();
}
private void disposeNow()
{
this.buffer = null;
if (accessFiles != null)
{
for (RandomAccessFile file : accessFiles)
{
try
{
file.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
accessFiles = null;
}
if (files != null)
{
for (File file : files)
{
file.delete();
}
files = null;
}
if (path != null)
{
new File(path).delete();
path = null;
}
}
}
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 Abex
* 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.util;
public interface KeyProvider
{
int[] getKey(int region);
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (c) 2021, 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.util;
import lombok.Data;
@Data
public class XteaKey
{
private int region;
private int keys[];
}

View File

@@ -24,25 +24,40 @@
*/
package net.runelite.cache.util;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class XteaKeyManager
public class XteaKeyManager implements KeyProvider
{
private static final Logger logger = LoggerFactory.getLogger(XteaKeyManager.class);
private Map<Integer, Integer[]> keys = new HashMap<>();
private final Map<Integer, int[]> keys = new HashMap<>();
public void loadKeys()
public void loadKeys(InputStream in)
{
keys = null;
// CHECKSTYLE:OFF
List<XteaKey> k = new Gson()
.fromJson(new InputStreamReader(in, StandardCharsets.UTF_8), new TypeToken<List<XteaKey>>() { }.getType());
// CHECKSTYLE:ON
for (XteaKey key : k)
{
keys.put(key.getRegion(), key.getKeys());
}
logger.info("Loaded {} keys", keys.size());
}
public Integer[] getKeys(int region)
@Override
public int[] getKey(int region)
{
return keys.get(region);
}

View File

@@ -72,8 +72,8 @@ public class FrameDumper
store.load();
Storage storage = store.getStorage();
Index frameIndex = store.getIndex(IndexType.FRAMES);
Index framemapIndex = store.getIndex(IndexType.FRAMEMAPS);
Index frameIndex = store.getIndex(IndexType.ANIMATIONS);
Index framemapIndex = store.getIndex(IndexType.SKELETONS);
for (Archive archive : frameIndex.getArchives())
{

View File

@@ -66,7 +66,7 @@ public class FramemapDumper
store.load();
Storage storage = store.getStorage();
Index index = store.getIndex(IndexType.FRAMEMAPS);
Index index = store.getIndex(IndexType.SKELETONS);
for (Archive archive : index.getArchives())
{

View File

@@ -52,7 +52,7 @@ public class HeightMapDumperTest
store.load();
HeightMapDumper dumper = new HeightMapDumper(store);
dumper.load();
dumper.load(null);
BufferedImage image = dumper.drawHeightMap(0);

View File

@@ -65,7 +65,6 @@ public class MapDumperTest
File base = StoreLocation.LOCATION,
outDir = folder.newFolder();
XteaKeyManager keyManager = new XteaKeyManager();
keyManager.loadKeys();
try (Store store = new Store(base))
{
@@ -76,7 +75,7 @@ public class MapDumperTest
for (int i = 0; i < MAX_REGIONS; i++)
{
Integer[] keysTmp = keyManager.getKeys(i);
int[] keys = keyManager.getKey(i);
int x = i >> 8;
int y = i & 0xFF;
@@ -95,9 +94,8 @@ public class MapDumperTest
Files.write(data, new File(outDir, "m" + x + "_" + y + ".dat"));
if (keysTmp != null)
if (keys != null)
{
int[] keys = {keysTmp[0], keysTmp[1], keysTmp[2], keysTmp[3]};
try
{
data = land.decompress(storage.loadArchive(land), keys);
@@ -122,7 +120,6 @@ public class MapDumperTest
Storage storage = store.getStorage();
Index index = store.getIndex(IndexType.MAPS);
XteaKeyManager keyManager = new XteaKeyManager();
keyManager.loadKeys();
for (int i = 0; i < MAX_REGIONS; ++i)
{
@@ -143,10 +140,9 @@ public class MapDumperTest
MapDefinition mapDef = new MapLoader().load(x, y, data);
LocationsDefinition locDef = null;
Integer[] keysTmp = keyManager.getKeys(i);
if (keysTmp != null)
int[] keys = keyManager.getKey(i);
if (keys != null)
{
int[] keys = {keysTmp[0], keysTmp[1], keysTmp[2], keysTmp[3]};
try
{
data = land.decompress(storage.loadArchive(land), keys);

View File

@@ -31,6 +31,7 @@ import javax.imageio.ImageIO;
import net.runelite.cache.fs.Store;
import net.runelite.cache.region.Region;
import net.runelite.cache.region.RegionLoader;
import net.runelite.cache.util.XteaKeyManager;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@@ -56,7 +57,10 @@ public class MapImageDumperTest
{
store.load();
MapImageDumper dumper = new MapImageDumper(store);
XteaKeyManager keyManager = new XteaKeyManager();
keyManager.loadKeys(null);
MapImageDumper dumper = new MapImageDumper(store, keyManager);
dumper.load();
for (int i = 0; i < Region.Z; ++i)
@@ -82,10 +86,13 @@ public class MapImageDumperTest
{
store.load();
RegionLoader regionLoader = new RegionLoader(store);
XteaKeyManager keyManager = new XteaKeyManager();
keyManager.loadKeys(null);
RegionLoader regionLoader = new RegionLoader(store, keyManager);
regionLoader.loadRegions();
MapImageDumper dumper = new MapImageDumper(store);
MapImageDumper dumper = new MapImageDumper(store, regionLoader);
dumper.load();
int z = 0;

View File

@@ -67,8 +67,8 @@ public class TrackDumperTest
store.load();
Storage storage = store.getStorage();
Index index = store.getIndex(IndexType.TRACK1);
Index index2 = store.getIndex(IndexType.TRACK2);
Index index = store.getIndex(IndexType.MUSIC_TRACKS);
Index index2 = store.getIndex(IndexType.MUSIC_JINGLES);
for (Archive archive : index.getArchives())
{

View File

@@ -66,7 +66,7 @@ public class WorldMapDumperTest
store.load();
Storage storage = store.getStorage();
Index index = store.getIndex(IndexType.WORLDMAP);
Index index = store.getIndex(IndexType.WORLDMAP_OLD);
Archive archive = index.getArchive(0); // there is also archive 1/2, but their data format is not this
byte[] archiveData = storage.loadArchive(archive);

View File

@@ -30,7 +30,7 @@ import org.junit.Test;
public class JagexColorTest
{
private static final double[] BRIGHTNESS_LEVELS = {
JagexColor.BRIGTHNESS_MIN,
JagexColor.BRIGHTNESS_MIN,
JagexColor.BRIGHTNESS_LOW,
JagexColor.BRIGHTNESS_HIGH,
JagexColor.BRIGHTNESS_MAX,