//+---------------------------------------------------------------------------
//
//  File:       rwinf.cpp
//
//  Contents:   Implementation for the Windows NT 3.51 inf Read/Write module
//
//  Classes:
//
//  History:    13-Mar-95   alessanm    created
//
//----------------------------------------------------------------------------

#include "stdafx.h"
#include <afxdllx.h>
#include "inf.h"
#include "..\common\helper.h"

#ifdef _DEBUG
#undef THIS_FILE
static char BASED_CODE THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// General Declarations
#define RWTAG "INF"

#define INF_TYPE        11
#define MAX_INF_TEXT_LINE    55
#define Pad4(x) ((((x+3)>>2)<<2)-x)

typedef struct tagUpdResList
{
    WORD *  pTypeId;
    BYTE *  pTypeName;
    WORD *  pResId;
    BYTE *  pResName;
    DWORD * pLang;
    DWORD * pSize;
    struct tagUpdResList* pNext;
} UPDATEDRESLIST, *PUPDATEDRESLIST;

class CLoadedFile : public CObject
{
public:
    CLoadedFile(LPCSTR lpfilename);

    CInfFile m_infFile;
    CString  m_strFileName;
};

CLoadedFile::CLoadedFile(LPCSTR lpfilename)
{
    TRY
    {
        m_infFile.Open(lpfilename, CFile::modeRead | CFile::shareDenyNone);
    }
    CATCH(CFileException, pfe)
    {
        AfxThrowFileException(pfe->m_cause, pfe->m_lOsError);
    }
    END_CATCH

    m_strFileName = lpfilename;
}


/////////////////////////////////////////////////////////////////////////////
// Function Declarations

LONG
WriteResInfo(
    BYTE** lplpBuffer, LONG* plBufSize,
    WORD wTypeId, LPCSTR lpszTypeId, BYTE bMaxTypeLen,
    WORD wNameId, LPCSTR lpszNameId, BYTE bMaxNameLen,
    DWORD dwLang,
    DWORD dwSize, DWORD dwFileOffset );

CInfFile * LoadFile(LPCSTR lpfilename);

PUPDATEDRESLIST CreateUpdateResList(BYTE * lpBuffer, UINT uiBufSize);
PUPDATEDRESLIST FindId(LPCSTR pstrId, PUPDATEDRESLIST pList);

/////////////////////////////////////////////////////////////////////////////
// Public C interface implementation

CObArray g_LoadedFile;

//[registration]
extern "C"
BOOL    FAR PASCAL RWGetTypeString(LPSTR lpszTypeName)
{
    strcpy( lpszTypeName, RWTAG );
    return FALSE;
}

extern "C"
BOOL    FAR PASCAL RWValidateFileType(LPCSTR lpszFilename)
{
    TRACE("RWINF.DLL: RWValidateFileType()\n");

    // Check file exstension and try to open it
    if(strstr(lpszFilename, ".INF")!=NULL || strstr(lpszFilename, ".inf")!=NULL)
        return TRUE;

    return FALSE;
}

extern "C"
DllExport
UINT
APIENTRY
RWReadTypeInfo(
    LPCSTR lpszFilename,
    LPVOID lpBuffer,
    UINT* puiSize
    )
{
    TRACE("RWINF.DLL: RWReadTypeInfo()\n");
    UINT uiError = ERROR_NO_ERROR;

    if (!RWValidateFileType(lpszFilename))
        return ERROR_RW_INVALID_FILE;
    //
    // Open the file
    //
    CInfFile * pinfFile;
    TRY
    {
        pinfFile = LoadFile(lpszFilename);
    }
    CATCH(CFileException, pfe)
    {
        return pfe->m_cause + IODLL_LAST_ERROR;
    }
    END_CATCH

    //
    // Read the data and fill the iodll buffer
    //
    // Get to the beginning of the localization section
    //
    if(!pinfFile->SeekToLocalize())
        return ERROR_RW_NO_RESOURCES;

    CString strSection;
    CString strLine;
    CString strTag;
    CInfLine infLine;

    BYTE ** pBuf = (BYTE**)&lpBuffer;
    LONG lBufSize = 0;

    while(pinfFile->ReadTextSection(strSection))
    {
        while(pinfFile->ReadSectionString(infLine))
        {
            strTag = strSection + '.' + infLine.GetTag();
            lBufSize += WriteResInfo(
                 pBuf, (LONG*)puiSize,
                 INF_TYPE, "", 0,
                 0, strTag, 255,
                 0l,
                 infLine.GetTextLength()+1, pinfFile->GetLastFilePos() );
        }
    }

    *puiSize = lBufSize;

    return uiError;
}

