6
\$\begingroup\$

My application compiles and seems to work correctly, but this is my first C project so I welcome any criticism. I developed this in Pelles C with no standard lib. Efficiency and optimizations are more important to me than readability or conformity. The parts that I'm particularly concerned about are marked with comments including ~~~~~~~~.

The purpose of this program is to run another program somewhere else. Let's say, for example, that you want to be able to run LibreOffice from the run dialog or command line. Your options are to add the LibreOffice folder to your path, install LibreOffice to a folder in your path (really bad idea), or use some other trick like putting a bat file in your path that will run it. With Wormhole, you could copy wormhole.exe to somewhere in your path, rename it to libreoffice.exe, rename wormhole.ini to libreoffice.ini, and edit the ini file to point to the original libreoffice.exe. Now if you run libreoffice.exe, the shim will run libreoffice from its original location.

/*
| Wormhole - A windows shim app
|
| Wormhole.c
|
| Copyright 2015 by Tim Byles / x79
| All Rights Reserved
|
| This software is distributed under the GPLv3
| The full text of the license can be found here
|   http://www.gnu.org/licenses/gpl.txt
*/


#include <windows.h>
#include <strsafe.h>

#define WIN32_LEAN_AND_MEAN
#pragma comment(linker,"/merge:.rdata=.data")
#pragma comment(linker,"/merge:.text=.data")
#pragma comment(linker,"/merge:.reloc=.data")

//#define debugme

//wchar_t * myitow(int __value, wchar_t *__string, int __radix);
//WINBASEAPI HMODULE WINAPI GetProcAddressW(HMODULE, LPCWSTR);

