File and compression routines



The following routines implement a fast buffered file I/O system, which supports the reading and writing of compressed files using a ring buffer algorithm based on the LZSS compressor by Haruhiko Okumura. This does not achieve quite such good compression as programs like zip and lha, but unpacking is very fast and it does not require much memory. Packed files always begin with the 32 bit value F_PACK_MAGIC, and autodetect files with the value F_NOPACK_MAGIC.

char *fix_filename_case(char *path);
Converts a filename to a standardised case. On DOS platforms, they will be entirely uppercase. Returns a copy of the path parameter.

char *fix_filename_slashes(char *path);
Converts all the directory separators in a filename to a standard character. On DOS platforms, this is a backslash. Returns a copy of the path parameter.

char *fix_filename_path(char *dest, char *path, int size);
Converts a partial filename into a full path, generating up to the specified maximum number of characters. Returns a copy of the dest parameter.

char *replace_filename(char *dest, char *path, char *filename, int size);
Replaces the specified path+filename with a new filename tail, generating up to the specified maximum number of characters. Returns a copy of the dest parameter.

char *replace_extension(char *dest, char *filename, char *ext, int size);
Replaces the specified filename+extension with a new extension tail, generating up to the specified maximum number of characters. Returns a copy of the dest parameter.

char *append_filename(char *dest, char *path, char *filename, int size);
Concatenates the specified filename onto the end of the specified path, generating up to the specified maximum number of characters. Returns a copy of the dest parameter.

char *get_filename(char *path);
When passed a completely specified file path, this returns a pointer to the filename portion. Both '\' and '/' are recognized as directory separators.

char *get_extension(char *filename);
When passed a complete filename (with or without path information) this returns a pointer to the file extension.

void put_backslash(char *filename);
If the last character of the filename is not a '\', '/', or '#', this routine will concatenate a '\' on to it.

int file_exists(char *filename, int attrib, int *aret);
Checks whether a file matching the given name and attributes exists, returning non-zero if it does. The file attribute may contain any of the FA_* constants from dir.h. If aret is not NULL, it will be set to the attributes of the matching file. If an error occurs the system error code will be stored in errno.

int exists(char *filename);
Shortcut version of file_exists(), which checks for normal files, which may have the archive or read-only bits set, but are not hidden, directories, system files, etc.

long file_size(char *filename);
Returns the size of a file, in bytes. If the file does not exist or an error occurs, it will return zero and store the system error code in errno.

long file_time(char *filename);
Returns the modification time of a file.

int delete_file(char *filename);
Removes a file from the disk.

int for_each_file(char *name, int attrib, void (*callback)(), int param);
Finds all the files on the disk which match the given wildcard specification and file attributes, and executes callback() once for each. callback() will be passed three arguments, the first a string which contains the completed filename, the second being the attributes of the file, and the third an int which is simply a copy of param (you can use this for whatever you like). If an error occurs an error code will be stored in errno, and callback() can cause for_each_file() to abort by setting errno itself. Returns the number of successful calls made to callback(). The file attribute may contain any of the FA_* flags from dir.h.

void packfile_password(char *password);
Sets the encryption password to be used for all future read/writes of compressed files. Files written with an encryption password cannot be read unless the same password is selected, so be careful: if you forget the key, I can't make your data come back again! Pass NULL or an empty string to return to the normal, non-encrypted mode. If you are using this function to prevent people getting access to your datafiles, be careful not to store an obvious copy of the password in your executable: if there are any strings like "I'm the password for the datafile", it would be fairly easy to get access to your data :-)

PACKFILE *pack_fopen(char *filename, char *mode);
Opens a file according to mode, which may contain any of the flags:

Instead of these flags, one of the constants F_READ, F_WRITE, F_READ_PACKED, F_WRITE_PACKED or F_WRITE_NOPACK may be used as the mode parameter. On success, pack_fopen() returns a pointer to a file structure, and on error it returns NULL and stores an error code in errno. An attempt to read a normal file in packed mode will cause errno to be set to EDOM.

