Actions

Jagex Store: Difference between revisions

From RuneWiki

No edit summary
No edit summary
Line 7: Line 7:
const BZIP2_HEADER = new Uint8Array([0x42, 0x5A, 0x68, 0x31]);
const BZIP2_HEADER = new Uint8Array([0x42, 0x5A, 0x68, 0x31]);


export class FileStore {
// Backwards compatible with 234-402 caches (legacy)
export class JagexStore {
     constructor(path) {
     constructor(path) {
         this.dat2 = new RandomAccessFile(`${path}/main_file_cache.dat2`, false);
         this.legacy = false;
        this.idx255 = new RandomAccessFile(`${path}/main_file_cache.idx255`, false);


         let idxCount = this.idx255.length / 6;
         if (fs.existsSync(`${path}/main_file_cache.dat`)) {
        for (let i = 0; i < idxCount; ++i) {
            this.legacy = true;
             this[`idx${i}`] = new RandomAccessFile(`${path}/main_file_cache.idx${i}`, false);
        }
 
        if (!this.legacy) {
            this.dat2 = new RandomAccessFile(`${path}/main_file_cache.dat2`, false);
            this.idx255 = new RandomAccessFile(`${path}/main_file_cache.idx255`, false);
 
            let idxCount = this.idx255.length / 6;
            for (let i = 0; i < idxCount; ++i) {
                this[`idx${i}`] = new RandomAccessFile(`${path}/main_file_cache.idx${i}`, false);
            }
 
            this.index_offset = 0;
        } else {
            this.dat2 = new RandomAccessFile(`${path}/main_file_cache.dat`, false);
 
             for (let i = 0; i < 5; ++i) {
                this[`idx${i}`] = new RandomAccessFile(`${path}/main_file_cache.idx${i}`, false);
            }
 
            this.index_offset = 1;
         }
         }
     }
     }