int main(void) {
    // Disable Wow64 Redirection
    // Found this trick at some google result
    // We declare the api function as a pointer so the app doesn't go looking for the entry point until we need it
    typedef BOOL WINAPI fn64();
    fn64 *pfn64 = (fn64*)GetProcAddress(GetModuleHandleA("kernel32.dll"),
     "Wow64DisableWow64FsRedirection");
    if (pfn64)
       (*pfn64)(NULL);


    // These are used all the way through
    unsigned long lWait = 0;
    unsigned long lPriority = 0;
    HANDLE pMain = GetProcessHeap();


    // Start with a buffer of 32 characters and increment by 8 until we have enough room for the module name
    unsigned long iTmp = 32ul;
    unsigned long iFileNameLen = 0ul;
    wchar_t *sIniPath = HeapAlloc(pMain, 8ul, iTmp*4ul);
    while (sIniPath) {
        iFileNameLen = GetModuleFileNameW(0, sIniPath, iTmp);
        // GetLastError() didn't work correctly for me on XP
        if (iFileNameLen < iTmp)    // Number of characters copied to buffer is less than buffer
            break;
        iTmp += 8;
        sIniPath = HeapReAlloc(pMain, 8ul, sIniPath, iTmp*4ul);
    }

    if (!iFileNameLen)
        MessageBoxW(NULL,L"Error getting EXE name.",L"Error",0x2030u);
    // Now replace .exe with .ini
    sIniPath[iFileNameLen-3u] = L'i'; sIniPath[iFileNameLen-2u] = L'n'; sIniPath[iFileNameLen-1u] = L'i';

    // Open the Ini file
    HANDLE fIni = CreateFileW(sIniPath, GENERIC_READ,
        FILE_SHARE_READ, NULL, OPEN_EXISTING, 0ul, NULL);

    // We don't need sIniPath anymore
    HeapFree(pMain, 0ul, sIniPath);

    // If the ini couldn't be opened, show an error and quit
    if (fIni == INVALID_HANDLE_VALUE) {
        wchar_t sErr[256];
        StringCchCopyW(sErr, 256, L"Couldn't open ini file.\r\n");
        StringCchCatW(sErr, 256, sIniPath);
        MessageBoxW(NULL, sErr, L"ERROR", 0x2030u);
        return 1;
    }

    // Get file size and quit if too small
    unsigned long iSize = SetFilePointer(fIni, 0l, NULL, FILE_END);
    if (iSize < 6ul) {
        CloseHandle(fIni);
        MessageBoxW(NULL, L"Ini file too small.", L"ERROR", 0x2030u);
        return 1;
    }

    // File length is acceptable, reset pointer
    SetFilePointer(fIni, 0l, NULL, FILE_BEGIN);

    // Check for BOM. We support UCS-LE, UCS-BE and UTF-8.
    // The BOM will be either 2 or 3 bytes
    wchar_t sBOM[2] = L"\0";
    unsigned long iBytesRead = -1ul;
    unsigned long iReturn = ReadFile(fIni, sBOM, 3ul, &iBytesRead, NULL);

    /* ~~~~~~~~  Is this even is necessary?  ~~~~~~~~ */
    if (!iReturn || iBytesRead != 3ul) {
        CloseHandle(fIni);
        MessageBoxW(NULL, L"Error reading BOM.", L"ERROR", 0x2030u);
        return 1;
    }

    // sText main string used for parsing the text
    wchar_t *sText = 0;

    // If the text is UTF-16 then we can read it directly into the sText buffer
    if (*sBOM == 0xFEFF || *sBOM == 0xFFFE) {           // UCS-LE or UCS-BE
        sText = HeapAlloc(pMain, 8ul, (iSize+2)*4);     // Allocate an extra slot here for the byte-swap routine
        *sText = L'\0';
        SetFilePointer(fIni, 2ul, NULL, FILE_BEGIN);
        iReturn = ReadFile(fIni, sText, iSize, &iSize, NULL);
        CloseHandle(fIni);
        if (!iReturn) {
            MessageBoxW(NULL, L"Error reading ini file.", L"ERROR", 0x2030u);
            return 1;
        }

        // The Readfile function adds only one where we need two
        //  so we make sure the last character is null
        sText[iSize-1] = L'\0';     // We added that extra slot so the terminator goes to iSize-1

        // If BE, then swap the byte order
        // With this little trick I devised, we only need to move half of the bytes in the string
        // What happens is we leapfrog the odd bytes forward by two bytes
        //  then we increment the pointer by one byte (half of a wchar_t)
        if (*sBOM == 0xFFFE) {
            sText++;        // Without this the loop was stepping back too far
            char *cPtr = ((char *)sText) + iSize;
            // We could use iSize to calculate the number of loops needed but
            //  any gain is probably negligible to non-existent
            while (cPtr > (char *)sText) {
                cPtr -= 2;
                *cPtr = *(cPtr-2);
            }
            sText = (wchar_t *)(cPtr - 1);      // Set sText to the new location
        }
    }
    else {      // UTF-8 or ANSI
        unsigned iBOM = (*sBOM == 0xBBEF && *(sBOM+1) == 0xBF) ? 3ul : 0ul;
        unsigned uSourceCP = (iBOM) ? CP_UTF8 : CP_ACP;
        char sBuffer[iSize+1];                              // Create a buffer the size of the text file + 1

        SetFilePointer(fIni, iBOM, NULL, FILE_BEGIN);
        iReturn = ReadFile(fIni, sBuffer, iSize, &iSize, NULL);     // Read the entire file in one sequence
        CloseHandle(fIni);

        /* ~~~~~~~~  Is this even is necessary?  ~~~~~~~~ */
        sBuffer[iSize] = '\0';

        if (!iReturn) {
            MessageBoxW(NULL, L"Error reading ini file.", L"ERROR", 0x2030u);
            return 1;
        }

        // Get the size needed for sText
        unsigned iNewSize = MultiByteToWideChar(uSourceCP, 0UL, sBuffer, -1, sText, 0);
        if (!iNewSize) {
            if (iBOM)
                MessageBoxW(NULL, L"Error trying to understand UTF-8 input file", L"ERROR", 0x2030u);
            else
                MessageBoxW(NULL, L"Error trying to understand ANSI input file", L"ERROR", 0x2030u);
        }

        // Convert ANSI or UTF-8 to wchar_t
        sText = HeapAlloc(pMain, 8ul, iNewSize*4);
        MultiByteToWideChar(uSourceCP, 0UL, sBuffer, -1, sText, iNewSize);
    }

    // Now we should have a valid UCS-16 LE string in sText
    SHELLEXECUTEINFOW rInfo = {0};
    rInfo.cbSize = 60;
    rInfo.nShow = 1;


    // Create pointers used to parse the text
    wchar_t *sStartPtr;
    wchar_t *sEndPtr = sText;

    // Parse loop
    while (*sEndPtr)
    {
        // Skip any leading \r or \n on this line
        // This allows us to begin a file with \r\n and 
        //  skip trying to parse blank lines
        while (*sEndPtr == L'\r' || *sEndPtr == L'\n')
            sEndPtr++;

        // Catch sStart up to sEnd
        sStartPtr = sEndPtr;

        // Find the end of the line
        while (*sEndPtr++)
            if (*sEndPtr == L'\r' || *sEndPtr == L'\n')
                break;

        // If any line is less than 5 characters, we can't use it
        if ((sEndPtr - sStartPtr) < 5)  //Rewrite this!!!!!!!!!!!
            continue;

        // Here is the meat of the parsing loop
        switch (*sStartPtr)
        {
            // We could use the WINAPI string compare but for the 
            //  difference of a few bytes, I would argue this is faster
            case L'#' :     // Ignore lines that begin with #
                break;
            case L'C' :     // cmd
            case L'c' :
                if ((*(sStartPtr+1) == L'M' || *(sStartPtr+1) == L'm') &&
                    (*(sStartPtr+2) == L'D' || *(sStartPtr+2) == L'd'))
                {
                    sStartPtr += 3;
                    // skip past any =, space, or tab
                    while (*sStartPtr == L'=' || *sStartPtr == 0x20 || *sStartPtr == 0x9)
                        sStartPtr++;
                    unsigned iCmdLen = (sEndPtr-sStartPtr)+1;

                    // ~~~~~~~~  I'm not sure how to properly handle this  ~~~~~~~~
                    //  I'm pretty sure I need to free the memory but if I do it in this loop
                    //  it messes up rInfo's pointer to it
                    wchar_t *sCmdTmp = HeapAlloc(pMain, 8ul, (iCmdLen)*4);
                    StringCchCopyW(sCmdTmp, iCmdLen, sStartPtr);
                    rInfo.lpFile = sCmdTmp;
                }
                break;
            case L'A' :     // args
            case L'a' :
                if ((*(sStartPtr+1) == L'R' || *(sStartPtr+1) == L'r') &&
                    (*(sStartPtr+2) == L'G' || *(sStartPtr+2) == L'g') &&
                    (*(sStartPtr+3) == L'S' || *(sStartPtr+3) == L's'))
                {
                    sStartPtr += 4;
                    while (*sStartPtr == L'=' || *sStartPtr == 0x20 || *sStartPtr == 0x9)
                        sStartPtr++;
                    unsigned iArgsLen = (sEndPtr-sStartPtr)+1;

                    /* ~~~~~~~~  I have the same issue here as above  ~~~~~~~~ */
                    wchar_t *sArgs = HeapAlloc(pMain, 8ul, iArgsLen*4);
                    *sArgs = L'\0';

                    // Look for %s
                    wchar_t *sArgsTmp = sStartPtr;
                    while (sArgsTmp != sEndPtr)
                    {
                        if (*sArgsTmp == L'%')
                            if (*(sArgsTmp+1) == L'S' || *(sArgsTmp+1) == L's')
                                break;
                        sArgsTmp++;
                    }

                    // If we found %s
                    if (sArgsTmp < sEndPtr)
                    {
                        // Get command line and skip past module name
                        wchar_t *sCli = GetCommandLineW();

                        // Skip past module name
                        // First check for "
                        if (*sCli == 0x22)
                        {
                            // If found then skip to the second "
                            sCli++;
                            while (*sCli++ != 0x22)
                                continue;
                        }

                        // Now skip any space or tab
                        while (*sCli == 0x20 || *sCli == 0x9)
                            sCli++;

                        // If there is any Cli left, replace %s with it
                        if (*sCli)
                        {
                            // It took me a while to get this working correctly
                            // I hope someone appreciates it :-p
                            wchar_t *sCliEnd = sCli;
                            unsigned iNewLen = iArgsLen - 2;        // subtract 2 because we removed %s
                            while (*sCliEnd++)  
                                iNewLen++;

                            // Resize sArgs to accomodate the replacement
                            if (iNewLen > iArgsLen)     // No point in rellocating if we replace %s with 1 or 2 chars
                                sArgs = HeapReAlloc(pMain, 8ul, sArgs, iArgsLen*4);

                            // If sArgs didn't start with %s, copy the left part of the original string
                            if (sArgsTmp > sStartPtr)
                                StringCchCatW(sArgs, (sArgsTmp-sStartPtr)+1, sStartPtr);

                            // Now concat the Cli
                            StringCchCatW(sArgs, iArgsLen, sCli);
                            // And finally the right part of the original string
                            StringCchCatW(sArgs, iArgsLen, (sArgsTmp+2));
                        }
                    }
                    else    // No %s present
                    {
                        StringCchCopyW(sArgs, iArgsLen, sStartPtr);

                    }
                    rInfo.lpParameters = sArgs;
                }
                break;
            case L'W' :     // wait or workingdir
            case L'w' :
                if ((*(sStartPtr+1) == L'A' || *(sStartPtr+1) == L'a') &&
                    (*(sStartPtr+2) == L'I' || *(sStartPtr+2) == L'i') &&
                    (*(sStartPtr+3) == L'T' || *(sStartPtr+3) == L't'))
                {   // wait
                    sStartPtr += 4;
                    while (*sStartPtr == L'=' || *sStartPtr == 0x20 || *sStartPtr == 0x9)
                        sStartPtr++;
                    if (*sStartPtr == L'1')
                        lWait = SEE_MASK_NOCLOSEPROCESS;
                }
                else if ((*(sStartPtr+1) == L'O' || *(sStartPtr+1) == L'o') &&
                    (*(sStartPtr+2) == L'R' || *(sStartPtr+2) == L'r') &&
                    (*(sStartPtr+3) == L'K' || *(sStartPtr+3) == L'k') &&
                    (*(sStartPtr+4) == L'I' || *(sStartPtr+4) == L'i') &&
                    (*(sStartPtr+5) == L'N' || *(sStartPtr+5) == L'n') &&
                    (*(sStartPtr+6) == L'G' || *(sStartPtr+6) == L'g') &&
                    (*(sStartPtr+7) == L'D' || *(sStartPtr+7) == L'd') &&
                    (*(sStartPtr+8) == L'I' || *(sStartPtr+8) == L'i') &&
                    (*(sStartPtr+9) == L'R' || *(sStartPtr+9) == L'r'))
                {   // workingdir
                    sStartPtr += 10;
                    while (*sStartPtr == L'=' || *sStartPtr == 0x20 || *sStartPtr == 0x9)
                        sStartPtr++;
                    unsigned iDirLen = (sEndPtr-sStartPtr)+1;
                    /* ~~~~~~~~  Here we are again  ~~~~~~~~ */
                    wchar_t *sDir = HeapAlloc(pMain, 8ul, iDirLen*4);
                    *sDir = L'\0';
                    StringCchCatW(sDir, iDirLen, sStartPtr);
                    rInfo.lpDirectory = sDir;
                }
                break;
            case L'S' :     // ShowCmd
            case L's' :
                if ((*(sStartPtr+1) == L'H' || *(sStartPtr+1) == L'h') &&
                    (*(sStartPtr+2) == L'O' || *(sStartPtr+2) == L'o') &&
                    (*(sStartPtr+3) == L'W' || *(sStartPtr+3) == L'w') &&
                    (*(sStartPtr+4) == L'C' || *(sStartPtr+4) == L'c') &&
                    (*(sStartPtr+5) == L'M' || *(sStartPtr+5) == L'm') &&
                    (*(sStartPtr+6) == L'D' || *(sStartPtr+6) == L'd'))
                {
                    sStartPtr += 7;
                    while (*sStartPtr == L'=' || *sStartPtr == 0x20 || *sStartPtr == 0x9)
                        sStartPtr++;
                    switch (*sStartPtr)
                    {
                        case L'0' :
                        case L'2' ... L'9' :
                            rInfo.nShow = *sStartPtr - 0x30;    // This technique only works for 1-9
                        case L'1' :
                            if (*(sStartPtr+1) == L'0')
                                rInfo.nShow = 10;
                            //else                          // This is redundant
                                //rInfo.nShow = 1;
                    }
                }
                break;
            case L'P' :         // priority
            case L'p' :
                if ((*(sStartPtr+1) == L'R' || *(sStartPtr+1) == L'r') &&
                    (*(sStartPtr+2) == L'I' || *(sStartPtr+2) == L'i') &&
                    (*(sStartPtr+3) == L'O' || *(sStartPtr+3) == L'o') &&
                    (*(sStartPtr+4) == L'R' || *(sStartPtr+4) == L'r') &&
                    (*(sStartPtr+5) == L'I' || *(sStartPtr+5) == L'i') &&
                    (*(sStartPtr+6) == L'T' || *(sStartPtr+6) == L't') &&
                    (*(sStartPtr+7) == L'Y' || *(sStartPtr+7) == L'y'))
                {
                    sStartPtr += 8;
                    while (*sStartPtr == L'=' || *sStartPtr == 0x20 || *sStartPtr == 0x9)
                        sStartPtr++;
                    switch ((*sStartPtr) - 0x30)    // This technique would only work for 1-9
                    {
                        case 1 :        // Idle
                            lPriority = IDLE_PRIORITY_CLASS;
                            break;
                        case 2 :        // Below Normal
                            lPriority = BELOW_NORMAL_PRIORITY_CLASS;
                            break;
                        case 4 :        // Above Normal
                            lPriority = ABOVE_NORMAL_PRIORITY_CLASS;
                            break;
                        case 5 :        // High
                            lPriority = HIGH_PRIORITY_CLASS;
                            break;
                        case 6 :        // Realtime (!! NOT RECOMMENDED !!)
                            lPriority = REALTIME_PRIORITY_CLASS;
                    }
                }
            case L'E' :     // elevate
            case L'e' :
                if ((*(sStartPtr+1) == L'L' || *(sStartPtr+1) == L'l') &&
                    (*(sStartPtr+2) == L'E' || *(sStartPtr+2) == L'e') &&
                    (*(sStartPtr+3) == L'V' || *(sStartPtr+3) == L'v') &&
                    (*(sStartPtr+4) == L'A' || *(sStartPtr+4) == L'a') &&
                    (*(sStartPtr+5) == L'T' || *(sStartPtr+5) == L't') &&
                    (*(sStartPtr+6) == L'E' || *(sStartPtr+6) == L'e'))
                {
                    sStartPtr += 7;
                    while (*sStartPtr == L'=' || *sStartPtr == 0x20 || *sStartPtr == 0x9)
                        sStartPtr++;
                    if (*sStartPtr == L'1')
                        rInfo.lpVerb = L"runas";
                }
        }
    }

    // We're done with sText so free it
    HeapFree(pMain, 0ul, sText);

    // lWait should either be 0 or SEE_MASK_NOCLOSEPROCESS
    rInfo.fMask = lWait | 0x300ul;  //SEE_MASK_DOENVSUBST | SEE_MASK_NOASYNC = 0x300


#ifdef debugme

    // Spit out all the variables
    unsigned iMsgLen = 784;
    wchar_t sMsg[iMsgLen];
    *sMsg = L'\0';
    StringCchCatW(sMsg, iMsgLen, L"Cmd = ");
    if (rInfo.lpFile)
        StringCchCatW(sMsg, iMsgLen, rInfo.lpFile);
    StringCchCatW(sMsg, iMsgLen, L"\r\n\r\nArgs = ");
    if (rInfo.lpParameters)
        StringCchCatW(sMsg, iMsgLen, rInfo.lpParameters);
    StringCchCatW(sMsg, iMsgLen, L"\r\n\r\nWorkingDir = ");
    if (rInfo.lpDirectory)
        StringCchCatW(sMsg, iMsgLen, rInfo.lpDirectory);
    StringCchCatW(sMsg, iMsgLen, L"\r\n\r\nWait = ");
    if (lWait)
        StringCchCatW(sMsg, iMsgLen, L"1");
    else
        StringCchCatW(sMsg, iMsgLen, L"0");
    StringCchCatW(sMsg, iMsgLen, L"\r\n\r\nCmdShow = ");
    wchar_t sNum[2] = L"\0";
    switch (rInfo.nShow)
    {
        case 0 ... 9 :
            *sNum = rInfo.nShow+0x30;
            break;
        case 10 :
            *sNum = L'1';
            *(sNum+1) = L'0';
            break;
        default :
            *sNum = L'-';
            *(sNum+1) = L'1';
    }
    StringCchCatW(sMsg, iMsgLen, sNum);
    StringCchCatW(sMsg, iMsgLen, L"\r\n\r\nPriority = ");
    switch (lPriority)
    {
        case NORMAL_PRIORITY_CLASS :
            StringCchCatW(sMsg, iMsgLen, L"Normal");
            break;
        case IDLE_PRIORITY_CLASS :
            StringCchCatW(sMsg, iMsgLen, L"Idle");
            break;
        case HIGH_PRIORITY_CLASS :
            StringCchCatW(sMsg, iMsgLen, L"High");
            break;
        case BELOW_NORMAL_PRIORITY_CLASS :
            StringCchCatW(sMsg, iMsgLen, L"Below Normal");
            break;
        case ABOVE_NORMAL_PRIORITY_CLASS :
            StringCchCatW(sMsg, iMsgLen, L"Above Normal");
            break;
        case REALTIME_PRIORITY_CLASS :
            StringCchCatW(sMsg, iMsgLen, L"!!Realtime!!");
    }
    StringCchCatW(sMsg, iMsgLen, L"\r\n\r\nElevate = ");
    if (rInfo.lpVerb)
        StringCchCatW(sMsg, iMsgLen, L"1");
    else
        StringCchCatW(sMsg, iMsgLen, L"0");
    MessageBoxW(NULL, sMsg, L"Debug", MB_OK);

#else

    if (!rInfo.lpFile)
    {
        // We just went through all this for nothing??
        MessageBoxW(NULL, L"Ini file missing 'Cmd=<command>'", L"ERROR", 0x2030);
        return 1;
    }
    if (ShellExecuteExW(&rInfo))
    {
        if (rInfo.hProcess && lWait)
        {
            WaitForSingleObject(rInfo.hProcess, INFINITE);
            CloseHandle(rInfo.hProcess);
        }

        if (lPriority)
            SetPriorityClass(rInfo.hProcess, lPriority);
        return 0;
    }
    return 1;
#endif
}

