|
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.
The following FA_* flags are guaranteed to work: FA_RDONLY, FA_HIDDEN,
FA_SYSTEM, FA_LABEL, FA_DIREC, FA_ARCH. Do not use any other flags from
DOS/Windows or your code will not compile on another platform. Flags
FA_SYSTEM, FA_LABEL and FA_ARCH are valuable only on DOS/Windows (entries
with system flag, volume labels and archive flag). FA_RDONLY is for
directory entries with read-only flag on DOS-like systems or unwritable
by current user on Unix-like systems. FA_HIDDEN is for entries with hidden
flag on DOS-like systems or starting with '.' on Unix (dotted files -
excluding '.' and '..'). FA_DIREC represents directories. Flags can be
combined using '|' (binary OR operator).
When passed to the functions as the 'attrib' parameter, these flags
represent an upper set in which the actual flag set of a matching file must
be included. That is, in order for a file to be matching, its attributes
may contain any of the specified flags but must not contain any of the
unspecified flags. Thus, if you pass 'FA_DIREC | FA_RDONLY', normal files
and directories will be included as well as read-only files and
directories, but not hidden files and directories. Similarly, if you pass
'FA_ARCH' then both archived and non-archived files will be included.
void get_executable_name(char *buf, int size);
Fills buf with the full path to the current executable, writing at most
size bytes. This generally comes from argv[0], but on Unix systems if
argv[0] does not specify the path, we search for our file in $PATH.
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, const char *path, int size);
Converts a partial filename into a full path, storing at most size bytes
into the dest buffer. Returns a copy of the dest parameter.
char *replace_filename(char *dest, const char *path,
const char *filename, int size);
Replaces the specified path+filename with a new filename tail, storing
at most size bytes into the dest buffer. Returns a copy of the dest
parameter.
char *replace_extension(char *dest, const char *filename,
const char *ext, int size);
Replaces the specified filename+extension with a new extension tail,
storing at most size bytes into the dest buffer. Returns a copy of the
dest parameter.
char *append_filename(char *dest, const char *path,
const char *filename, int size);
Concatenates the specified filename onto the end of the specified path,
storing at most size bytes into the dest buffer. Returns a copy of the
dest parameter.
char *get_filename(const 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(const 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 a device
separator (ie. ':' under DOS), this routine will concatenate either a '\'
or '/' on to it (depending on the platform). Note: ignore the function
name, it's out of date.
int file_exists(const char *filename, int attrib, int *aret);
Checks whether a file matching the given name and attributes (see above)
exists, returning non-zero if it does. 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(const 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(const 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.
time_t file_time(const char *filename);
Returns the modification time (number of seconds since 00:00:00 GMT
1/1/1970) of a file.
int delete_file(const char *filename);
Removes a file from the disk.
int for_each_file(const char *name, int attrib,
void (*callback)(const char *filename, int attrib, int param),
int param);
Finds all the files on the disk which match the given wildcard
specification and file attributes (see above), 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().
int al_findfirst(const char *pattern, struct al_ffblk *info, int attrib);
Low-level function for searching files. This function finds the first
file which matches the given wildcard specification and file attributes
(see above). The information about the file (if any) will be put in the
al_ffblk structure which you have to provide. The function returns zero
if a match is found, nonzero if none is found or if an error occured
and, in the latter case, sets errno accordingly. The al_ffblk structure
looks like:
struct al_ffblk
{
int attrib; - actual attributes of the file found
time_t time; - modification time of file
long size; - size of file
char name[512]; - name of file
};
There is some other stuff in the structure as well, but it is there for
internal use only.
int al_findnext(struct al_ffblk *info);
This finds the next file in a search started by al_findfirst(). Returns
zero if a match is found, nonzero if none is found or if an error
occured and, in the latter case, sets errno accordingly.
void al_findclose(struct al_ffblk *info);
This closes a previously opened search with al_findfirst().
int find_allegro_resource(char *dest, const char *resource,
const char *ext, const char *datafile,
const char *objectname, const char *envvar,
const char *subdir, int size);
Searches for a support file, eg. allegro.cfg or language.dat. Passed a
resource string describing what you are looking for, along with extra
optional information such as the default extension, what datafile to look
inside, what the datafile object name is likely to be, any special
environment variable to check, and any subdirectory that you would like
to check as well as the default location, this function looks in a hell
of a lot of different places :-) Returns zero on success, and stores a
full path to the file (at most size bytes) into the dest buffer.
void packfile_password(const char *password);
Sets the encryption password to be used for all read/write operations
on files opened in future using Allegro's packfile functions (whether
they are compressed or not), including all the save, load and config
routines. 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 :-)
Note #1: when writing a packfile, you can change the password to whatever
you want after opening the file, without affecting the write operation.
On the contrary, when writing a sub-chunk of a packfile, you must make
sure that the password that was active at the time the sub-chunk was
opened is still active before closing the sub-chunk. This is guaranteed
to be true if you didn't call the packfile_password() routine in the
meantime. Read operations, either on packfiles or sub-chunks, have no
such restriction.
Note #2: as explained above, the password is used for all read/write
operations on files, including for several functions of the library that
operate on files without explicitly using packfiles, e.g load_bitmap().
The unencrypted mode is mandatory in order for those functions to work.
Therefore remember to call packfile_password(NULL) before using them if
you previously changed the password. As a rule of thumb, always call
packfile_password(NULL) when you are done with operations on packfiles.
PACKFILE *pack_fopen(const char *filename, const char *mode);
Opens a file according to mode, which may contain any of the flags:
-
'r' - open file for reading.
-
'w' - open file for writing, overwriting any existing data.
-
'p' - open file in packed mode. Data will be compressed as it is
written to the file, and automatically uncompressed during read
operations. Files created in this mode will produce garbage if
they are read without this flag being set.
-
'!' - open file for writing in normal, unpacked mode, but add the
value F_NOPACK_MAGIC to the start of the file, so that it can
later be opened in packed mode and Allegro will automatically
detect that the data does not need to be decompressed.
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:
-
"#" - read data that has been appended to your executable file with
the exedat utility, as if it was a regular independent disk file.
-
'filename.dat#object_name' - open a specific object from a datafile,
and read from it as if it was a regular file. You can treat nested
datafiles exactly like a normal directory structure, for example
you could open 'filename.dat#graphics/level1/mapdata'.
-
'#object_name' - combination of the above, reading an object from a
datafile that has been appended onto your executable.
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(const void *p, long n, PACKFILE *f);
char *pack_fgets(char *p, int max, PACKFILE *f);
int pack_fputs(const char *p, PACKFILE *f);
These work like the equivalent stdio functions. There are some
differences, however:
-
Seeking only supports forward movement relative to the current position.
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.
-
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.
Intel is least significant byte first (little-endian); Motorola is most
significant byte first (big-endian).
-
pack_fread() and pack_fwrite() take a single size parameter instead of
that silly size and num_elements system.
-
The pack_fgets() function does not include a trailing carriage return in
the returned string.
-
pack_fputs() always writes in the UTF-8 text encoding format, converting
from the current text encoding. Newlines (\n) are written as \r\n on
DOS/Windows. If you do not want either of these things to happen,
use pack_fwrite() and/or pack_putc() instead.
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 */
f = pack_fopen_chunk(f, pack); /* in write mode */
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 */
f = pack_fopen_chunk(f, FALSE); */ in read mode */
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().
|