extern "C"
DllExport
DWORD
APIENTRY
RWGetImage(
    LPCSTR  lpszFilename,
    DWORD   dwImageOffset,
    LPVOID  lpBuffer,
    DWORD   dwSize
    )
{
    UINT uiError = ERROR_NO_ERROR;

    //
    // Open the file
    //
    CInfFile * pinfFile;
    TRY
    {
        pinfFile = LoadFile(lpszFilename);

    }
    CATCH(CFileException, pfe)
    {
        return pfe->m_cause + IODLL_LAST_ERROR;
    }
    END_CATCH

    //
    // Seek to the string to retrieve and read it
    //
    CInfLine infLine;

    pinfFile->Seek( dwImageOffset, SEEK_SET );
    pinfFile->ReadSectionString(infLine);

    //
    // Fill the buffer with the string
    //
    if(infLine.GetTextLength()+1<=(LONG)dwSize)
    {
        memcpy(lpBuffer, infLine.GetText(), infLine.GetTextLength()+1);
        uiError = infLine.GetTextLength()+1;
    }
    else
        uiError = 0;

    return (DWORD)uiError;
}

extern "C"
DllExport
UINT
APIENTRY
RWParseImage(
    LPCSTR  lpszType,
    LPVOID  lpImageBuf,
    DWORD   dwImageSize,
    LPVOID  lpBuffer,
    DWORD   dwSize
    )
{
    UINT uiSizeOfDataStruct = strlen((LPCSTR)lpImageBuf)+sizeof(RESITEM);

    if(uiSizeOfDataStruct<=dwSize)
    {
        //
        // We have to fill the RESITEM Struct
        //
        LPRESITEM pResItem = (LPRESITEM)lpBuffer;
        memset(pResItem, '\0', uiSizeOfDataStruct);

        pResItem->dwSize = uiSizeOfDataStruct;
        pResItem->lpszCaption = (LPSTR)memcpy( ((BYTE*)pResItem)+sizeof(RESITEM), lpImageBuf, dwImageSize);        // Caption
    }

    return uiSizeOfDataStruct;
}

