RWRES.C


/******************************************************************************\
* This is a part of the Microsoft Source Code Samples.
* Copyright 1993 - 1998 Microsoft Corporation.
* All rights reserved.
* This source code is only intended as a supplement to
* Microsoft Development Tools and/or WinHelp documentation.
* See these sources for detailed information regarding the
* Microsoft samples programs.
\******************************************************************************/

/****************************** Module Header *******************************
* Module Name: rwres.c
*
* Does all the reading and writing of the .RES (resource) file.
*
* Functions:
*
* OpenResFile()
* WriteRes()
* LoadResFile()
* IsValidResFile()
* SafeParseResHeader()
* SafeNameOrdLen()
* SafeDWordAlign()
* WriteDlgIncludeRes()
*
* Comments:
*
****************************************************************************/

#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"


/*
* The bytes in the special RT_RESOURCE32 type resource that is the
* first resource in every Win32 format res file. The first 8 bytes
* in this resource's header were specially designed to be invalid
* for a 16 bit format resource file, so that tools can determine
* immediately if they are reading a 16 bit or a Win32 format res
* file.
*/
static BYTE abResource32[] = {
0x00, 0x00, 0x00, 0x00, // DataSize (0 bytes).
0x20, 0x00, 0x00, 0x00, // HeaderSize (32 bytes).
0xff, 0xff, 0x00, 0x00, // Type (RT_RESOURCE32).
0xff, 0xff, 0x00, 0x00, // Name (ordinal 0).
0x00, 0x00, 0x00, 0x00, // DataVersion
0x00, 0x00, // MemoryFlags
0x00, 0x00, // LanguageId
0x00, 0x00, 0x00, 0x00, // Version
0x00, 0x00, 0x00, 0x00 // Characteristics
};


STATICFN BOOL LoadResFile(HANDLE hfRes, LPTSTR pszFullResFile,
LPTSTR pszIncludeBuf);
STATICFN BOOL IsValidResFile(PRES pRes, INT cbFileSize);
STATICFN PRES SafeParseResHeader(PRES pRes, INT cbMaxSize);
STATICFN INT SafeNameOrdLen(LPTSTR psz, INT cbMaxLen);
STATICFN VOID SafeDWordAlign(PBYTE *ppb, PINT pcbMax);
STATICFN BOOL WriteDlgIncludeRes(HANDLE hfWrite, LPTSTR pszFullResFile);



/************************************************************************
* OpenResFile
*
* High level function to load the data in a resource file. The
* function LoadResFile is called to do the actual work, after
* this code does some housekeeping.
*
* Arguments:
* LPTSTR pszFullPath - The full path to the resource file.
*
* Returns:
* TRUE if resource file was opened; otherwise, FALSE.
*
************************************************************************/

BOOL OpenResFile(
LPTSTR pszFullPath)
{
HCURSOR hcurSave;
PRESLINK prl;
PRESLINK prlSave;
BOOL fSuccess = FALSE;
INT cDlg;
HANDLE hfRes;
TCHAR szInclude[CCHMAXPATH];
TCHAR szFullInclude[CCHMAXPATH];
BOOL fIncOpened = FALSE;

hcurSave = SetCursor(hcurWait);

/*
* Close any existing resource and include file and free memory.
* It is assumed that if either had been changed, the user was asked
* if they wanted to save them, because it is too late now.
*/
FreeRes();
FreeInclude();

if ((hfRes = CreateFile(pszFullPath, GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN, NULL)) != (HANDLE)-1 &&
LoadResFile(hfRes, pszFullPath, szInclude)) {
lstrcpy(szFullResFile, pszFullPath);
pszResFile = FileInPath(szFullResFile);

ShowFileStatus(TRUE);

/*
* If there was a DLGINCLUDE resource found, try and open the
* specified include file, after making sure that it is a
* fully formed path.
*/
if (*szInclude) {
/*
* Does the include filespec from the res file appear to
* be a simple filename without a path? If so, look for
* it in the same directory that the res file is in.
* Otherwise, assume it has a fully qualified path.
*/
if (!HasPath(szInclude)) {
lstrcpy(szFullInclude, szFullResFile);
lstrcpy(FileInPath(szFullInclude), szInclude);
}
else {
lstrcpy(szFullInclude, szInclude);
}

fIncOpened = OpenIncludeFile(szFullInclude);
}

/*
* If there wasn't an include resource found, or there was
* but it couldn't be opened, we want to ask the user for the
* include file to use for this resource file.
*/
if (!fIncOpened)
Open(FILE_INCLUDE);

/*
* Start counting the dialogs in the resource, but stop at two.
*/
cDlg = 0;
for (cDlg = 0, prl = gprlHead; prl; prl = prl->prlNext) {
if (prl->fDlgResource) {
if (++cDlg > 1)
break;

prlSave = prl;
}
}

/*
* If there are multiple dialogs, display the "Select Dialog"
* dialog to ask the user which one they want to edit. If
* there is exactly one dialog, just go ahead and show it
* initially.
*/
if (cDlg == 1)
ResLinkToDialog(prlSave);
else if (cDlg > 1)
SelectDialogDialog();

fSuccess = TRUE;
}

if (hfRes != (HANDLE)-1)
CloseHandle(hfRes);

ShowFileStatus(TRUE);
SetCursor(hcurSave);

return fSuccess;
}



