
/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993-1996 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;
}