extern"C"
DllExport
UINT
APIENTRY
RWWriteFile(
    LPCSTR          lpszSrcFilename,
    LPCSTR          lpszTgtFilename,
    HANDLE          hResFileModule,
    LPVOID          lpBuffer,
    UINT            uiSize,
    HINSTANCE       hDllInst,
    LPCSTR          lpszSymbolPath
    )
{
    UINT uiError = ERROR_NO_ERROR;

    // Get the handle to the IODLL
    hDllInst = LoadLibrary("iodll.dll");
    if (!hDllInst)
        return ERROR_DLL_LOAD;

    DWORD (FAR PASCAL * lpfnGetImage)(HANDLE, LPCSTR, LPCSTR, DWORD, LPVOID, DWORD);
    // Get the pointer to the function to extract the resources image
    lpfnGetImage = (DWORD (FAR PASCAL *)(HANDLE, LPCSTR, LPCSTR, DWORD, LPVOID, DWORD))
                        GetProcAddress( hDllInst, "RSGetResImage" );
    if (lpfnGetImage==NULL) {
        FreeLibrary(hDllInst);
        return ERROR_DLL_LOAD;
    }

    //
    // Get the handle to the source file
    //
    CInfFile * psrcinfFile;
    TRY
    {
        psrcinfFile = LoadFile(lpszSrcFilename);

    }
    CATCH(CFileException, pfe)
    {
        return pfe->m_cause + IODLL_LAST_ERROR;
    }
    END_CATCH

    //
    // Create the target file
    //
    CFile tgtFile;
    CFileException fe;
    if(!tgtFile.Open(lpszTgtFilename, CFile::modeCreate | CFile::modeReadWrite | CFile::shareDenyNone, &fe))
    {
        return fe.m_cause + IODLL_LAST_ERROR;
    }

    //
    // Copy the part of the file that is not localizable
    //
    LONG lLocalize = psrcinfFile->SeekToLocalize();
    const BYTE * pStart = psrcinfFile->GetBuffer();

    if(lLocalize==-1)
    {
        // the file has no localizable info  in it just copy it
        lLocalize = psrcinfFile->SeekToEnd();
    }

    TRY
    {
        tgtFile.Write(pStart, lLocalize);
    }
    CATCH(CFileException, pfe)
    {
        return pfe->m_cause + IODLL_LAST_ERROR;
    }
    END_CATCH

    //
    // Create the list of updated resources
    //
    PUPDATEDRESLIST pResList = CreateUpdateResList((BYTE*)lpBuffer, uiSize);

    //
    // What we have now is a part that is mized. Part of it has localizable
    // information and part has none.
    // We will read each section and decide if is a localizable section or not.
    // If it is we will update it otherwise just copy it
    //
    CString strSection, str;
    CString strLang = psrcinfFile->GetLanguage();
    LONG lEndPos, lStartPos;
    CInfLine infLine;

    while(psrcinfFile->ReadSection(strSection))
    {
        TRY
        {
            tgtFile.Write(strSection, strSection.GetLength());
            tgtFile.Write("\r\n", 2);
        }
        CATCH(CFileException, pfe)
        {
            return pfe->m_cause + IODLL_LAST_ERROR;
        }
        END_CATCH

        if(strSection.Find(strLang)==-1)
        {
            //
            // This is not a localizable section
            //
            lStartPos = psrcinfFile->Seek(0, SEEK_CUR);

            //
            // Read the next section untill we find a localizable section
            //
            while(psrcinfFile->ReadSection(strSection))
            {
                if(strSection.Find(strLang)!=-1)
                    break;
            }

            //
            // Where are we now?
            //

            lEndPos = psrcinfFile->Seek(0, SEEK_CUR) - strSection.GetLength()-2;

            //
            // Make sure we are not at the end of the file
            //
            if(lEndPos<=lStartPos)
            {
                // we have no more section so copy all is left
                lEndPos = psrcinfFile->Seek(0, SEEK_END) - 1;
            }

            //
            // copy the full block
            //

            pStart = psrcinfFile->GetBuffer(lStartPos);
            TRY
            {
                tgtFile.Write(pStart, lEndPos-lStartPos);
            }
            CATCH(CFileException, pfe)
            {
                return pfe->m_cause + IODLL_LAST_ERROR;
            }
            END_CATCH

            psrcinfFile->Seek(lEndPos, SEEK_SET);
        }
        else
        {
            //
            // This is a localizable section
            // Read all the strings and see if they have been updated
            //
            CString strId;
            PUPDATEDRESLIST pListItem;
            BYTE * pByte;

            lEndPos = psrcinfFile->Seek(0, SEEK_CUR);

            while(psrcinfFile->ReadSectionString(str))
            {
                str += "\r\n";

                infLine = str;

                //
                // Check if we need to update this string
                //
                strId = strSection + "." + infLine.GetTag();

                if(pListItem = FindId(strId, pResList))
                {
                    // allocate the buffer to hold the resource data
                    pByte = new BYTE[*pListItem->pSize];
                    if(!pByte){
                        uiError = ERROR_NEW_FAILED;
                        goto exit;
                    }

                    // get the data from the iodll
                    LPSTR	lpType = NULL;
        			LPSTR	lpRes = NULL;
        			if (*pListItem->pTypeId) {
        				lpType = (LPSTR)((WORD)*pListItem->pTypeId);
        			} else {
        				lpType = (LPSTR)pListItem->pTypeName;
        			}
        			if (*pListItem->pResId) {
        				lpRes = (LPSTR)((WORD)*pListItem->pResId);
        			} else {
        				lpRes = (LPSTR)pListItem->pResName;
        			}

        			DWORD dwImageBufSize = (*lpfnGetImage)(  hResFileModule,
        											lpType,
        											lpRes,
        											*pListItem->pLang,
        											pByte,
        											*pListItem->pSize
        						   					);

                    if(dwImageBufSize!=*pListItem->pSize)
                    {
                        // something is wrong...
                        delete []pByte;
                    }
                    else {

                        infLine.ChangeText((LPCSTR)pByte);

                        //
                        // Now we have the updated image...
                        //

                        //
                        // Check how long is the Data and split it in to lines
                        //
                        if(infLine.GetTextLength()>MAX_INF_TEXT_LINE)
                        {
                            //
                            // First write the tag
                            //
                            str = infLine.GetData();
                            int iSpaceLen = str.Find('=')+1;
                            int iTagLen = 0;

                            TRY
                            {
                                tgtFile.Write(str, iSpaceLen);
                            }
                            CATCH(CFileException, pfe)
                            {
                                return pfe->m_cause + IODLL_LAST_ERROR;
                            }
                            END_CATCH

                            //
                            // Now write the rest
                            //
                            int iExtra, iMaxStr;
                            CString strLine;
                            CString strSpace( ' ', iSpaceLen+1 );
                            BOOL bFirstLine = TRUE;

                            strSpace += '\"';
                            str = infLine.GetText();
                            str.TrimLeft();

                            while(str.GetLength()>MAX_INF_TEXT_LINE)
                            {
                                iMaxStr = str.GetLength();

                                strLine = str.Left(MAX_INF_TEXT_LINE);

                                //
                                // Check if we are in the middle of a word
                                //
                                iExtra = 0;
                                while((iMaxStr>MAX_INF_TEXT_LINE+iExtra) && str.GetAt(MAX_INF_TEXT_LINE+iExtra)!=' ')
                                {
                                    strLine += str.GetAt(MAX_INF_TEXT_LINE+iExtra++);
                                }

                                //
                                // Make sure the spaces are the last thing
                                //
                                while((iMaxStr>MAX_INF_TEXT_LINE+iExtra) && str.GetAt(MAX_INF_TEXT_LINE+iExtra)==' ')
                                {
                                    strLine += str.GetAt(MAX_INF_TEXT_LINE+iExtra++);
                                }

                                str = str.Mid(MAX_INF_TEXT_LINE+iExtra);
                                if(str.IsEmpty())
                                {
                                    //
                                    // This string is all done write it as is, we can't break it
                                    //
                                    strLine += "\r\n";
                                }
                                else strLine += "\"+\r\n";

                                if(bFirstLine)
                                {
                                    strLine = " " + strLine;
                                    bFirstLine = FALSE;

                                } else
                                {
                                    strLine = strSpace + strLine;
                                }

                                TRY
                                {
                                    tgtFile.Write(strLine, strLine.GetLength());
                                }
                                CATCH(CFileException, pfe)
                                {
                                    return pfe->m_cause + IODLL_LAST_ERROR;
                                }
                                END_CATCH

                                //str = str.Mid(MAX_INF_TEXT_LINE+iExtra);
                            }

                            if(bFirstLine)
                            {
                                strLine = " " + str;
                            } else
                            {
                                if(!str.IsEmpty())
                                    strLine = strSpace + str;
                                else strLine = "";
                            }

                            if(!strLine.IsEmpty())
                            {
                                TRY
                                {
                                    tgtFile.Write(strLine, strLine.GetLength());
                                    tgtFile.Write("\r\n", 2);
                                }
                                CATCH(CFileException, pfe)
                                {
                                    return pfe->m_cause + IODLL_LAST_ERROR;
                                }
                                END_CATCH
                            }
                        }
                        else
                        {
                            TRY
                            {
                                tgtFile.Write(infLine.GetData(), infLine.GetDataLength());
                                tgtFile.Write("\r\n", 2);
                            }
                            CATCH(CFileException, pfe)
                            {
                                return pfe->m_cause + IODLL_LAST_ERROR;
                            }
                            END_CATCH
                        }

                        delete []pByte;
                    }
                }
                else
                {
                    TRY
                    {
                        tgtFile.Write(infLine.GetData(), infLine.GetDataLength());
                    }
                    CATCH(CFileException, pfe)
                    {
                        return pfe->m_cause + IODLL_LAST_ERROR;
                    }
                    END_CATCH
                }

                lEndPos = psrcinfFile->Seek(0, SEEK_CUR);
            }
        }
    }

exit:
    tgtFile.Close();

    if(pResList)
        delete []pResList;

    return uiError;
}

