157 lines
5.2 KiB
Markdown
157 lines
5.2 KiB
Markdown
|
\[\[Category Cache\]\]
|
|||
|
|
|||
|
== Introduction ==
|
|||
|
|
|||
|
Since 194 all way up until 377, all the files in cache 0 have an
|
|||
|
archive-like format which contains a collection of named files (e.g.
|
|||
|
'''BADENC.TXT''' is a file which contains bad words in the '''wordenc'''
|
|||
|
archive).
|
|||
|
|
|||
|
=== Diagram ===
|
|||
|
|
|||
|
------------------------------------------------------------------------
|
|||
|
|
|||
|
\[http://img263.imageshack.us/img263/9678/68481568.png External Diagram
|
|||
|
Image\]
|
|||
|
|
|||
|
== Usage ==
|
|||
|
|
|||
|
These files are used by the client for a variety of purposes. Some, such
|
|||
|
as the '''DATA''' file contain data themselves (in this case the
|
|||
|
interfaces). Others, such as the '''MAP\_INDEX''' file, contain
|
|||
|
information about where to locate the map and landscape files in the
|
|||
|
cache.
|
|||
|
|
|||
|
== Format ==
|
|||
|
|
|||
|
tribyte uncompressedsize tribyte compressedsize
|
|||
|
|
|||
|
If the uncompressed and compressed sizes are equal, the whole file is
|
|||
|
not compressed but the individual entries are compressed using bzip2. If
|
|||
|
they are not equal, the entire file is compressed using bzip2 but the
|
|||
|
individual entries are not.
|
|||
|
|
|||
|
Also note, the magic id at the start of the bzip2 entries are not
|
|||
|
included in the cache. If you use an existing API to read the files and
|
|||
|
want to add this back, you must append the four characters: BZh1 before
|
|||
|
you decompress.
|
|||
|
|
|||
|
short fileCount
|
|||
|
|
|||
|
Each file entry has the format:
|
|||
|
|
|||
|
int nameHash tribyte uncompressedSize tribyte compressedSize
|
|||
|
|
|||
|
When you are looping through the files, you need to keep track of the
|
|||
|
file offset yourself. This psuedocode demonstrates how:
|
|||
|
|
|||
|
int offset = buffer.getCurrentOffset() + numFiles \* 10; for(int i = 0;
|
|||
|
i \< numFiles; i++) { // read values int thisFileOffset = offset; offset
|
|||
|
+= thisFileCompressedSize; }
|
|||
|
|
|||
|
To get a named file by its name, you should first hash the name using
|
|||
|
this method:
|
|||
|
|
|||
|
public static int hash(String name) { int hash = 0; name =
|
|||
|
name.toUpperCase(); for(int j = 0; j \< name.length(); j++) { hash =
|
|||
|
(hash \* 61 + name.charAt(j)) - 32; } return hash; }
|
|||
|
|
|||
|
Then, loop through the file entries you loaded earlier to find a
|
|||
|
matching hash. Read the compressed file size from the offset. If the
|
|||
|
whole file is not compressed, you should decompress the individual
|
|||
|
entry.
|
|||
|
|
|||
|
== '''\#194 Archive Format''' ==
|
|||
|
|
|||
|
The 194 (RuneScape 2 beta) client worked with a very simple cache
|
|||
|
format. Each file in the cache was a file on the operating system.
|
|||
|
|
|||
|
=== Name hashing ===
|
|||
|
|
|||
|
Every name in the cache was hashed using the following method which is,
|
|||
|
incidentally, similar to the way player names are converted to longs.
|
|||
|
|
|||
|
public static final long gethash(String string) { string =
|
|||
|
string.trim(); long l = 0L; for (int i = 0; i \< string.length() && i \<
|
|||
|
12; i++) { char c = string.charAt(i); l \*= 37L; if (c \>= 'A' && c \<=
|
|||
|
'Z') l += (long) ('\\001' + c - 'A'); else if (c \>= 'a' && c \<= 'z') l
|
|||
|
+= (long) ('\\001' + c - 'a'); else if (c \>= '0' && c \<= '9') l +=
|
|||
|
(long) ('\\033' + c - '0'); } return l; }
|
|||
|
|
|||
|
The resulting long was converted to a string and the file was given that
|
|||
|
name.
|
|||
|
|
|||
|
=== Files ===
|
|||
|
|
|||
|
The files in the cache were the ones used in the \[\[JAGGRAB
|
|||
|
Protocol\|JAGGRAB Protocol\]\] (i.e. files in cache 0 in old engine
|
|||
|
caches) and map (mX\_Y) and landscape (lX\_Y) files. Incidentally, this
|
|||
|
naming is very similar to the names of the map and landscape files in
|
|||
|
new engine caches.
|
|||
|
|
|||
|
== '''\#317 Archive Format''' ==
|
|||
|
|
|||
|
The old engine cache is made up two types of files.
|
|||
|
|
|||
|
=== Data file ===
|
|||
|
|
|||
|
The data file holds all of the files in the cache and is named
|
|||
|
'''main\_file\_cache.dat'''. It is therefore very big, typically \~10-20
|
|||
|
megabytes..
|
|||
|
|
|||
|
=== Index file ===
|
|||
|
|
|||
|
There are several index files, named '''main\_file\_cache.idx''' and
|
|||
|
then postfixed with a number. Each index file holds 'pointers' to where
|
|||
|
a file is located in the main cache. Each index file represents a type
|
|||
|
of file.
|
|||
|
|
|||
|
=== Index file format ===
|
|||
|
|
|||
|
The index file is made up of 6 byte blocks which hold information about
|
|||
|
where a file can be located in the data file. The format of a single
|
|||
|
block is as follows:
|
|||
|
|
|||
|
tribyte fileSize tribyte initialDataBlockId
|
|||
|
|
|||
|
=== Data file format ===
|
|||
|
|
|||
|
The data file is made up of 520 byte blocks. The format of each of these
|
|||
|
blocks is as follows:
|
|||
|
|
|||
|
short nextFileId short currentFilePartId tribyte nextDataBlockId byte
|
|||
|
nextFileTypeId byte\[512\] blockData
|
|||
|
|
|||
|
=== Explanation ===
|
|||
|
|
|||
|
An example will be used here as it is easier to follow.
|
|||
|
|
|||
|
Let us say, the client wishes to fetch file type 2, file id 17.
|
|||
|
|
|||
|
First off, it will open the main\_file\_cache.idx2 file and seek to the
|
|||
|
index 17 \* 6 (102). It will then read two tribytes.
|
|||
|
|
|||
|
fileSize = 1200 intialDataBlockId = 4
|
|||
|
|
|||
|
The client will now open the main\_file\_cache.dat file and seek to the
|
|||
|
index 4 \* 520 (2080). The values it reads will be:
|
|||
|
|
|||
|
nextFileId = 17 currentFilePartId = 0 nextDataBlockId = 5 nextFileTypeId
|
|||
|
= 2 blockData = ...
|
|||
|
|
|||
|
It will read the first 512 bytes of the file and then knows that there
|
|||
|
is 688 bytes left. Therefore, it has to read the next block.
|
|||
|
|
|||
|
nextFileId = 17 currentFilePartId = 1 nextDataBlockId = 6 nextFileTypeId
|
|||
|
= 2 blockData ...
|
|||
|
|
|||
|
It reads these next 512 bytes of the file and now knows that there are
|
|||
|
176 bytes left. So for a final time, it will read the next block.
|
|||
|
|
|||
|
nextFileId = 18 currentFilePartId = 2 nextDataBlockId = 7 nextFileTypeId
|
|||
|
= 2 blockData = ...
|
|||
|
|
|||
|
It can ignore most of these values (the next ones are meaningless at
|
|||
|
this stage) and read the final 176 bytes. The whole 1200 byte file has
|
|||
|
now been read.
|