/************************************************************************
* LoadResFile
*
* Loads the resource file specified by the passed in file handle.
* This function first verifies that it is a valid resource file.
*
* Arguments:
* HANDLE hfRes - File handle to read from.
* LPTSTR pszFullResFile - Full name of resource file being loaded.
* LPTSTR pszIncludeBuf - Where to return the include file name, if
* a DLGINCLUDE resource is found in the res
* file. If not, this buffer gets a null byte
* as its first character.
*
* Returns:
* TRUE if load was successful; otherwise, FALSE is returned.
*
************************************************************************/

STATICFN BOOL LoadResFile(
HANDLE hfRes,
LPTSTR pszFullResFile,
LPTSTR pszIncludeBuf)
{
HANDLE hAllRes;
PRES pRes;
PRES pResAll;
PRESLINK prl;
PRESLINK prlT;
INT cbRead;
LPTSTR pszResType;
DWORD cbFileSize;

cbFileSize = GetFileSize((HANDLE)hfRes, NULL);

if (!(hAllRes = GlobalAlloc(GMEM_MOVEABLE, cbFileSize))) {
Message(MSG_OUTOFMEMORY);
return FALSE;
}

*pszIncludeBuf = CHAR_NULL;
pRes = pResAll = (PRES)GlobalLock(hAllRes);
if ((cbRead = _lread((HFILE)hfRes, (LPSTR)pResAll, cbFileSize)) != -1 &&
cbRead == (INT)cbFileSize) {
if (!IsValidResFile(pResAll, cbFileSize)) {
Message(MSG_BADRESFILE, pszFullResFile);
}
else do {
pszResType = ResourceType(pRes);

if (IsOrd(pszResType) && OrdID(pszResType) == ORDID_RT_DLGINCLUDE) {
/*
* Pass back the include file name. This resource
* will not be saved in the res list because it is
* going to be explicitly written out later if
* necessary.
*/
NameOrdCpy(pszIncludeBuf, (LPTSTR)SkipResHeader(pRes));
}
else if (IsOrd(pszResType) &&
OrdID(pszResType) == ORDID_RT_RESOURCE32) {
/*
* This is the dummy resource that identifies a
* 32 bit resource file. This resource should be
* skipped also.
*/
}
else {
/*
* This is some other kind of a resource.
* Save it in the resource list.
*/
if (!(prlT = AllocResLink(pRes))) {
FreeResList();
break;
}

if (!gprlHead) {
gprlHead = prl = prlT;
}
else {
prl->prlNext = prlT;
prl = prlT;
}
}

/*
* Move to the next resource.
*/
pRes = (PRES)((PBYTE)pRes + pRes->HeaderSize + pRes->DataSize);
DWordAlign((PBYTE *)&pRes);
} while (pRes < (PRES)((PBYTE)pResAll + cbFileSize));
}

GlobalUnlock(hAllRes);
GlobalFree(hAllRes);

return (gprlHead ? TRUE : FALSE);
}



/************************************************************************
* IsValidResFile
*
* This function does some basic checks on the resource file in memory
* pointed to by pbRes. It does this by walking through the resource
* checking for the resource header info and lengths.
*
* Arguments:
* PRES pRes - Pointer to the first resource in the file.
* INT cbFileSize - Size of the file in memory.
*
* Returns:
* TRUE if it is a valid resource file, FALSE if not.
*
************************************************************************/