int _start(void)
{
    ExitProcess(main());
}
\$\endgroup\$
3
  • \$\begingroup\$ Could you further describe the purpose(s) of this program? \$\endgroup\$
    – glampert
    Mar 11, 2015 at 2:56
  • 1
    \$\begingroup\$ Another example is notepad++. Many power users like to replace notepad.exe with this text editor. Instead of associating notepad++.exe with file types, you could rename wormhole to notepad, replace notepad.exe in the windows folder with it, and point it to your notepad++ installation. \$\endgroup\$
    – x79
    Mar 11, 2015 at 4:25
  • \$\begingroup\$ Okay, now I get it ;) \$\endgroup\$
    – glampert
    Mar 11, 2015 at 5:01

1 Answer 1

3
\$\begingroup\$
  • Is there some reason why you aren't using the Win32 API routines to read from an existing INI file? GetPrivateProfileString() and the like?
  • When you're reallocating a buffer where you don't know the actual size, you're supposed to double its size each time instead of increasing by a fixed amount (use iTmp *= 2; instead of iTmp += 8;)
  • Instead of if ((sEndPtr - sStartPtr) < 5) how about if (sEndPtr < sStartPtr + 5u)
  • Instead of the switch (lPriority), put all the strings in an array and just concatenate on the proper one.
\$\endgroup\$
1
  • \$\begingroup\$ I looked at GetPrivateProfileString() and there are some things I don't like about it. 1) it will not take UTF-8 input. 2) it will open the file and close it every time I call the function. I have implemented the other changes though. The (sEndPtr < sStartPtr + 5) tip is great :) \$\endgroup\$
    – x79
    Mar 12, 2015 at 20:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.