RuneHive-Game
Loading...
Searching...
No Matches
IndexedFileSystem.java
Go to the documentation of this file.
1package dev.advo.fs.fs;
2
3import java.io.Closeable;
4import java.io.File;
5import java.io.FileNotFoundException;
6import java.io.IOException;
7import java.io.RandomAccessFile;
8import java.nio.ByteBuffer;
9import java.util.zip.CRC32;
10import java.util.zip.GZIPOutputStream;
11
12import java.io.*;
13
14/**
15 * A file system based on top of the operating system's file system. It
16 * consists of a data file and index files. Index files point to blocks in the
17 * data file, which contains the actual data.
18 * @author Graham
19 */
20public final class IndexedFileSystem implements Closeable {
21
22 /**
23 * Read only flag.
24 */
25 private final boolean readOnly;
26
27 /**
28 * The index files.
29 */
30 private RandomAccessFile[] indices = new RandomAccessFile[256];
31
32 /**
33 * The data file.
34 */
35 private RandomAccessFile data;
36
37 /**
38 * The cached CRC table.
39 */
40 private ByteBuffer crcTable;
41
42 /**
43 * Creates the file system with the specified base directory.
44 * @param base The base directory.
45 * @param readOnly A flag indicating if the file system will be read only.
46 * @throws Exception if the file system is invalid.
47 */
48 public IndexedFileSystem(File base, boolean readOnly) throws Exception {
49 this.readOnly = readOnly;
50 detectLayout(base);
51 }
52
53 /**
54 * Checks if this {@link IndexedFileSystem} is read only.
55 * @return {@code true} if so, {@code false} if not.
56 */
57 public boolean isReadOnly() {
58 return readOnly;
59 }
60
61 /**
62 * Automatically detect the layout of the specified directory.
63 * @param base The base directory.
64 * @throws Exception if the file system is invalid.
65 */
66 private void detectLayout(File base) throws Exception {
67 int indexCount = 0;
68 for (int index = 0; index < indices.length; index++) {
69 File f = new File(base.getAbsolutePath() + "/main_file_cache.idx" + index);
70 if (f.exists() && !f.isDirectory()) {
71 indexCount++;
72 indices[index] = new RandomAccessFile(f, readOnly ? "r" : "rw");
73 }
74 }
75 if (indexCount <= 0) {
76 throw new Exception("No index file(s) present");
77 }
78
79 File oldEngineData = new File(base.getAbsolutePath() + "/main_file_cache.dat");
80 File newEngineData = new File(base.getAbsolutePath() + "/main_file_cache.dat2");
81 if (oldEngineData.exists() && !oldEngineData.isDirectory()) {
82 data = new RandomAccessFile(oldEngineData, readOnly ? "r" : "rw");
83 } else if (newEngineData.exists() && !oldEngineData.isDirectory()) {
84 data = new RandomAccessFile(newEngineData, readOnly ? "r" : "rw");
85 } else {
86 throw new Exception("No data file present");
87 }
88 if(bytes == null) {
90 }
91 }
92
93 public static byte[] bytes;
94
95 /**
96 * Gets the index of a file.
97 * @param fd The {@link FileDescriptor} which points to the file.
98 * @return The {@link Index}.
99 * @throws IOException if an I/O error occurs.
100 */
101 private Index getIndex(FileDescriptor fd) throws IOException {
102 int index = fd.getType();
103 if (index < 0 || index >= indices.length) {
104 throw new IndexOutOfBoundsException();
105 }
106
107 byte[] buffer = new byte[FileSystemConstants.INDEX_SIZE];
108 RandomAccessFile indexFile = indices[index];
109 synchronized (indexFile) {
110 long ptr = (long) fd.getFile() * (long) FileSystemConstants.INDEX_SIZE;
111 if (ptr >= 0 && indexFile.length() >= (ptr + FileSystemConstants.INDEX_SIZE)) {
112 indexFile.seek(ptr);
113 indexFile.readFully(buffer);
114 } else {
115 throw new FileNotFoundException();
116 }
117 }
118
119 return Index.decode(buffer);
120 }
121
122 /**
123 * Gets the number of files with the specified type.
124 * @param type The type.
125 * @return The number of files.
126 * @throws IOException if an I/O error occurs.
127 */
128 private int getFileCount(int type) throws IOException {
129 if (type < 0 || type >= indices.length) {
130 throw new IndexOutOfBoundsException();
131 }
132
133 RandomAccessFile indexFile = indices[type];
134 synchronized (indexFile) {
135 return (int) (indexFile.length() / FileSystemConstants.INDEX_SIZE);
136 }
137 }
138
139 /**
140 * Gets the CRC table.
141 * @return The CRC table.
142 * @throws IOException if an I/O erorr occurs.
143 */
144 public ByteBuffer getCrcTable() throws IOException {
145 if (readOnly) {
146 synchronized (this) {
147 if (crcTable != null) {
148 return crcTable.slice();
149 }
150 }
151
152 // the number of archives
153 int archives = getFileCount(0);
154
155 // the hash
156 int hash = 1234;
157
158 // the CRCs
159 int[] crcs = new int[archives];
160
161 // calculate the CRCs
162 CRC32 crc32 = new CRC32();
163 for (int i = 1; i < crcs.length; i++) {
164 crc32.reset();
165
166 ByteBuffer bb = getFile(0, i);
167 byte[] bytes = new byte[bb.remaining()];
168 bb.get(bytes, 0, bytes.length);
169 crc32.update(bytes, 0, bytes.length);
170
171 crcs[i] = (int) crc32.getValue();
172 }
173
174 // hash the CRCs and place them in the buffer
175 ByteBuffer buf = ByteBuffer.allocate(crcs.length * 4 + 4);
176 for (int i = 0; i < crcs.length; i++) {
177 hash = (hash << 1) + crcs[i];
178 buf.putInt(crcs[i]);
179 }
180
181 // place the hash into the buffer
182 buf.putInt(hash);
183 buf.flip();
184
185 synchronized (this) {
186 crcTable = buf;
187 return crcTable.slice();
188 }
189 } else {
190 throw new IOException("cannot get CRC table from a writable file system");
191 }
192 }
193
194 /**
195 * Compresses a GZIP file.
196 *
197 * @param bytes
198 * The uncompressed bytes.
199 * @return The compressed bytes.
200 * @throws IOException
201 * if an I/O error occurs.
202 */
203 public static byte[] gzip(byte[] bytes) throws IOException {
204 /* create the streams */
205 InputStream is = new ByteArrayInputStream(bytes);
206 try {
207 ByteArrayOutputStream bout = new ByteArrayOutputStream();
208 OutputStream os = new GZIPOutputStream(bout);
209 try {
210 /* copy data between the streams */
211 byte[] buf = new byte[4096];
212 int len = 0;
213 while ((len = is.read(buf, 0, buf.length)) != -1) {
214 os.write(buf, 0, len);
215 }
216 } finally {
217 os.close();
218 }
219
220 /* return the compressed bytes */
221 return bout.toByteArray();
222 } finally {
223 is.close();
224 }
225 }
226
227
228 /*
229 *
230Client writes a single byte (0) to the server.
231Server responds with checksum/crc information.
232This information consists of a big endian 32 bit int followed by some data that is gzipped.
233
234The gzipped portion consists of
235a ubyte for the number of indexes: in the case of osroyale, 6.
236and then it just reads the # of entries in each index as a big endian medium, and then a big endian int for the crc value of each entry in the index.
237
238 */
239 private byte[] initialResponse() throws IOException {
240 // calculate the CRCs
241 CRC32 crc32 = new CRC32();
242// {
243// ChannelBuffer channelBuffer = ChannelBuffers.dynamicBuffer();
244// channelBuffer.writeByte(6);
245// for(int type = 0; type < 6; type++) {
246// int files = getFileCount(type);
247// channelBuffer.writeMedium(files);
248//
249// for(int file = 0; file < files; file++) {
250// crc32.reset();
251// ByteBuffer bb = getFile(type, file);
252// byte[] bytes = new byte[bb.remaining()];
253// bb.get(bytes, 0, bytes.length);
254// crc32.update(bytes, 0, bytes.length);
255// }
256//
257// //write int for the crc value.
258// channelBuffer.writeInt((int) crc32.getValue());
259// }
260// }
261// {
262 int bufLen = 0;
263 for(int i = 0; i < 6; i++) {
264 bufLen += getFileCount(i) * 4;
265 }
266 bufLen += (6*3) + 1;
267
268 ByteBuffer buf = ByteBuffer.allocate(bufLen);
269 buf.put((byte) 6);
270
271 for(int i = 0; i < 6; i++) {
272 //write the # of entries as a medium.
273 int value = getFileCount(i);
274 buf.put((byte) (value >> 16));
275 buf.put((byte) (value >> 8));
276 buf.put((byte) value);
277 for(int f = 0; f < value; f++) {
278 crc32.reset();
279 ByteBuffer bb = getFile(i, f);
280 byte[] bytes = new byte[bb.remaining()];
281 bb.get(bytes, 0, bytes.length);
282 crc32.update(bytes, 0, bytes.length);
283
284 //write int for the crc value.
285 buf.putInt((int) crc32.getValue());
286 }
287 }
288
289 buf.rewind();
290 byte[] uncompressed = new byte[buf.remaining()];
291 //System.out.println("unc" + uncompressed.length);
292 buf.get(uncompressed);
293// System.out.println(Arrays.toString(uncompressed));
294// System.exit(0);
295 byte[] compressed = gzip(uncompressed);
296 System.out.println("comp" + compressed.length);
297// }
298
299 return compressed;
300 }
301
302 public ByteBuffer getJireCRCTable() throws IOException {
303 if (readOnly) {
304 synchronized (this) {
305 if (crcTable != null) {
306 return crcTable.slice();
307 }
308 }
309
310 int size = 6;
311
312 // the number of archives
313 int archives = getFileCount(0);
314
315 // the hash
316 int hash = 1234;
317
318 // the CRCs
319 int[] crcs = new int[archives];
320
321 // calculate the CRCs
322 CRC32 crc32 = new CRC32();
323 for (int i = 1; i < crcs.length; i++) {
324 crc32.reset();
325
326 ByteBuffer bb = getFile(0, i);
327 byte[] bytes = new byte[bb.remaining()];
328 bb.get(bytes, 0, bytes.length);
329 crc32.update(bytes, 0, bytes.length);
330
331 crcs[i] = (int) crc32.getValue();
332 }
333
334 // hash the CRCs and place them in the buffer
335 ByteBuffer buf = ByteBuffer.allocate(crcs.length * 4 + 4);
336 for (int i = 0; i < crcs.length; i++) {
337 hash = (hash << 1) + crcs[i];
338 buf.putInt(crcs[i]);
339 }
340
341 // place the hash into the buffer
342 buf.putInt(hash);
343 buf.flip();
344
345 synchronized (this) {
346 crcTable = buf;
347 return crcTable.slice();
348 }
349 } else {
350 throw new IOException("cannot get CRC table from a writable file system");
351 }
352 }
353
354 /**
355 * Gets a file.
356 * @param type The file type.
357 * @param file The file id.
358 * @return A {@link ByteBuffer} which contains the contents of the file.
359 * @throws IOException if an I/O error occurs.
360 */
361 public ByteBuffer getFile(int type, int file) throws IOException {
362 return getFile(new FileDescriptor(type, file));
363 }
364
365 /**
366 * Gets a file.
367 * @param fd The {@link FileDescriptor} which points to the file.
368 * @return A {@link ByteBuffer} which contains the contents of the file.
369 * @throws IOException if an I/O error occurs.
370 */
371 public ByteBuffer getFile(FileDescriptor fd) throws IOException {
372 Index index = getIndex(fd);
373 ByteBuffer buffer = ByteBuffer.allocate(index.getSize());
374
375 // calculate some initial values
376 long ptr = (long) index.getBlock() * (long) FileSystemConstants.BLOCK_SIZE;
377 int read = 0;
378 int size = index.getSize();
379 int blocks = size / FileSystemConstants.CHUNK_SIZE;
380 if (size % FileSystemConstants.CHUNK_SIZE != 0) {
381 blocks++;
382 }
383
384 for (int i = 0; i < blocks; i++) {
385
386 // read header
387 byte[] header = new byte[FileSystemConstants.HEADER_SIZE];
388 synchronized (data) {
389 data.seek(ptr);
390 data.readFully(header);
391 }
392
393 // increment pointers
395
396 // parse header
397 int nextFile = ((header[0] & 0xFF) << 8) | (header[1] & 0xFF);
398 int curChunk = ((header[2] & 0xFF) << 8) | (header[3] & 0xFF);
399 int nextBlock = ((header[4] & 0xFF) << 16) | ((header[5] & 0xFF) << 8) | (header[6] & 0xFF);
400 int nextType = header[7] & 0xFF;
401
402 // check expected chunk id is correct
403 if (i != curChunk) {
404 throw new IOException("Chunk id mismatch.");
405 }
406
407 // calculate how much we can read
408 int chunkSize = size - read;
409 if (chunkSize > FileSystemConstants.CHUNK_SIZE) {
411 }
412
413 // read the next chunk and put it in the buffer
414 byte[] chunk = new byte[chunkSize];
415 synchronized (data) {
416 data.seek(ptr);
417 data.readFully(chunk);
418 }
419 buffer.put(chunk);
420
421 // increment pointers
422 read += chunkSize;
423 ptr = (long) nextBlock * (long) FileSystemConstants.BLOCK_SIZE;
424
425 // if we still have more data to read, check the validity of the
426 // header
427 if (size > read) {
428 if (nextType != (fd.getType() + 1)) {
429 throw new IOException("File type mismatch.");
430 }
431
432 if (nextFile != fd.getFile()) {
433 throw new IOException("File id mismatch.");
434 }
435 }
436 }
437
438 buffer.flip();
439 return buffer;
440 }
441
442 @Override
443 public void close() throws IOException {
444 if (data != null) {
445 synchronized (data) {
446 data.close();
447 }
448 }
449
450 for (RandomAccessFile index : indices) {
451 if (index != null) {
452 synchronized (index) {
453 index.close();
454 }
455 }
456 }
457 }
458
459}
A class which points to a file in the cache.
Holds file system related constants.
static final int CHUNK_SIZE
The size of a chunk.
static final int HEADER_SIZE
The size of a header.
static final int BLOCK_SIZE
The size of a block.
static final int INDEX_SIZE
The size of an index.
An Index points to a file in the main_file_cache.dat file.
Definition Index.java:7
static Index decode(byte[] buffer)
Decodes a buffer into an index.
Definition Index.java:15
static byte[] gzip(byte[] bytes)
Compresses a GZIP file.
ByteBuffer getFile(int type, int file)
Gets a file.
Index getIndex(FileDescriptor fd)
Gets the index of a file.
IndexedFileSystem(File base, boolean readOnly)
Creates the file system with the specified base directory.
void detectLayout(File base)
Automatically detect the layout of the specified directory.
ByteBuffer getFile(FileDescriptor fd)
Gets a file.
final boolean readOnly
Read only flag.
ByteBuffer crcTable
The cached CRC table.
int getFileCount(int type)
Gets the number of files with the specified type.
boolean isReadOnly()
Checks if this IndexedFileSystem is read only.
ByteBuffer getCrcTable()
Gets the CRC table.
RandomAccessFile[] indices
The index files.
RandomAccessFile data
The data file.