STATICFN BOOL IsValidResFile(
PRES pRes,
INT cbFileSize)
{
INT cbCurLoc = 0;
PRES pResT;

/*
* The file is zero size.
*/
if (!cbFileSize)
return FALSE;

pResT = pRes;
while (cbCurLoc < cbFileSize) {
/*
* Check this resource for validity.
*/
if (!(pResT = SafeParseResHeader(pResT, cbFileSize - cbCurLoc)))
return FALSE;

/*
* Point just past the resource that was just checked.
*/
cbCurLoc = (PBYTE)pResT - (PBYTE)pRes;
}

return (cbCurLoc == cbFileSize) ? TRUE : FALSE;
}



/************************************************************************
* SafeParseResHeader
*
* This function parses the specified resource header and returns a
* pointer to the next resource header in the resource file. It does
* it in a safe manner, not touching memory beyond the maximum size
* specified. If the resource header is somehow messed up and
* specifies a size that is larger than will fit in the given maximum
* size, NULL is returned.
*
* Arguments:
* PRES pRes - Pointer to the resource.
* INT cbMaxSize - Maximum size the resource can be.
*
* Returns:
* A pointer to just past this resource, or NULL if the resource
* is larger than cbMaxSize.
*
************************************************************************/

STATICFN PRES SafeParseResHeader(
PRES pRes,
INT cbMaxSize)
{
INT cbLen;
DWORD cbDataSize;
PBYTE pb;

pb = (PBYTE)pRes;

/*
* There must be room for the first part of the resource header.
*/
if (sizeof(RES) > cbMaxSize)
return FALSE;

pb += sizeof(RES);
cbMaxSize -= sizeof(RES);

/*
* Parse the type field then skip over it.
*/
cbLen = SafeNameOrdLen((LPTSTR)pb, cbMaxSize);
if (cbLen > cbMaxSize)
return NULL;

pb += cbLen;
cbMaxSize -= cbLen;

/*
* Parse the name field then skip over it.
*/
cbLen = SafeNameOrdLen((LPTSTR)pb, cbMaxSize);
if (cbLen > cbMaxSize)
return NULL;

pb += cbLen;
cbMaxSize -= cbLen;

SafeDWordAlign(&pb, &cbMaxSize);

/*
* There must be room for the second part of the resource header.
*/
if (sizeof(RES2) > cbMaxSize)
return FALSE;

pb += sizeof(RES2);
cbMaxSize -= sizeof(RES2);

/*
* The header size field must be valid.
*/
if (pRes->HeaderSize != (DWORD)(pb - (PBYTE)pRes))
return FALSE;

/*
* Calculate the size of the data, taking into account any
* padding that may be at the end to make it DWORD aligned.
*/
cbDataSize = pRes->DataSize;
DWordAlign((PBYTE *)&cbDataSize);

/*
* There must be room enough left for the data.
*/
if (cbDataSize > (DWORD)cbMaxSize)
return FALSE;

return (PRES)(pb + cbDataSize);
}



/************************************************************************
* SafeNameOrdLen
*
* This function returns the size of the specified name/ordinal. It
* does it in a safe manner, not touching memory beyond the specified
* maximum size. If it is a string and the terminating null is not
* found within cbMaxLen bytes, then cbMaxLen plus one is returned.
*
* Arguments:
* LPTSTR psz - Pointer to the name/ordinal.
* INT cbMaxLen - Maximum length to probe.
*
* Returns:
* The length of the ordinal if it is an ordinal, or the length
* of the string (plus the null terminator) if it is a string.
*
************************************************************************/

STATICFN INT SafeNameOrdLen(
LPTSTR psz,
INT cbMaxLen)
{
INT cbLen = 0;

if (cbMaxLen == 0)
return 1;

if (IsOrd(psz))
return sizeof(ORDINAL);

for (cbLen = 0; cbLen < cbMaxLen && *psz; psz++, cbLen += sizeof(TCHAR))
;

/*
* Account for the null terminator.
*/
cbLen += sizeof(TCHAR);

return cbLen;
}



/************************************************************************
* SafeDWordAlign
*
* This function aligns the passed pointer to a DWORD boundary. At the
* same time, it subtracts from the specified counter the amount that
* it had to add to the pointer, if any.
*
* Arguments:
* PBYTE *ppb - Points to the pointer to align.
* PINT pcbMax - Points to the current count to decrement.
*
************************************************************************/

STATICFN VOID SafeDWordAlign(
PBYTE *ppb,
PINT pcbMax)
{
INT cbAlign;

cbAlign = (4 - (((WORD)(DWORD)*ppb) & 3)) % 4;
*ppb += cbAlign;
*pcbMax -= cbAlign;
}