The packfile functions also understand several "magic" filenames that are used for special purposes. These are:

With these special filenames, the contents of a datafile object or appended file can be read in an identical way to a normal disk file, so any of the file access functions in Allegro (eg. load_pcx() and set_config_file()) can be used to read from them. Note that you can't write to these special files, though: the fake file is read only. Also, you must save your datafile uncompressed or with per-object compression if you are planning on loading individual objects from it (otherwise there will be an excessive amount of seeking when it is read). Finally, be aware that the special Allegro object types aren't the same format as the files you import the data from. When you import data like bitmaps or samples into the grabber, they are converted into a special Allegro-specific format, but the '#' marker file syntax reads the objects as raw binary chunks. This means that if, for example, you want to use load_pcx to read an image from a datafile, you should import it as a binary block rather than as a BITMAP object.

int pack_fclose(PACKFILE *f);
int pack_fseek(PACKFILE *f, int offset);
int pack_feof(PACKFILE *f);
int pack_ferror(PACKFILE *f);
int pack_getc(PACKFILE *f);
int pack_putc(int c, PACKFILE *f);
int pack_igetw(PACKFILE *f);
long pack_igetl(PACKFILE *f);
int pack_iputw(int w, PACKFILE *f);
long pack_iputl(long l, PACKFILE *f);
int pack_mgetw(PACKFILE *f);
long pack_mgetl(PACKFILE *f);
int pack_mputw(int w, PACKFILE *f);
long pack_mputl(long l, PACKFILE *f);
long pack_fread(void *p, long n, PACKFILE *f);
long pack_fwrite(void *p, long n, PACKFILE *f);
char *pack_fgets(char *p, int max, PACKFILE *f);
int pack_fputs(char *p, PACKFILE *f);

These work like the equivalent stdio functions, except that pack_fread() and pack_fwrite() take a single size parameter instead of that silly size and num_elements system, seeking only supports forward movement relative to the current position, and the pack_fgets() function does not include a trailing carriage return in the returned string. The pack_i* and pack_m* routines read and write 16 and 32 bit values using the Intel and Motorola byte ordering systems (endianness) respectively. Note that seeking is very slow when reading compressed files, and so should be avoided unless you are sure that the file is not compressed.

PACKFILE *pack_fopen_chunk(PACKFILE *f, int pack);
Opens a sub-chunk of a file. Chunks are primarily intended for use by the datafile code, but they may also be useful for your own file routines. A chunk provides a logical view of part of a file, which can be compressed as an individual entity and will automatically insert and check length counts to prevent reading past the end of the chunk. To write a chunk to the file f, use the code:

      /* assumes f is a PACKFILE * which has been opened in write mode */
      f = pack_fopen_chunk(f, pack);
      write some data to f
      f = pack_fclose_chunk(f);

The data written to the chunk will be prefixed with two length counts (32 bit, big-endian). For uncompressed chunks these will both be set to the size of the data in the chunk. For compressed chunks (created by setting the pack flag), the first length will be the raw size of the chunk, and the second will be the negative size of the uncompressed data.

To read the chunk, use the code:

      /* assumes f is a PACKFILE * which has been opened in read mode */
      f = pack_fopen_chunk(f, FALSE);
      read data from f
      f = pack_fclose_chunk(f);

This sequence will read the length counts created when the chunk was written, and automatically decompress the contents of the chunk if it was compressed. The length will also be used to prevent reading past the end of the chunk (Allegro will return EOF if you attempt this), and to automatically skip past any unread chunk data when you call pack_fclose_chunk().

Chunks can be nested inside each other by making repeated calls to pack_fopen_chunk(). When writing a file, the compression status is inherited from the parent file, so you only need to set the pack flag if the parent is not compressed but you want to pack the chunk data. If the parent file is already open in packed mode, setting the pack flag will result in data being compressed twice: once as it is written to the chunk, and again as the chunk passes it on to the parent file.

PACKFILE *pack_fclose_chunk(PACKFILE *f);
Closes a sub-chunk of a file, previously obtained by calling pack_fopen_chunk().




Back to Contents