     read(index, entry, extract = false) {
     read(index, entry, extract = false) {
        if (!this[`idx${index}`]) {
            return null;
        }
         let buffer = this[`idx${index}`].front().seek(entry * 6).read(6);
         let buffer = this[`idx${index}`].front().seek(entry * 6).read(6);
         if (!buffer.available) {
         if (!buffer.available) {
Line 25: Line 48:


         let length = buffer.readSWord();
         let length = buffer.readSWord();
         if (length > this.dat2.length) {
         if (length === 0 || length > this.dat2.length) {
             return null;
             return null;
         }
         }
Line 47: Line 70:
             let currentFile = sector.readWord();
             let currentFile = sector.readWord();
             if (currentFile !== entry) {
             if (currentFile !== entry) {
                console.error('currentFile !== entry');
                 return null;
                 return null;
             }
             }
Line 53: Line 75:
             let currentPart = sector.readWord();
             let currentPart = sector.readWord();
             if (currentPart !== part) {
             if (currentPart !== part) {
                console.error('currentPart !== part');
                 return null;
                 return null;
             }
             }
Line 59: Line 80:
             nextSector = sector.readSWord();
             nextSector = sector.readSWord();
             if (nextSector * 520 > this.dat2.length) {
             if (nextSector * 520 > this.dat2.length) {
                console.error('nextSector * 520 > this.dat2.length');
                 return null;
                 return null;
             }
             }


             let currentIndex = sector.readByte();
             let currentIndex = sector.readByte();
             if (currentIndex !== index) {
             if (currentIndex - this.index_offset !== index) {
                console.error('currentIndex !== index');
                 return null;
                 return null;
             }
             }
Line 74: Line 93:
         data.front();
         data.front();


         let file = {
         let file = { compression: 0, size: 0, decompressedSize: 0, data };
             compression: data.readByte(),
 
             size: data.readDWord(),
        if (!this.legacy) {
             decompressedSize: data.readDWord(),
             file.compression = data.readByte(),
             data: data.read()
             file.size = data.readDWord(),
         };
             file.decompressedSize = data.readDWord(),
             file.data = data.read()
         } else {
            file.compression = index === 0 ? 1 : 2;
            file.size = file.data.length;
        }


         if (extract) {
         if (extract) {
             if (file.compression === 1) {
             if (file.compression === 1 && this.legacy) {
                 file.data = file.data.prepend(BZIP2_HEADER);
                 file = new FileArchive(file.data);
                 file.data = bz2.decompress(file.data.raw);
            } else if (file.compression === 1) {
                 file.data = bz2.decompress(file.data.prepend(BZIP2_HEADER).raw);
             } else if (file.compression === 2) {
             } else if (file.compression === 2) {
                 file.data = new Uint8Array(zlib.gunzipSync(file.data.raw));
                 file.data = new Uint8Array(zlib.gunzipSync(file.data.raw));
                if (this.legacy) {
                    file.decompressedSize = file.data.length;
                }
             }
             }
         }
         }

Revision as of 21:59, 23 January 2022

This format is functionally the same as File Store, but uses more indices to separate file types. You can get the number of indices to read from by taking the length of idx255 and dividing it by 6 (each idx255 entry is its own file). Reading works the same way (520 byte sectors) except there is now a 9-byte header before each entry.

The 9-byte header has the compression type (1 byte), file size (4 bytes), and uncompressed size (4 bytes). A compression type of 1 uses BZip2 and you have to prepend the magic number. A compression type of 2 uses gzip.

Reading

const BZIP2_HEADER = new Uint8Array([0x42, 0x5A, 0x68, 0x31]);

// Backwards compatible with 234-402 caches (legacy)
export class JagexStore {
    constructor(path) {
        this.legacy = false;

        if (fs.existsSync(`${path}/main_file_cache.dat`)) {
            this.legacy = true;
        }

        if (!this.legacy) {
            this.dat2 = new RandomAccessFile(`${path}/main_file_cache.dat2`, false);
            this.idx255 = new RandomAccessFile(`${path}/main_file_cache.idx255`, false);

            let idxCount = this.idx255.length / 6;
            for (let i = 0; i < idxCount; ++i) {
                this[`idx${i}`] = new RandomAccessFile(`${path}/main_file_cache.idx${i}`, false);
            }

            this.index_offset = 0;
        } else {
            this.dat2 = new RandomAccessFile(`${path}/main_file_cache.dat`, false);

            for (let i = 0; i < 5; ++i) {
                this[`idx${i}`] = new RandomAccessFile(`${path}/main_file_cache.idx${i}`, false);
            }

            this.index_offset = 1;
        }
    }

    read(index, entry, extract = false) {
        if (!this[`idx${index}`]) {
            return null;
        }

        let buffer = this[`idx${index}`].front().seek(entry * 6).read(6);
        if (!buffer.available) {
            return null;
        }

        let length = buffer.readSWord();
        if (length === 0 || length > this.dat2.length) {
            return null;
        }

        let data = new ByteBuffer(new Uint8Array(length), false);
        let nextSector = buffer.readSWord();

        if (nextSector * 520 > this.dat2.length) {
            return null;
        }

        let part = 0;
        while (data.available && nextSector !== 0) {
            let sector = this.dat2.front().seek(nextSector * 520).read(520);

            let bytes = data.available;
            if (bytes > 512) {
                bytes = 512;
            }

            let currentFile = sector.readWord();
            if (currentFile !== entry) {
                return null;
            }

            let currentPart = sector.readWord();
            if (currentPart !== part) {
                return null;
            }

            nextSector = sector.readSWord();
            if (nextSector * 520 > this.dat2.length) {
                return null;
            }

            let currentIndex = sector.readByte();
            if (currentIndex - this.index_offset !== index) {
                return null;
            }

            data.write(sector.read(bytes));
            part++;
        }
        data.front();

        let file = { compression: 0, size: 0, decompressedSize: 0, data };

        if (!this.legacy) {
            file.compression = data.readByte(),
            file.size = data.readDWord(),
            file.decompressedSize = data.readDWord(),
            file.data = data.read()
        } else {
            file.compression = index === 0 ? 1 : 2;
            file.size = file.data.length;
        }

        if (extract) {
            if (file.compression === 1 && this.legacy) {
                file = new FileArchive(file.data);
            } else if (file.compression === 1) {
                file.data = bz2.decompress(file.data.prepend(BZIP2_HEADER).raw);
            } else if (file.compression === 2) {
                file.data = new Uint8Array(zlib.gunzipSync(file.data.raw));
                if (this.legacy) {
                    file.decompressedSize = file.data.length;
                }
            }
        }

        return file;
    }
}