extern "C"
DllExport
UINT
APIENTRY
RWUpdateImage(
    LPCSTR  lpszType,
    LPVOID  lpNewBuf,
    DWORD   dwNewSize,
    LPVOID  lpOldImage,
    DWORD   dwOldImageSize,
    LPVOID  lpNewImage,
    DWORD*  pdwNewImageSize
    )
{
    UINT uiError = ERROR_NO_ERROR;

    //
    // Get the new string
    //
    LPCSTR lpNewStr = (LPCSTR)(((LPRESITEM)lpNewBuf)->lpszCaption);

    //
    // Copy the string in the new image buffer
    //

    int iLen = strlen(lpNewStr)+1;
    if(iLen<=(LONG)*pdwNewImageSize)
    {
        memcpy(lpNewImage, lpNewStr, iLen);
    }

    *pdwNewImageSize = iLen;

    return uiError;
}

///////////////////////////////////////////////////////////////////////////
// Functions implementation

//=============================================================================
//  WriteResInfo
//
//  Fill the buffer to pass back to the iodll
//=============================================================================

LONG WriteResInfo(
    BYTE** lplpBuffer, LONG* plBufSize,
    WORD wTypeId, LPCSTR lpszTypeId, BYTE bMaxTypeLen,
    WORD wNameId, LPCSTR lpszNameId, BYTE bMaxNameLen,
    DWORD dwLang,
    DWORD dwSize, DWORD dwFileOffset )
{
    LONG lSize = 0;
    lSize = PutWord( lplpBuffer, wTypeId, plBufSize );
    lSize += PutStringA( lplpBuffer, (LPSTR)lpszTypeId, plBufSize );   // Note: PutStringA should get LPCSTR and not LPSTR
    lSize += Allign( lplpBuffer, plBufSize, lSize);

    lSize += PutWord( lplpBuffer, wNameId, plBufSize );
    lSize += PutStringA( lplpBuffer, (LPSTR)lpszNameId, plBufSize );
    lSize += Allign( lplpBuffer, plBufSize, lSize);

    lSize += PutDWord( lplpBuffer, dwLang, plBufSize );
    lSize += PutDWord( lplpBuffer, dwSize, plBufSize );
    lSize += PutDWord( lplpBuffer, dwFileOffset, plBufSize );

    return (LONG)lSize;
}

