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\$