/************************************************************************
* WriteRes
*
* Worker routine that does the actual writing out of the resource data.
*
* Arguments:
* HANDLE hfWrite - Resource file to write to.
* LPTSTR pszFullResFile - Full pathname to the resource file that
* is being written.
*
* Returns:
* TRUE if successful; otherwise, FALSE.
*
************************************************************************/

BOOL WriteRes(
HANDLE hfWrite,
LPTSTR pszFullResFile)
{
PRESLINK prl;
PRES pRes;

/*
* Write the special RT_RESOURCE32 dummy resource to the beginning
* of the resource file. This resource is aligned, so no padding
* needs to be done before writing the resource that follows it.
*/
if (_lwrite((HFILE)hfWrite, abResource32, sizeof(abResource32)) == -1)
return FALSE;

/*
* Write out any DLGINCLUDE resource there may be.
*/
if (!WriteDlgIncludeRes(hfWrite, pszFullResFile))
return FALSE;

/*
* Loop through all the resources.
*/
for (prl = gprlHead; prl; prl = prl->prlNext) {
if (!(pRes = (PRES)GlobalLock(prl->hRes)))
return FALSE;

/*
* Write the actual data.
*/
if (_lwrite((HFILE)hfWrite, (LPSTR)pRes, prl->cbRes) == -1)
return FALSE;

/*
* Write pads out to the next DWORD boundary.
*/
if (!WriteDWordPad(hfWrite, prl->cbRes))
return FALSE;

GlobalUnlock(prl->hRes);
}

return TRUE;
}



/************************************************************************
* WriteDlgIncludeRes
*
* Writes out a DLGINCLUDE resource to the specified resource file for
* the currently open include file.
*
* Arguments:
* HANDLE hfWrite - Resource file handle to write to.
* LPTSTR pszFullResFile - Full pathname to the resource file that
* is being written.
*
* Returns:
* Number of characters written if the include resource was
* written successfully (or there wasn't one to write) or -1
* if an error occurred.
*
************************************************************************/

STATICFN BOOL WriteDlgIncludeRes(
HANDLE hfWrite,
LPTSTR pszFullResFile)
{
INT cbResSize;
INT cbDataSize;
PRES pResBegin;
PBYTE pb;
INT cbWritten;
LPTSTR pszInc;
ORDINAL ordDlgIncName;
BOOL fSuccess = FALSE;

/*
* No include file. Do nothing (return success).
*/
if (!pszIncludeFile)
return TRUE;

/*
* If the include file is in a different directory than the resource
* file, write the full path to it. Otherwise, we just write the
* include file name.
*/
if (DifferentDirs(pszFullResFile, szFullIncludeFile))
pszInc = szFullIncludeFile;
else
pszInc = pszIncludeFile;

/*
* The DLGINCLUDE resource name always is the same (a value of 1).
*/
WriteOrd(&ordDlgIncName, ORDID_DLGINCLUDE_NAME);

/*
* Determine the size of the resource data.
*/
cbDataSize = NameOrdLen(pszInc);

/*
* Determine the resource size. Note that there is no need for
* DWORD padding after the res header, because the header will
* be aligned (there are no strings in it).
*/
cbResSize = sizeof(RES) + // First part of res header.
sizeof(ORDINAL) + // Type ordinal.
sizeof(ORDINAL) + // Name ordinal.
sizeof(RES2) + // Second half of header.
cbDataSize; // Size of data.

if (!(pResBegin = (PRES)MyAlloc(cbResSize)))
return FALSE;

/*
* Write the resource header.
*/
pb = WriteResHeader(pResBegin, cbDataSize, ORDID_RT_DLGINCLUDE,
(LPTSTR)&ordDlgIncName, MMF_MOVEABLE | MMF_PURE | MMF_DISCARDABLE,
0, 0, 0, 0);

/*
* Write the resource data. This is simply the name
* of the include file.
*/
NameOrdCpy((LPTSTR)pb, pszInc);

/*
* Write the resource to the file.
*/
cbWritten = _lwrite((HFILE)hfWrite, (LPSTR)pResBegin, cbResSize);

if (cbWritten == cbResSize) {
/*
* Write pads out to the next DWORD boundary.
*/
if (WriteDWordPad(hfWrite, cbWritten))
fSuccess = TRUE;
}

MyFree(pResBegin);

return fSuccess;
}