CInfFile * LoadFile(LPCSTR lpfilename)
{
    // Check if we have loaded the file before
    int c = (int)g_LoadedFile.GetSize();
    CLoadedFile * pLoaded;
    while(c)
    {
        pLoaded = (CLoadedFile*)g_LoadedFile.GetAt(--c);
        if(pLoaded->m_strFileName==lpfilename)
            return &pLoaded->m_infFile;
    }

    // The file need to be added to the list
    pLoaded = new CLoadedFile(lpfilename);

    g_LoadedFile.Add((CObject*)pLoaded);

    return &pLoaded->m_infFile;
}


PUPDATEDRESLIST CreateUpdateResList(BYTE * lpBuffer, UINT uiBufSize)
{
    //
    // Walk the buffer and count how many resources we have
    //
    int iResCount = 0;
    int iBufSize = uiBufSize;
    int iResSize = 0;
    BYTE * pBuf = lpBuffer;
    while(iBufSize>0)
    {
        iResSize = 2;
        iResSize += strlen((LPSTR)(pBuf+iResSize))+1;
        iResSize += Pad4(iResSize);

        iResSize += 2;
        iResSize += strlen((LPSTR)(pBuf+iResSize))+1;
        iResSize += Pad4(iResSize);

        iResSize += 4*2;

        if(iResSize<=iBufSize)
        {
            iBufSize -= iResSize;
            pBuf = pBuf + iResSize;
            iResCount++;
        }
    }

    //
    // Allocate the buffer that will hold the list
    //
    if(!iResCount)
        return NULL;

    pBuf = lpBuffer;
    iBufSize = uiBufSize;

    PUPDATEDRESLIST pListHead = new UPDATEDRESLIST[iResCount];

    if(pListHead==NULL)
        AfxThrowMemoryException();

    memset(pListHead, 0, sizeof(UPDATEDRESLIST)*iResCount);

    PUPDATEDRESLIST pList = pListHead;
    BYTE bPad = 0;
    WORD wSize = 0;
    while(iBufSize>0) {
        pList->pTypeId = (WORD*)pBuf;
        pList->pTypeName = (BYTE*)pList->pTypeId+sizeof(WORD);
        // check the allignement
        bPad = strlen((LPSTR)pList->pTypeName)+1+sizeof(WORD);
        bPad += Pad4(bPad);
        wSize = bPad;
        pList->pResId = (WORD*)((BYTE*)pBuf+bPad);
        pList->pResName = (BYTE*)pList->pResId+sizeof(WORD);
        bPad = strlen((LPSTR)pList->pResName)+1+sizeof(WORD);
        bPad += Pad4(bPad);
        wSize += bPad;
        pList->pLang = (DWORD*)((BYTE*)pList->pResId+bPad);
        pList->pSize = (DWORD*)((BYTE*)pList->pLang+sizeof(DWORD));
        pList->pNext = (PUPDATEDRESLIST)pList+1;
        wSize += sizeof(DWORD)*2;
        pBuf = pBuf+wSize;
        iBufSize -= wSize;
        if(!iBufSize)
            pList->pNext = NULL;
        else
            pList++;
    }

    return pListHead;
}

