package net.scapeemulator.cache; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; /** * An {@link Archive} is a file within the cache that can have multiple member * files inside it. * @author Graham * @author `Discardedx2 */ public final class Archive { /** * Decodes the specified {@link ByteBuffer} into an {@link Archive}. * @param buffer The buffer. * @param size The size of the archive. * @return The decoded {@link Archive}. */ public static Archive decode(ByteBuffer buffer, int size) { /* allocate a new archive object */ Archive archive = new Archive(size); /* read the number of chunks at the end of the archive */ buffer.position(buffer.limit() - 1); int chunks = buffer.get() & 0xFF; /* read the sizes of the child entries and individual chunks */ int[][] chunkSizes = new int[chunks][size]; int[] sizes = new int[size]; buffer.position(buffer.limit() - 1 - chunks * size * 4); for (int chunk = 0; chunk < chunks; chunk++) { int chunkSize = 0; for (int id = 0; id < size; id++) { /* read the delta-encoded chunk length */ int delta = buffer.getInt(); chunkSize += delta; chunkSizes[chunk][id] = chunkSize; /* store the size of this chunk */ sizes[id] += chunkSize; /* and add it to the size of the whole file */ } } /* allocate the buffers for the child entries */ for (int id = 0; id < size; id++) { archive.entries[id] = ByteBuffer.allocate(sizes[id]); } /* read the data into the buffers */ buffer.position(0); for (int chunk = 0; chunk < chunks; chunk++) { for (int id = 0; id < size; id++) { /* get the length of this chunk */ int chunkSize = chunkSizes[chunk][id]; /* copy this chunk into a temporary buffer */ byte[] temp = new byte[chunkSize]; buffer.get(temp); /* copy the temporary buffer into the file buffer */ archive.entries[id].put(temp); } } /* flip all of the buffers */ for (int id = 0; id < size; id++) { archive.entries[id].flip(); } /* return the archive */ return archive; } /** * The array of entries in this archive. */ private final ByteBuffer[] entries; /** * Creates a new archive. * @param size The number of entries in the archive. */ public Archive(int size) { this.entries = new ByteBuffer[size]; } /** * Encodes this {@link Archive} into a {@link ByteBuffer}. * <p /> * Please note that this is a fairly simple implementation that does not * attempt to use more than one chunk. * @return An encoded {@link ByteBuffer}. * @throws IOException if an I/O error occurs. */ public ByteBuffer encode() throws IOException { // TODO: an implementation that can use more than one chunk ByteArrayOutputStream bout = new ByteArrayOutputStream(); try (DataOutputStream os = new DataOutputStream(bout)) { /* add the data for each entry */ for (ByteBuffer entry : entries) { /* copy to temp buffer */ byte[] temp = new byte[entry.limit()]; entry.position(0); entry.get(temp); entry.position(0); /* copy to output stream */ os.write(temp); } /* write the chunk lengths */ int prev = 0; for (ByteBuffer entry : entries) { /* * since each file is stored in the only chunk, just write the * delta-encoded file size */ int chunkSize = entry.limit(); os.writeInt(chunkSize - prev); prev = chunkSize; } /* we only used one chunk due to a limitation of the implementation */ bout.write(1); /* wrap the bytes from the stream in a buffer */ byte[] bytes = bout.toByteArray(); return ByteBuffer.wrap(bytes); } } /** * Gets the size of this archive. * @return The size of this archive. */ public int size() { return entries.length; } /** * Gets the entry with the specified id. * @param id The id. * @return The entry. */ public ByteBuffer getEntry(int id) { return entries[id]; } /** * Inserts/replaces the entry with the specified id. * @param id The id. * @param buffer The entry. */ public void putEntry(int id, ByteBuffer buffer) { entries[id] = buffer; } }