Cache already-decoded Archives.

This commit is contained in:
Major-
2015-08-27 22:25:22 +01:00
parent 4680f1a181
commit 493c5010e8
6 changed files with 179 additions and 128 deletions
+17
View File
@@ -1,5 +1,7 @@
package org.apollo.cache;
import java.io.File;
/**
* A class which points to a file in the cache.
*
@@ -28,6 +30,16 @@ public final class FileDescriptor {
this.file = file;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof FileDescriptor) {
FileDescriptor other = (FileDescriptor) obj;
return type == other.type && file == other.file;
}
return false;
}
/**
* Gets the file id.
*
@@ -46,4 +58,9 @@ public final class FileDescriptor {
return type;
}
@Override
public int hashCode() {
return file * FileSystemConstants.ARCHIVE_COUNT + type;
}
}
+74 -40
View File
@@ -4,13 +4,18 @@ import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.zip.CRC32;
import com.google.common.base.Preconditions;
import org.apollo.cache.archive.Archive;
/**
* A file system based on top of the operating system's file system. It consists of a data file and index files. Index
@@ -20,6 +25,21 @@ import com.google.common.base.Preconditions;
*/
public final class IndexedFileSystem implements Closeable {
/**
* The Map that caches already-decoded Archives.
*/
private final Map<FileDescriptor, Archive> cache = new HashMap<>(FileSystemConstants.ARCHIVE_COUNT);
/**
* The index files.
*/
private final RandomAccessFile[] indices = new RandomAccessFile[256];
/**
* Read only flag.
*/
private final boolean readOnly;
/**
* The cached CRC table.
*/
@@ -35,16 +55,6 @@ public final class IndexedFileSystem implements Closeable {
*/
private RandomAccessFile data;
/**
* The index files.
*/
private final RandomAccessFile[] indices = new RandomAccessFile[256];
/**
* Read only flag.
*/
private final boolean readOnly;
/**
* Creates the file system with the specified base directory.
*
@@ -75,30 +85,26 @@ public final class IndexedFileSystem implements Closeable {
}
/**
* Automatically detect the layout of the specified directory.
* Gets the {@link Archive} pointed to by the specified {@link FileDescriptor}.
*
* @param base The base directory.
* @throws FileNotFoundException If the data files could not be found.
* @param type The file type.
* @param file The file id.
* @return The Archive.
* @throws IOException If there is an error decoding the Archive.
*/
private void detectLayout(Path base) throws FileNotFoundException {
int indexCount = 0;
for (int index = 0; index < indices.length; index++) {
Path idx = base.resolve("main_file_cache.idx" + index);
if (Files.exists(idx) && !Files.isDirectory(idx)) {
indexCount++;
indices[index] = new RandomAccessFile(idx.toFile(), readOnly ? "r" : "rw");
public Archive getArchive(int type, int file) throws IOException {
FileDescriptor descriptor = new FileDescriptor(type, file);
Archive cached = cache.get(descriptor);
if (cached == null) {
cached = Archive.decode(getFile(descriptor));
synchronized (this) {
cache.put(descriptor, cached);
}
}
if (indexCount <= 0) {
throw new FileNotFoundException("No index file(s) present.");
}
Path resources = base.resolve("main_file_cache.dat");
if (Files.exists(resources) && !Files.isDirectory(resources)) {
data = new RandomAccessFile(resources.toFile(), readOnly ? "r" : "rw");
} else {
throw new FileNotFoundException("No data file present.");
}
return cached;
}
/**
@@ -146,6 +152,7 @@ public final class IndexedFileSystem implements Closeable {
return crcTable.duplicate();
}
}
throw new IllegalStateException("Cannot get CRC table from a writable file system.");
}
@@ -244,6 +251,42 @@ public final class IndexedFileSystem implements Closeable {
return getFile(new FileDescriptor(type, file));
}
/**
* Checks if this {@link IndexedFileSystem} is read only.
*
* @return {@code true} if so, {@code false} if not.
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Automatically detect the layout of the specified directory.
*
* @param base The base directory.
* @throws FileNotFoundException If the data files could not be found.
*/
private void detectLayout(Path base) throws FileNotFoundException {
int indexCount = 0;
for (int index = 0; index < indices.length; index++) {
Path idx = base.resolve("main_file_cache.idx" + index);
if (Files.exists(idx) && !Files.isDirectory(idx)) {
indexCount++;
indices[index] = new RandomAccessFile(idx.toFile(), readOnly ? "r" : "rw");
}
}
if (indexCount <= 0) {
throw new FileNotFoundException("No index file(s) present.");
}
Path resources = base.resolve("main_file_cache.dat");
if (Files.exists(resources) && !Files.isDirectory(resources)) {
data = new RandomAccessFile(resources.toFile(), readOnly ? "r" : "rw");
} else {
throw new FileNotFoundException("No data file present.");
}
}
/**
* Gets the number of files with the specified type.
*
@@ -251,7 +294,7 @@ public final class IndexedFileSystem implements Closeable {
* @return The number of files.
* @throws IOException If there is an error getting the length of the specified index file.
* @throws IndexOutOfBoundsException If {@code type} is less than 0, or greater than or equal to the amount of
* indices.
* indices.
*/
private int getFileCount(int type) throws IOException {
Preconditions.checkElementIndex(type, indices.length, "File type out of bounds.");
@@ -269,7 +312,7 @@ public final class IndexedFileSystem implements Closeable {
* @return The {@link Index}.
* @throws IOException If there is an error reading from the index file.
* @throws IndexOutOfBoundsException If the descriptor type is less than 0, or greater than or equal to the amount
* of indices.
* of indices.
*/
private Index getIndex(FileDescriptor descriptor) throws IOException {
int index = descriptor.getType();
@@ -290,13 +333,4 @@ public final class IndexedFileSystem implements Closeable {
return Index.decode(buffer);
}
/**
* Checks if this {@link IndexedFileSystem} is read only.
*
* @return {@code true} if so, {@code false} if not.
*/
public boolean isReadOnly() {
return readOnly;
}
}
@@ -36,7 +36,7 @@ public final class ItemDefinitionDecoder {
* @throws IOException If an I/O error occurs.
*/
public ItemDefinition[] decode() throws IOException {
Archive config = Archive.decode(fs.getFile(0, 2));
Archive config = fs.getArchive(0, 2);
ByteBuffer data = config.getEntry("obj.dat").getBuffer();
ByteBuffer idx = config.getEntry("obj.idx").getBuffer();
@@ -63,7 +63,7 @@ public final class ItemDefinitionDecoder {
* @param buffer The buffer.
* @return The {@link ItemDefinition}.
*/
private static ItemDefinition decode(int id, ByteBuffer buffer) {
private ItemDefinition decode(int id, ByteBuffer buffer) {
ItemDefinition definition = new ItemDefinition(id);
while (true) {
int opcode = buffer.get() & 0xFF;
+56 -56
View File
@@ -17,48 +17,21 @@ import org.apollo.cache.archive.ArchiveEntry;
*/
public final class MapFileDecoder {
/**
* The width (and length) of a map file, in tiles.
*/
public static final int MAP_FILE_WIDTH = 64;
/**
* The file id of the versions archive.
*/
private static final int VERSIONS_ARCHIVE_FILE_ID = 5;
/**
* Decodes {@link MapDefinition}s from the specified {@link IndexedFileSystem}.
*
* @param fs The IndexedFileSystem.
* @return A {@link Map} of packed coordinates to their MapDefinitions.
* @throws IOException If there is an error reading or decoding the Archive.
*/
public static Map<Integer, MapDefinition> decode(IndexedFileSystem fs) throws IOException {
Archive archive = Archive.decode(fs.getFile(0, VERSIONS_ARCHIVE_FILE_ID));
ArchiveEntry entry = archive.getEntry("map_index");
Map<Integer, MapDefinition> definitions = new HashMap<>();
ByteBuffer buffer = entry.getBuffer();
int count = buffer.capacity() / (3 * Short.BYTES + Byte.BYTES);
for (int times = 0; times < count; times++) {
int packed = buffer.getShort() & 0xFFFF;
int terrain = buffer.getShort() & 0xFFFF;
int objects = buffer.getShort() & 0xFFFF;
boolean members = buffer.get() == 1;
definitions.put(packed, new MapDefinition(packed, terrain, objects, members));
}
return definitions;
}
/**
* A definition for a region.
*/
public static final class MapDefinition {
/**
* Indicates whether or not this map is members-only.
*/
private final boolean members;
/**
* The object file id.
*/
private final int objects;
/**
* The packed coordinates.
*/
@@ -69,16 +42,6 @@ public final class MapFileDecoder {
*/
private final int terrain;
/**
* The object file id.
*/
private final int objects;
/**
* Indicates whether or not this map is members-only.
*/
private final boolean members;
/**
* Creates the {@link MapDefinition}.
*
@@ -94,6 +57,15 @@ public final class MapFileDecoder {
this.members = members;
}
/**
* Gets the id of the file containing the object data.
*
* @return The file id.
*/
public int getObjectFile() {
return objects;
}
/**
* Gets the packed coordinates.
*
@@ -112,15 +84,6 @@ public final class MapFileDecoder {
return terrain;
}
/**
* Gets the id of the file containing the object data.
*
* @return The file id.
*/
public int getObjectFile() {
return objects;
}
/**
* Returns whether or not this MapDefinition is for a members-only area of the world.
*
@@ -132,4 +95,41 @@ public final class MapFileDecoder {
}
/**
* The width (and length) of a map file, in tiles.
*/
public static final int MAP_FILE_WIDTH = 64;
/**
* The file id of the versions archive.
*/
private static final int VERSIONS_ARCHIVE_FILE_ID = 5;
/**
* Decodes {@link MapDefinition}s from the specified {@link IndexedFileSystem}.
*
* @param fs The IndexedFileSystem.
* @return A {@link Map} of packed coordinates to their MapDefinitions.
* @throws IOException If there is an error reading or decoding the Archive.
*/
public static Map<Integer, MapDefinition> decode(IndexedFileSystem fs) throws IOException {
Archive archive = fs.getArchive(0, VERSIONS_ARCHIVE_FILE_ID);
ArchiveEntry entry = archive.getEntry("map_index");
Map<Integer, MapDefinition> definitions = new HashMap<>();
ByteBuffer buffer = entry.getBuffer();
int count = buffer.capacity() / (3 * Short.BYTES + Byte.BYTES);
for (int times = 0; times < count; times++) {
int packed = buffer.getShort() & 0xFFFF;
int terrain = buffer.getShort() & 0xFFFF;
int objects = buffer.getShort() & 0xFFFF;
boolean members = buffer.get() == 1;
definitions.put(packed, new MapDefinition(packed, terrain, objects, members));
}
return definitions;
}
}
@@ -37,7 +37,7 @@ public final class NpcDefinitionDecoder {
* @throws IOException If an I/O error occurs.
*/
public NpcDefinition[] decode() throws IOException {
Archive config = Archive.decode(fs.getFile(0, 2));
Archive config = fs.getArchive(0, 2);
ByteBuffer data = config.getEntry("npc.dat").getBuffer();
ByteBuffer idx = config.getEntry("npc.idx").getBuffer();
@@ -64,7 +64,7 @@ public final class NpcDefinitionDecoder {
* @param buffer The buffer.
* @return The {@link NpcDefinition}.
*/
private static NpcDefinition decode(int id, ByteBuffer buffer) {
private NpcDefinition decode(int id, ByteBuffer buffer) {
NpcDefinition definition = new NpcDefinition(id);
while (true) {
@@ -139,7 +139,7 @@ public final class NpcDefinitionDecoder {
* @param value The value.
* @return -1 if {@code value} is 65,535, otherwise {@code value}.
*/
private static int wrap(int value) {
private int wrap(int value) {
return value == 65_535 ? -1 : value;
}
@@ -29,32 +29,6 @@ public final class ObjectDefinitionDecoder {
this.fs = fs;
}
/**
* Decodes all of the data into {@link ObjectDefinition}s.
*
* @return The definitions.
* @throws IOException If an error occurs when decoding the archive or finding an entry.
*/
public ObjectDefinition[] decode() throws IOException {
Archive config = Archive.decode(fs.getFile(0, 2));
ByteBuffer data = config.getEntry("loc.dat").getBuffer();
ByteBuffer idx = config.getEntry("loc.idx").getBuffer();
int count = idx.getShort(), index = 2;
int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = index;
index += idx.getShort();
}
ObjectDefinition[] defs = new ObjectDefinition[count];
for (int i = 0; i < count; i++) {
data.position(indices[i]);
defs[i] = decode(i, data);
}
return defs;
}
/**
* Decodes data from the cache into an {@link ObjectDefinition}.
*
@@ -62,7 +36,7 @@ public final class ObjectDefinitionDecoder {
* @param data The {@link ByteBuffer} containing the data.
* @return The object definition.
*/
public static ObjectDefinition decode(int id, ByteBuffer data) {
public ObjectDefinition decode(int id, ByteBuffer data) {
ObjectDefinition definition = new ObjectDefinition(id);
while (true) {
int opcode = data.get() & 0xFF;
@@ -137,4 +111,30 @@ public final class ObjectDefinitionDecoder {
}
}
/**
* Decodes all of the data into {@link ObjectDefinition}s.
*
* @return The definitions.
* @throws IOException If an error occurs when decoding the archive or finding an entry.
*/
public ObjectDefinition[] decode() throws IOException {
Archive config = fs.getArchive(0, 2);
ByteBuffer data = config.getEntry("loc.dat").getBuffer();
ByteBuffer idx = config.getEntry("loc.idx").getBuffer();
int count = idx.getShort(), index = 2;
int[] indices = new int[count];
for (int i = 0; i < count; i++) {
indices[i] = index;
index += idx.getShort();
}
ObjectDefinition[] defs = new ObjectDefinition[count];
for (int i = 0; i < count; i++) {
data.position(indices[i]);
defs[i] = decode(i, data);
}
return defs;
}
}