PUPDATEDRESLIST FindId(LPCSTR pstrId, PUPDATEDRESLIST pList)
{
    //
    // Note that this function assumes that the type is always right
    // since it is a inf file this is a fair assumption.
    // It could be optimized.
    //
    if(!pList)
        return NULL;

    PUPDATEDRESLIST pLast = pList;
    while(pList)
    {
        if(!strcmp((LPSTR)pList->pResName, pstrId)) {
                return pList;
        }
        pList = pList->pNext;
    }

    return NULL;
}

////////////////////////////////////////////////////////////////////////////
// DLL Specific code implementation
////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////
// Library init
static AFX_EXTENSION_MODULE rwinfDLL = { NULL, NULL };

extern "C" int APIENTRY
DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        TRACE0("RWINF.DLL Initializing!\n");

        AfxInitExtensionModule(rwinfDLL, hInstance);

        new CDynLinkLibrary(rwinfDLL);

    }
    else if (dwReason == DLL_PROCESS_DETACH)
    {
        TRACE0("RWINF.DLL Terminating!\n");

        // free all the loaded files
        int c = (int)g_LoadedFile.GetSize();
        CLoadedFile * pLoaded;
        while(c)
        {
            pLoaded = (CLoadedFile*)g_LoadedFile.GetAt(--c);
            delete pLoaded;
        }
    }
    return 1;
}