|
|
|
|
|
|
C H A
P T E RF
O U R
THE
STARCRAFT
CAMPAIGN
EDITOR
AND THE MPQ API LIBRARY
|
|
|
|
Just
what is the Starcraft Campaign Editor (or StarEdit, for short)? Just about
anyone who owns Starcraft can tell you that it is a program that allows you
to make Starcraft missions, and save them to disk in the form of SCM or SCX
files. But is that really all it is? Are these SCM/SCXs nothing more than
raw data files, like they were in Warcraft 2? Well, anyone who has ever
looked at an SCM/SCX in a hex editor can tell you a different answer. As a
matter of fact, SCM/SCXs are actually MPQs! The reasons for this should be
immediately obvious: for faster distribution over Battle.net with
compression, and to allow the sounds you put in a level to easily be packed
right into the SCM/SCX itself. But what does all this mean to us? It
means that StarEdit contains the elusive MPQ-writing routines we've been
unable to find in Storm.
|
|
|
Using StarEdit - The MPQ API Library |
|
|
|
WARNING: THE REST OF
THIS CHAPTER IS SPECIFIC TO THE WINDOWS PLATFORM, AND APPLIES ONLY TO THE
MPQ API LIBRARY VERSION 2.0 OR GREATER!
Click
here to download the MPQ API Library for Windows
But not so quick to fire up
your compilers, there. Just because StarEdit has the functions doesn't mean
we can use them. In fact, it couldn't really be more difficult to use them.
You see, unlike shared libraries like Storm, StarEdit's internal functions
are protected by complicated OS mechanisms which are well beyond the
scope of this document. Consequently, it is totally impossible for a normal
(or even good) programmer to get access to them. For this task, none other
than a programmer who can manipulate the internal workings of the operating
system will do. This is where Andrey Lelikov (commonly referred to as Lelik)
enters the picture.
Lelik
is a (very) experienced Russian programmer who is no stranger to the
internal workings of operating system. He has engineered an ingenious - if
however unorthodox - method of using the StarEdit MPQ functions. Once again,
I won't confuse you with the details, as you probably wouldn't understand
them anyway (I've seen the source code); suffice to say that Lelik
"possesses" StarEdit, and makes it call its own functions (aren't you sorry
you asked?). Fortunately, Lelik has neatly packaged his skills into
something called the MPQ API Library (as a matter of fact, I've had the
privilege of working with Lelik on actually writing the most recent version
of the Library).
Like
Storm, the MPQ API Library (aka LMPQAPI) is contained in a shared library
(unfortunately, like the Storm Interface Library, no Mac version of LMPQAPI
exists at this time). LMPQAPI not only encapsulates the interface to access
the MPQ writing functions in StarEdit, but also an interface to access
Storm. That means that if you want to use both Storm and StarEdit, you don't
need to use both LMPQAPI and the Storm Interface Library; you only need
LMPQAPI.
Okay,
now I have just one more thing to mention here. The way you can
differentiate between the Storm and StarEdit functions when using LMPQAPI is
this: Storm functions have a prefix of 'SFile' (just like when using the
Storm Interface Library), while StarEdit functions have a prefix of 'Mpq'.
This is important to know for one reason: Storm and StarEdit functions are
not compatible. That means that you may not obtain an MPQ HANDLE
with SFileOpenArchive and then call a function in StarEdit (an 'Mpq'
prefixed function) with that HANDLE, or vice versa. If you do this,
the call will fail, or maybe even crash. Now you know.
|
|
|
¿Sé Habla Español? |
|
|
|
Because 75% or more of the
Starcraft/Diablo players are English speaking, most MPQs are developed and
tested on the English version of these games. To any of these people, any
home-brewed MPQ would work fine. As a matter of fact, since 98% of the files
in the standard MPQs are language-neutral (files such as images, etc.), even
someone using the same MPQ with a non-English version of the game would
stand a good chance of never noticing anything unusual. But, obvious or not,
there are some problems here, and it's only a matter of time before
someone notices them.
As
explained in chapters 2 and
3, the MPQ format contains robust multilingual
features. But, in case you haven't noticed, there is no real need to create
multilingual SCM/SCXs. That means that it isn't necessary for StarEdit to
support the multilingual features; and, as infamously lazy as Blizzard
programmers are, StarEdit doesn't support these features. But,
we are interested in working with common MPQs, where multilingualism is
useful, if not essential. Nevertheless, there is no getting around the fact
StarEdit simply can't do it. Technically, all of the StarEdit functions only
work with files that have a language code of 0, the language-neutral code
(see chapter 3). So that
means that MpqAddFileToArchive and MpqAddWAVToArchive will
only add language- neutral files, MpqDeleteFile only deletes
language-neutral files, and MpqRenameFile only renames
language-neutral files.
The
consequences of this design decision are not immediately obvious. Also
mentioned in chapter 3 is the fact that Storm
uses a language filter set by SFileSetLocale to decide which file to
open if there are multiple ones with the same name in different languages.
Suppose you have an MPQ that is replacing patch_rt.mpq in Starcraft, and in
that MPQ is an English/language-neutral version of the file rez\gluAll.tbl
(this is one of the files that has multiple language versions) but not a
Portuguese (language chosen completely arbitrarily) version. When you run
this thing on a Portuguese version of Starcraft, it will check for the
Portuguese version in your MPQ, fail, and default to the English version,
right? Well, no. You see, Storm allows you to open several MPQs at one time
and let Storm search through all the loaded MPQs for a file and load it from
the newest MPQ (whichever one it may be) automatically. But, in doing this,
Storm checks all the open MPQs for a specific language before it
defaults to the language-neutral one. That means that, in the previous
scenario, Storm will load the Portuguese version of the file from
broodat.mpq instead of your own language-neutral version!
Unfortunately, at this time there is no way around this problem. I hope to
remedy this problem in an upcoming version of LMPQAPI.
|
|
|
Initializing the MPQ API Library
- MpqInitialize |
|
|
|
BOOL WINAPI MpqInitialize();
Unlike
Storm, LMPQAPI has an enormous, complicated task to perform in taking the
reins of StarEdit. Consequently, it is not possible for LMPQAPI to do all of
this at startup. You must tell LMPQAPI when it's time to go to work.
Fortunately, this is a simple task; all you must do is call MpqInitialize
at the beginning of your program, and LMPQAPI will do the rest. But, it is
imperative that you do indeed call MpqInitialize near the beginning
of you program, and definitely before you make any other calls to LMPQAPI.
Also, MpqInitialize must be called before you make any Storm calls,
even though you didn't have to do anything in the Storm Interface Library
(one call to MpqInitialize is all it takes to initialize both Storm
and StarEdit).
- Either Starcraft/Brood War 1.07 must be
installed properly on the computer or the Storm.dll and StarEdit.exe files
from Starcraft/Brood War 1.07 must be in the directory of your program.
- StarEdit must not be running at the
same time LMPQAPI is.
The
two requirements shown above must be met in order for MpqInitialize
to succeed, and, one of these conditions not being met is by far the most
common reason for MpqInitialize to fail (although in rare cases it
may do so for some other reason). Should MpqInitialize fail for one
reason or another, it will return FALSE, and usually set an error
value you can retrieve with GetLastError. If LMPQAPI cannot find the
StarEdit.exe file in the Starcraft directory or in the same directory as
your program, GetLastError will return MPQ_ERROR_NO_STAREDIT.
If the version of StarEdit.exe LMPQAPI finds is not that from Starcraft/Brood
War 1.07, GetLastError will return MPQ_ERROR_BAD_STAREDIT. If
any version of StarEdit is running when you call MpqInitialize,
GetLastError will return MPQ_ERROR_STAREDIT_RUNNING. If
MpqInitialize should fail for any other reason, GetLastError will
usually return MPQ_ERROR_INIT_FAILED. But, regardless of why
MpqInitialize failed or what GetLastError returns, it is very
important that you end your program as quickly as possible; and whatever you
do, do not call any other LMPQAPI functions (Storm or StarEdit
functions); that includes trying to call MpqInitialize again.
|
|
|
Opening an MPQ for Editing -
MpqOpenArchiveForUpdate |
|
|
|
HANDLE WINAPI
MpqOpenArchiveForUpdate(LPCSTR lpFileName,
DWORD
dwCreationDisposition , DWORD
dwHashTableSize);
|
|
Parameter |
What it is |
|
|
lpFileName |
[in] A pointer to NULL-terminated
string that holds the path of the MPQ to open.
MpqOpenArchiveForUpdate will fail if this is NULL. |
|
|
dwCreationDisposition |
[in] Specifies what
MpqOpenArchiveForUpdate should do with the archive if it
does/doesn't exist. Must be one of the following values defined in
lmpqapi.h:
MOAU_CREATE_NEW |
MpqOpenArchiveForUpdate will create a brand new archive. If
lpFileName already exists, MpqOpenArchiveForUpdate will
fail. |
MOAU_CREATE_ALWAYS |
MpqOpenArchiveForUpdate will create a new archive if
lpFileName doesn't exist. If lpFileName does exist, it
will be deleted and overwritten. |
MOAU_OPEN_EXISTING |
MpqOpenArchiveForUpdate will open the archive lpFileName.
MpqOpenArchiveForUpdate will fail if lpFileName does
not exist. |
MOAU_OPEN_ALWAYS |
If lpFileName
exists, MpqOpenArchiveForUpdate will open it. If it doesn't,
MpqOpenArchiveForUpdate will create a new archive. |
MOAU_MAINTAIN_LISTFILE |
This flag specifies
that as MpqOpenArchiveForUpdate uses the archive
lpFileName, it should update the internal listfile, if one
exists. This is a flag, and must be ORed (with the | operator) with
one of the other options for dwCreationDisposition |
|
|
|
dwHashTableSize |
[in] Whenever
MpqOpenArchiveForUpdate creates a new archive (see the
dwCreationDisposition for details
on when this occurs),
dwHashTableSize specifies how
large the hash table for the new archive will be, with a minimum size of
16, and a maximum size of 262,144 (if
dwHashTableSize
is not within these values,
MpqOpenArchiveForUpdate will
change it). This parameter does not affect archives that already
exist. |
|
As
with Storm, before you can start using an MPQ archive, you must open it.
MpqOpenArchiveForUpdate opens (or creates) an archive for use with the
other StarEdit functions, and gives a HANDLE to that archive as its
return value. But unlike SFileOpenArchive, MpqOpenArchiveForUpdate
requires that you make some choices ahead of time. The first choice is the
dwCreationDisposition parameter. It tells MpqOpenArchiveForUpdate
whether it should create a new archive, open an existing one, or something
in-between. The other decisive parameter is dwHashTableSize.
dwHashTableSize tells MpqOpenArchiveForUpdate what size the
archive's hash table (which is also the file limit) should be, in the event
that MpqOpenArchiveForUpdate must create a new archive. 1000 is
generally a good size for an archive's hash table unless you know that the
archive will definitely need to hold more than 1000 files. But also keep in
mind that each hash table entry will add 16 bytes to the archive's base size
(see chapter 2 for general information about hash
tables, or chapter 5 for more info about the MPQ
hash table).
As
with nearly everything, MpqOpenArchiveForUpdate fails on occasion. If
this happens, MpqOpenArchiveForUpdate's return value will be either
INVALID_HANDLE_VALUE or NULL. We can often (but not always)
get helpful information about what happened by calling GetLastError.
If the lpFileName parameter is NULL, GetLastError will
return ERROR_INVALID_PARAMETER. If dwCreationDisposition is
MOAU_OPEN_EXISTING and the file lpFileName does not exist,
GetLastError will return ERROR_FILE_NOT_FOUND. Conversely, if
dwCreationDisposition is MOAU_CREATE_NEW and the file
lpFileName already exists, GetLastError will return
ERROR_ALREADY_EXISTS. Finally, if the MPQ archive exists but is invalid
or corrupted, GetLastError will return MPQ_ERROR_MPQ_INVALID.
On some rare occasions, GetLastError will return some other, cryptic
error code.
|
|
|
Closing a Modified Archive -
MpqCloseUpdatedArchive |
|
|
|
BOOL WINAPI
MpqCloseUpdatedArchive(HANDLE hMPQ,
DWORD dwUnknown);
|
|
Parameter |
What it is |
|
|
hMPQ |
[in] The HANDLE of
the MPQ to close, which was acquired earlier with
MpqOpenArchiveForUpdate. MpqCloseUpdatedArchive will fail (or
worse) if this is NULL or a HANDLE not obtained with
MpqOpenArchiveForUpdate. |
|
|
dwUnknown |
Unknown. Should always be
NULL. |
|
Just
like in Storm, where any MPQ you open with SFileOpenArchive must be
closed with SFileCloseArchive when you're done with it; an MPQ
archive opened with MpqOpenArchiveForUpdate must be closed with
MpqCloseUpdatedArchive. However, in this case things are a little more
dire. You see, Storm doesn't modify the actual MPQ, so nothing special needs
to be done to close a MPQ HANDLE. But, StarEdit does modify
the MPQ. And, as a matter of fact, an MPQ's
hash and file tables (see chapters 2 and
5) don't actually get written to the MPQ on disk
until the MPQ HANDLE is closed with MpqCloseUpdatedArchive.
That means it's very important to close StarEdit MPQ HANDLEs
quickly, or you risk corrupting the MPQ (this is especially true in the case
of a crash).
|
|
|
Adding a File - MpqAddFileToArchive |
|
|
|
BOOL WINAPI
MpqAddFileToArchive(HANDLE hMPQ,
LPCSTR lpSourceFileName,
LPCSTR lpDestFileName, DWORD
dwFlags); |
|
Parameter |
What it is |
|
|
hMPQ |
[in] The HANDLE of
the MPQ to add the file to, which was acquired earlier with
MpqOpenArchiveForUpdate. MpqAddFileToArchive will fail if
this is NULL or a HANDLE not obtained with
MpqOpenArchiveForUpdate. |
|
|
lpSourceFileName |
[in] A pointer to a NULL-terminated
string containing the path of the file on disk to add.
MpqAddFileToArchive will crash if this is NULL. |
|
|
lpDestFileName |
[in] A pointer to a NULL-terminated
string containing the name that the file will be given in the MPQ.
MpqAddFileToArchive will crash if this is NULL. |
|
|
dwFlags |
[in] Flags specifying
properties that MpqAddFileToArchive will apply to the file inside
the MPQ. Must be a combination of the following flags specified in
lmpqapi.h:
MAFA_ENCRYPT |
The file will be
encrypted. |
MAFA_COMPRESS |
The file will be
compressed. |
MAFA_REPLACE_EXISTING |
If the file
lpDestFileName already exists in the MPQ, it will be replaced
with the new file to be added |
|
|
Invariably,
about 95% of the time you'll use the StarEdit MPQ functions for no other
reason than to add files to an MPQ. For this task, you'll use
MpqAddFileToArchive and its sister function MpqAddWAVToArchive
(discussed later). MpqAddFileToArchive adds the file
lpSourceFileName on disk to the MPQ hMPQ with a name of
lpDestFileName, compressing and/or encrypting it in the process.
However,
there are a number of design oversights in MpqAddFileToArchive which
can quickly become major trouble spots if you're not ready for them. First
of all, MpqAddFileToArchive does not check to make sure that
lpSourceFileName and lpDestFileName are not NULL. That
means that MpqAddFileToArchive may crash if either of the parameters
are NULL. Next is the way MpqAddFileToArchive handles things
when you overwrite an existing file in the MPQ. You see, when you call
MpqAddFileToArchive to add a file that already exists in the MPQ (in
which case you would have to specify MAFA_REPLACE_EXISTING in
dwFlags), MpqAddFileToArchive will indiscriminately delete the
existing file lpDestFileName before it makes sure that the
file lpSourceFileName even exists. It doesn't take a rocket-scientist
to figure out the serious problem this flaw could cause. Fortunately, the
solution to both these problems is simple and easy: don't leave it up to
chance; make sure lpSourceFileName and lpDestFileName are
valid (non-NULL), and that lpSourceFileName exists before
calling MpqAddFileToArchive.
Now,
should you make it past these and all the other hurdles and
MpqAddFileToArchive is able to successfully add the file,
MpqAddFileToArchive will gives you the thumbs-up with a return value of
TRUE. Or, if something went wrong, it will return FALSE. In
this case, a small amount of information can be obtained by calling
GetLastError. If the file lpSourceFileName doesn't exist,
GetLastError will return ERROR_FILE_NOT_FOUND (although it's a
little late now!). If the hash table is full (see
chapter 2), GetLastError will return MPQ_ERROR_HASH_TABLE_FULL.
If the file lpDestFileName already exists and
MAFA_REPLACE_EXISTING was not specified in dwFlags,
GetLastError will return MPQ_ERROR_ALREADY_EXISTS. But, on many
occasions, GetLastError will return some other, cryptic error code or
no code at all.
|
|
|
Adding a File with WAV
Compression - MpqAddWAVToArchive |
|
|
|
BOOL WINAPI
MpqAddWAVToArchive(HANDLE hMPQ,
LPCSTR lpSourceFileName,
LPCSTR lpDestFileName, DWORD
dwFlags, DWORD
dwQuality); |
|
Parameter |
What it is |
|
|
hMPQ |
[in] The HANDLE of
the MPQ to add the file to, which was acquired earlier with
MpqOpenArchiveForUpdate. MpqAddWAVToArchive will fail if this
is NULL or a HANDLE not obtained with
MpqOpenArchiveForUpdate. |
|
|
lpSourceFileName |
[in] A pointer to a NULL-terminated
string containing the path of the file on disk to add.
MpqAddWAVToArchive will crash if this is NULL. |
|
|
lpDestFileName |
[in] A pointer to a NULL-terminated
string containing the name that the file will be given in the MPQ.
MpqAddWAVToArchive will crash if this is NULL. |
|
|
dwFlags |
[in] Flags specifying
properties that MpqAddWAVToArchive will apply to the file inside
the MPQ. Must be a combination of the following flags specified in
lmpqapi.h:
MAFA_ENCRYPT |
The file will be
encrypted. |
MAFA_REPLACE_EXISTING |
If the file
lpDestFileName already exists in the MPQ, it will be replaced
with the new file to be added |
|
|
|
dwQuality |
[in] Specifies the quality
the WAV file will be compressed to. Must be one of the following values
defined in lmpqapi.h:
MAWA_QUALITY_HIGH |
Best sound quality,
least compression. WAV
lpDestFileName in MPQ will
occupy the most space after compression (but still less space than
if you had added the file with MpqAddFileToArchive instead) |
MAWA_QUALITY_MEDIUM |
Medium sound quality,
medium compression. Balance between quality and compression. |
MAWA_QUALITY_LOW |
Highest compression,
worst sound quality. WAV
lpDestFileName in MPQ will
occupy the least space after compression. |
|
|
Undoubtedly,
the most popular new feature of the LMPQAPI v2.0 (the one that I worked on!)
would have to be this function right here: MpqAddWAVToArchive. You
see, while MpqAddFileToArchive's standard compression works fine for
about 80% of files (non-WAV files), it chokes on WAV files, compressing
them, on average, about 5%. This is due to the nature of WAV data and its
fundamental incompressibility. Here, a special type of compression is
necessary: WAV compression. MpqAddWAVToArchive supplies this special
compression type.
Despite
the extreme differences in the way they function, the interface to
MpqAddWAVToArchive is almost identical to that of MpqAddFileToArchive,
the only difference being a single new parameter: dwQuality. This
parameter specifies the quality the finished WAV will have after being
compressed. You see, unlike MpqAddFileToArchive's standard
compression, MpqAddWAVToArchive's WAV compression actually degrades
the WAV. That means the WAV after compression will be of lower quality than
the original WAV before compression. How much lower quality is dependant on
dwQuality. If you have a music WAV and music quality is important to
you, you'll probably want to use MAWA_QUALITY_HIGH, as it best
preserves the WAV; whereas with a voice WAV, MAWA_QUALITY_LOW will
usually suffice, since vocals are relatively easy to compress; and
MAWA_QUALITY_MEDIUM is always available should you need to compromise
for one reason or another.
|
|
|
Deleting a File - MpqDeleteFile |
|
|
|
BOOL WINAPI
MpqDeleteFile(HANDLE hMPQ,
LPCSTR lpFileName);
|
|
Parameter |
What it is |
|
|
hMPQ |
[in] The HANDLE of
the MPQ to close, which was acquired earlier with
MpqOpenArchiveForUpdate. MpqDelete will fail (or worse) if
this is NULL or a HANDLE not obtained with
MpqOpenArchiveForUpdate. |
|
|
lpFileName |
[in] A pointer to a NULL-terminated
name of the file in the MPQ to delete. MpqDeleteFile will crash
if this is NULL. |
|
On
rare occasions, it may be necessary to delete an unwanted file from an MPQ.
To perform this task, MpqDeleteFile is the function for the job.
MpqDeleteFile deletes the file lpFileName from the open archive
hMPQ. However, it's not quite that simple. MpqDeleteFile
removes the hash and file table entries for lpFileName, making it
impossible to access. But, MpqDeleteFile doesn't actually free the
space the file was occupying unless the it is at the physical end of the MPQ.
That means that often the MPQ won't actually decrease in size; however, the
space may be recycled and used by a new file added later with
MpqAddFileToArchive or MpqAddWAVToArchive.
Just
like almost every other function in Storm and StarEdit, MpqDeleteFile
returns TRUE on success and FALSE on failure, and you can call
GetLastError to get some information about why it failed (assuming it
did). In this case, GetLastError is surprisingly helpful; there is
basically only one reason MpqDeleteFile will fail (with the exception
of hMPQ being invalid): if the file lpFileName doesn't exist
in the MPQ. In that case, GetLastError will return
MPQ_ERROR_FILE_NOT_FOUND.
|
|
|
Renaming a File - MpqRenameFile |
|
|
|
BOOL WINAPI
MpqRenameFile(HANDLE hMPQ,
LPCSTR lpOldFileName, LPCSTR
lpNewFileName); |
|
Parameter |
What it is |
|
|
hMPQ |
[in] The HANDLE of
the MPQ that holds the file to be renamed, and was acquired earlier with
MpqOpenArchiveForUpdate. MpqRenameFile will fail if this
is NULL or a HANDLE not obtained with
MpqOpenArchiveForUpdate. |
|
|
lpOldFileName |
[in] A pointer to a NULL-terminated
string containing the name of the file in the MPQ to be renamed.
MpqRenameFile will fail if this is NULL. |
|
|
lpNewFileName |
[in] A pointer to a NULL-terminated
string containing the name that the file lpOldFileName will be
changed to. MpqRenameFile will fail if this is NULL. |
|
As
I got further and further in MPQ2K, it became increasingly apparent that
StarEdit was lacking a couple useful features. By that time I had
considerable knowledge of the workings of Storm and StarEdit, so I decided
to write the functions myself. The second of my home-brewed MPQ functions is
this one: MpqRenameFile. MpqRenameFile is extremely
straightforward; it renames the specified file lpOldFileName in
hMPQ to lpNewFileName.
Equally
straightforward is the feedback that MpqRenameFile gives you.
Following tradition, MpqRenameFile returns TRUE on success and
FALSE of failure, and lets you (usually) get the cause of failure
with GetLastError. If the hMPQ HANDLE is NULL or
invalid, or lpOldFileName or lpNewFileName is NULL,
GetLastError will return ERROR_INVALID_PARAMETER. If the file
lpOldFileName doesn't exist in the MPQ hMPQ, GetLastError
will return MPQ_ERROR_FILE_NOT_FOUND. If the file lpNewFileName
exists already in the MPQ, GetLastError will return
MPQ_ERROR_ALREADY_EXISTS.
|
|
|
Compacting an MPQ - MpqCompactArchive |
|
|
|
BOOL WINAPI
MpqCompactArchive(HANDLE hMPQ,
BOOL bFailOnBadFile); |
|
Parameter |
What it is |
|
|
hMPQ |
[in] The HANDLE of
the MPQ to compact, which was acquired earlier with
MpqOpenArchiveForUpdate. MpqCompactArchive will fail if this
is NULL or a HANDLE not obtained with
MpqOpenArchiveForUpdate. |
|
|
bFailOnBadFile |
[in] Explained below.
Should usually be FALSE. |
|
Recall,
if you will, that I said earlier that MpqDeleteFile doesn't actually
delete file from an MPQ (not usually, anyway); it just makes them unusable.
Well, MpqAddFileToArchive and MpqAddWAVToArchive do the same
thing. When you add a file that already exists and the new file is larger
than the old one, the new file will be appended, and the space the original
occupied won't be freed. Because of this, whenever you create a large,
complex MPQ containing many overwritten/deleted files, the will accumulate a
substantial amount of dead weight, and there is no way to get rid of it
without rebuilding the whole MPQ from scratch.
The
solution to this problem is MpqCompactArchive: my third, final, and
most difficult designer MPQ function. MpqCompactArchive functions by
marshalling data from the MPQ being compacted to a buffer file, and then
back again. That means that there must be enough free space remaining
on the disk to hold the buffer file (which will be the size of the used
portion of the MPQ) during the compacting process.
Although
MpqCompactArchive can compact nearly all of the files in any MPQ,
there is a certain type of file that it cannot touch: a file that has the
MAFA_ENCRYPT and MAFA_MODCRYPTKEY attributes, but lacks the
MAFA_COMPRESS and MAFA_COMPRESS2 attributes (for more
information, see chapter 5). Thus far, this nasty
combination has never been used on any file in any MPQ ever made. But,
should MpqCompactArchve ever encounter one of these files, it will
set the error code attainable with GetLastError to
MPQ_ERROR_COMPACT_ERROR, delete the offending file and continue with the
compacting process.
MpqCompactArchive
will return TRUE on success, and FALSE on failure. You can
obtain more information about a failure by calling GetLastError. If
hMPQ is NULL or an invalid StarEdit HANDLE,
GetLastError will return ERROR_INVALID_PARAMETER. If there
is not enough free memory for MpqCompactArchive to allocate a 2.5 MB
read buffer, GetLastError will return ERROR_OUTOFMEMORY. If
there isn't enough free disk space for MpqCompactArchive to allocate
the buffer file, GetLastError returns ERROR_DISK_FULL. In
certain remote situations, GetLastError may return some other strange
error code.
|
|
|
Getting Information About a File
- SFileGetFileInfo |
|
|
|
DWORD WINAPI
SFileGetFileInfo(HANDLE hMPQorFile,
DWORD dwInfoType);
|
|
Parameter |
What it is |
|
|
hMPQorFile |
[in] The HANDLE of
the either the MPQ or file inside an MPQ to obtain info about, which was
acquired earlier with either SFileOpenArchive or SFileOpenFileEx.
SFileGetFileInfo will fail (or worse) if this is NULL or a
HANDLE not obtained with SFileOpenArchive or
SFileOpenFileEx. |
|
|
dwInfoType |
[in] The type of info to
obtain about the file or MPQ. Must be one of the following values
defined in lmpqapi.h:
SFILE_INFO_HASH_TABLE_SIZE |
SFileGetFileInfo
will return the hash table size of hMPQorFile (see chapters
2 and 5 for
more info). hMPQorFile must be a HANDLE of an MPQ. |
SFILE_INFO_NUM_FILES |
SFileGetFileInfo
will return the number of files inside hMPQorFile.
hMPQorFile must be a HANDLE of an MPQ. |
SFILE_INFO_SIZE |
SFileGetFileInfo
will return the size of the MPQ hMPQorFile or the
uncompressed size of the file hMPQorFile. This is
functionally equivalent to the SFileGetFileSize function.
hMPQorFile can be either a HANDLE of an MPQ or a
HANDLE of a file inside. |
SFILE_INFO_COMPRESSED_SIZE |
SFileGetFileInfo
will return the compressed size (or uncompressed size if the file
isn't compressed) of the file hMPQorFile in an MPQ.
hMPQorFile must be a HANDLE of a file inside an MPQ. |
SFILE_INFO_FLAGS |
SFileGetFileInfo
will return the flags (see chapter 5 for
the meaning of specific flags) of the file hMPQorFile in an
MPQ. hMPQorFile must be a HANDLE of a file inside an
MPQ. |
SFILE_INFO_POSITION |
SFileGetFileInfo
will return the absolute position of the file pointer for the file
hMPQorFile in an MPQ. hMPQorFile must be a HANDLE
of a file inside an MPQ. |
|
|
SFileGetFileInfo
is, without a doubt, the oddball function in LMPQAPI. Not only is it the
only Storm function available through LMPQAPI and not the Storm Interface
Library, and the only function that can accept either an MPQ or a
file HANDLE, but it actually doesn't exist in Storm at all! Like with
StarEdit, it became apparent over time that Storm had some serious
deficiencies of its own: namely that it couldn't provide advanced
information, such as an MPQ's hash table size, or a file's compressed size.
To remedy this, I wrote SFileGetFileInfo, the first of my home-made
trio.
SFileGetFileInfo
is designed specifically to provide useful, more advanced information about
MPQs and the files inside; everything from the number of files in an MPQ to
the compressed size of files, to the position of a file's file pointer. It
is simple to use, but quite effective; it simple takes the HANDLE of
the open MPQ or file to obtain information about in hMPQorFile, and a
code for the type of information required in dwInfoType.
If
SFileGetFileInfo succeeds, the return value will be the requested
information about the file or MPQ. But, if it fails, it will return
0xffffffff, and set an error code you can retrieve with GetLastError.
If it failed because the hMPQorFile HANDLE was invalid,
GetLastError will return ERROR_INVALID_PARAMETER. If
SFileGetFileInfo failed because dwInfoType was an invalid
information code, GetLastError will return ERROR_UNKNOWN_PROPERTY.
SFileGetFileInfo may also fail because hMPQorFile was an MPQ
HANDLE and dwInfoType specifies information that can only be
obtained about a file HANDLE, or vice versa, in which case
GetLastError will return ERROR_UNKNOWN_PROPERTY also. Other than
a critical internal LMPQAPI error, these are the only reasons
SFileGetFileInfo will fail.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|