Home | History | Annotate | Download | only in SfxSetup
      1 /* SfxSetup.c - 7z SFX Setup
      2 2010-11-11 : Igor Pavlov : Public domain */
      3 
      4 #ifndef UNICODE
      5 #define UNICODE
      6 #endif
      7 
      8 #ifndef _UNICODE
      9 #define _UNICODE
     10 #endif
     11 
     12 #ifdef _CONSOLE
     13 #include <stdio.h>
     14 #endif
     15 
     16 #include "../../7z.h"
     17 #include "../../7zAlloc.h"
     18 #include "../../7zCrc.h"
     19 #include "../../7zFile.h"
     20 #include "../../CpuArch.h"
     21 
     22 #define k_EXE_ExtIndex 1
     23 
     24 static const char *kExts[] =
     25 {
     26   "bat",
     27   "exe",
     28   "inf",
     29   "msi",
     30   #ifdef UNDER_CE
     31   "cab",
     32   #endif
     33   "html",
     34   "htm"
     35 };
     36 
     37 static const char *kNames[] =
     38 {
     39   "setup",
     40   "install",
     41   "run",
     42   "start"
     43 };
     44 
     45 static unsigned FindExt(const wchar_t *s, unsigned *extLen)
     46 {
     47   unsigned len = (unsigned)wcslen(s);
     48   unsigned i;
     49   for (i = len; i > 0; i--)
     50   {
     51     if (s[i - 1] == '.')
     52     {
     53       *extLen = len - i;
     54       return i - 1;
     55     }
     56   }
     57   *extLen = 0;
     58   return len;
     59 }
     60 
     61 #define MAKE_CHAR_UPPER(c) ((((c) >= 'a' && (c) <= 'z') ? (c) -= 0x20 : (c)))
     62 
     63 static unsigned FindItem(const char **items, unsigned num, const wchar_t *s, unsigned len)
     64 {
     65   unsigned i;
     66   for (i = 0; i < num; i++)
     67   {
     68     const char *item = items[i];
     69     unsigned itemLen = (unsigned)strlen(item);
     70     unsigned j;
     71     if (len != itemLen)
     72       continue;
     73     for (j = 0; j < len; j++)
     74     {
     75       unsigned c = item[j];
     76       if (c != s[j] && MAKE_CHAR_UPPER(c) != s[j])
     77         break;
     78     }
     79     if (j == len)
     80       return i;
     81   }
     82   return i;
     83 }
     84 
     85 #ifdef _CONSOLE
     86 static BOOL WINAPI HandlerRoutine(DWORD ctrlType)
     87 {
     88   ctrlType = ctrlType;
     89   return TRUE;
     90 }
     91 #endif
     92 
     93 static void PrintErrorMessage(const char *message)
     94 {
     95   #ifdef _CONSOLE
     96   printf("\n7-Zip Error: %s\n", message);
     97   #else
     98   #ifdef UNDER_CE
     99   WCHAR messageW[256 + 4];
    100   unsigned i;
    101   for (i = 0; i < 256 && message[i] != 0; i++)
    102     messageW[i] = message[i];
    103   messageW[i] = 0;
    104   MessageBoxW(0, messageW, L"7-Zip Error", MB_ICONERROR);
    105   #else
    106   MessageBoxA(0, message, "7-Zip Error", MB_ICONERROR);
    107   #endif
    108   #endif
    109 }
    110 
    111 static WRes MyCreateDir(const WCHAR *name)
    112 {
    113   return CreateDirectoryW(name, NULL) ? 0 : GetLastError();
    114 }
    115 
    116 #ifdef UNDER_CE
    117 #define kBufferSize (1 << 13)
    118 #else
    119 #define kBufferSize (1 << 15)
    120 #endif
    121 
    122 #define kSignatureSearchLimit (1 << 22)
    123 
    124 static Bool FindSignature(CSzFile *stream, UInt64 *resPos)
    125 {
    126   Byte buf[kBufferSize];
    127   size_t numPrevBytes = 0;
    128   *resPos = 0;
    129   for (;;)
    130   {
    131     size_t numTests, pos;
    132     if (*resPos > kSignatureSearchLimit)
    133       return False;
    134 
    135     do
    136     {
    137       size_t processed = kBufferSize - numPrevBytes;
    138       if (File_Read(stream, buf + numPrevBytes, &processed) != 0)
    139         return False;
    140       if (processed == 0)
    141         return False;
    142       numPrevBytes += processed;
    143     }
    144     while (numPrevBytes <= k7zStartHeaderSize);
    145 
    146     numTests = numPrevBytes - k7zStartHeaderSize;
    147     for (pos = 0; pos < numTests; pos++)
    148     {
    149       for (; buf[pos] != '7' && pos < numTests; pos++);
    150       if (pos == numTests)
    151         break;
    152       if (memcmp(buf + pos, k7zSignature, k7zSignatureSize) == 0)
    153         if (CrcCalc(buf + pos + 12, 20) == GetUi32(buf + pos + 8))
    154         {
    155           *resPos += pos;
    156           return True;
    157         }
    158     }
    159     *resPos += numTests;
    160     numPrevBytes -= numTests;
    161     memmove(buf, buf + numTests, numPrevBytes);
    162   }
    163 }
    164 
    165 static Bool DoesFileOrDirExist(const WCHAR *path)
    166 {
    167   WIN32_FIND_DATAW fd;
    168   HANDLE handle;
    169   handle = FindFirstFileW(path, &fd);
    170   if (handle == INVALID_HANDLE_VALUE)
    171     return False;
    172   FindClose(handle);
    173   return True;
    174 }
    175 
    176 static WRes RemoveDirWithSubItems(WCHAR *path)
    177 {
    178   WIN32_FIND_DATAW fd;
    179   HANDLE handle;
    180   WRes res = 0;
    181   size_t len = wcslen(path);
    182   wcscpy(path + len, L"*");
    183   handle = FindFirstFileW(path, &fd);
    184   path[len] = L'\0';
    185   if (handle == INVALID_HANDLE_VALUE)
    186     return GetLastError();
    187   for (;;)
    188   {
    189     if (wcscmp(fd.cFileName, L".") != 0 &&
    190         wcscmp(fd.cFileName, L"..") != 0)
    191     {
    192       wcscpy(path + len, fd.cFileName);
    193       if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
    194       {
    195         wcscat(path, L"\\");
    196         res = RemoveDirWithSubItems(path);
    197       }
    198       else
    199       {
    200         SetFileAttributesW(path, 0);
    201         if (DeleteFileW(path) == 0)
    202           res = GetLastError();
    203       }
    204       if (res != 0)
    205         break;
    206     }
    207     if (!FindNextFileW(handle, &fd))
    208     {
    209       res = GetLastError();
    210       if (res == ERROR_NO_MORE_FILES)
    211         res = 0;
    212       break;
    213     }
    214   }
    215   path[len] = L'\0';
    216   FindClose(handle);
    217   if (res == 0)
    218   {
    219     if (!RemoveDirectoryW(path))
    220       res = GetLastError();
    221   }
    222   return res;
    223 }
    224 
    225 #ifdef _CONSOLE
    226 int MY_CDECL main()
    227 #else
    228 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    229   #ifdef UNDER_CE
    230   LPWSTR
    231   #else
    232   LPSTR
    233   #endif
    234   lpCmdLine, int nCmdShow)
    235 #endif
    236 {
    237   CFileInStream archiveStream;
    238   CLookToRead lookStream;
    239   CSzArEx db;
    240   SRes res = SZ_OK;
    241   ISzAlloc allocImp;
    242   ISzAlloc allocTempImp;
    243   WCHAR sfxPath[MAX_PATH + 2];
    244   WCHAR path[MAX_PATH * 3 + 2];
    245   size_t pathLen;
    246   DWORD winRes;
    247   const wchar_t *cmdLineParams;
    248   const char *errorMessage = NULL;
    249   Bool useShellExecute = True;
    250 
    251   #ifdef _CONSOLE
    252   SetConsoleCtrlHandler(HandlerRoutine, TRUE);
    253   #else
    254   hInstance = hInstance;
    255   hPrevInstance = hPrevInstance;
    256   lpCmdLine = lpCmdLine;
    257   nCmdShow = nCmdShow;
    258   #endif
    259 
    260   CrcGenerateTable();
    261 
    262   allocImp.Alloc = SzAlloc;
    263   allocImp.Free = SzFree;
    264 
    265   allocTempImp.Alloc = SzAllocTemp;
    266   allocTempImp.Free = SzFreeTemp;
    267 
    268   FileInStream_CreateVTable(&archiveStream);
    269   LookToRead_CreateVTable(&lookStream, False);
    270 
    271   winRes = GetModuleFileNameW(NULL, sfxPath, MAX_PATH);
    272   if (winRes == 0 || winRes > MAX_PATH)
    273     return 1;
    274   {
    275     cmdLineParams = GetCommandLineW();
    276     #ifndef UNDER_CE
    277     {
    278       Bool quoteMode = False;
    279       for (;; cmdLineParams++)
    280       {
    281         wchar_t c = *cmdLineParams;
    282         if (c == L'\"')
    283           quoteMode = !quoteMode;
    284         else if (c == 0 || (c == L' ' && !quoteMode))
    285           break;
    286       }
    287     }
    288     #endif
    289   }
    290 
    291   {
    292     unsigned i;
    293     DWORD d;
    294     winRes = GetTempPathW(MAX_PATH, path);
    295     if (winRes == 0 || winRes > MAX_PATH)
    296       return 1;
    297     pathLen = wcslen(path);
    298     d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId();
    299     for (i = 0;; i++, d += GetTickCount())
    300     {
    301       if (i >= 100)
    302       {
    303         res = SZ_ERROR_FAIL;
    304         break;
    305       }
    306       wcscpy(path + pathLen, L"7z");
    307 
    308       {
    309         wchar_t *s = path + wcslen(path);
    310         UInt32 value = d;
    311         unsigned k;
    312         for (k = 0; k < 8; k++)
    313         {
    314           unsigned t = value & 0xF;
    315           value >>= 4;
    316           s[7 - k] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10)));
    317         }
    318         s[k] = '\0';
    319       }
    320 
    321       if (DoesFileOrDirExist(path))
    322         continue;
    323       if (CreateDirectoryW(path, NULL))
    324       {
    325         wcscat(path, L"\\");
    326         pathLen = wcslen(path);
    327         break;
    328       }
    329       if (GetLastError() != ERROR_ALREADY_EXISTS)
    330       {
    331         res = SZ_ERROR_FAIL;
    332         break;
    333       }
    334     }
    335     if (res != SZ_OK)
    336       errorMessage = "Can't create temp folder";
    337   }
    338 
    339   if (res != SZ_OK)
    340   {
    341     if (!errorMessage)
    342       errorMessage = "Error";
    343     PrintErrorMessage(errorMessage);
    344     return 1;
    345   }
    346 
    347   if (InFile_OpenW(&archiveStream.file, sfxPath) != 0)
    348   {
    349     errorMessage = "can not open input file";
    350     res = SZ_ERROR_FAIL;
    351   }
    352   else
    353   {
    354     UInt64 pos = 0;
    355     if (!FindSignature(&archiveStream.file, &pos))
    356       res = SZ_ERROR_FAIL;
    357     else if (File_Seek(&archiveStream.file, (Int64 *)&pos, SZ_SEEK_SET) != 0)
    358       res = SZ_ERROR_FAIL;
    359     if (res != 0)
    360       errorMessage = "Can't find 7z archive";
    361   }
    362 
    363   if (res == SZ_OK)
    364   {
    365     lookStream.realStream = &archiveStream.s;
    366     LookToRead_Init(&lookStream);
    367   }
    368 
    369   SzArEx_Init(&db);
    370   if (res == SZ_OK)
    371   {
    372     res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp);
    373   }
    374   if (res == SZ_OK)
    375   {
    376     UInt32 executeFileIndex = (UInt32)(Int32)-1;
    377     UInt32 minPrice = 1 << 30;
    378     UInt32 i;
    379     UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */
    380     Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */
    381     size_t outBufferSize = 0;  /* it can have any value before first call (if outBuffer = 0) */
    382 
    383     for (i = 0; i < db.db.NumFiles; i++)
    384     {
    385       size_t offset = 0;
    386       size_t outSizeProcessed = 0;
    387       const CSzFileItem *f = db.db.Files + i;
    388       size_t len;
    389       WCHAR *temp;
    390       len = SzArEx_GetFileNameUtf16(&db, i, NULL);
    391 
    392       if (len >= MAX_PATH)
    393       {
    394         res = SZ_ERROR_FAIL;
    395         break;
    396       }
    397 
    398       temp = path + pathLen;
    399 
    400       SzArEx_GetFileNameUtf16(&db, i, temp);
    401       {
    402         res = SzArEx_Extract(&db, &lookStream.s, i,
    403           &blockIndex, &outBuffer, &outBufferSize,
    404           &offset, &outSizeProcessed,
    405           &allocImp, &allocTempImp);
    406         if (res != SZ_OK)
    407           break;
    408       }
    409       {
    410         CSzFile outFile;
    411         size_t processedSize;
    412         size_t j;
    413         size_t nameStartPos = 0;
    414         for (j = 0; temp[j] != 0; j++)
    415         {
    416           if (temp[j] == '/')
    417           {
    418             temp[j] = 0;
    419             MyCreateDir(path);
    420             temp[j] = CHAR_PATH_SEPARATOR;
    421             nameStartPos = j + 1;
    422           }
    423         }
    424 
    425         if (f->IsDir)
    426         {
    427           MyCreateDir(path);
    428           continue;
    429         }
    430         else
    431         {
    432           unsigned extLen;
    433           const WCHAR *name = temp + nameStartPos;
    434           unsigned len = (unsigned)wcslen(name);
    435           unsigned nameLen = FindExt(temp + nameStartPos, &extLen);
    436           unsigned extPrice = FindItem(kExts, sizeof(kExts) / sizeof(kExts[0]), name + len - extLen, extLen);
    437           unsigned namePrice = FindItem(kNames, sizeof(kNames) / sizeof(kNames[0]), name, nameLen);
    438 
    439           unsigned price = namePrice + extPrice * 64 + (nameStartPos == 0 ? 0 : (1 << 12));
    440           if (minPrice > price)
    441           {
    442             minPrice = price;
    443             executeFileIndex = i;
    444             useShellExecute = (extPrice != k_EXE_ExtIndex);
    445           }
    446 
    447           if (DoesFileOrDirExist(path))
    448           {
    449             errorMessage = "Duplicate file";
    450             res = SZ_ERROR_FAIL;
    451             break;
    452           }
    453           if (OutFile_OpenW(&outFile, path))
    454           {
    455             errorMessage = "Can't open output file";
    456             res = SZ_ERROR_FAIL;
    457             break;
    458           }
    459         }
    460         processedSize = outSizeProcessed;
    461         if (File_Write(&outFile, outBuffer + offset, &processedSize) != 0 || processedSize != outSizeProcessed)
    462         {
    463           errorMessage = "Can't write output file";
    464           res = SZ_ERROR_FAIL;
    465         }
    466 
    467         #ifdef USE_WINDOWS_FILE
    468         if (f->MTimeDefined)
    469         {
    470           FILETIME mTime;
    471           mTime.dwLowDateTime = f->MTime.Low;
    472           mTime.dwHighDateTime = f->MTime.High;
    473           SetFileTime(outFile.handle, NULL, NULL, &mTime);
    474         }
    475         #endif
    476 
    477         {
    478           SRes res2 = File_Close(&outFile);
    479           if (res != SZ_OK)
    480             break;
    481           if (res2 != SZ_OK)
    482           {
    483             res = res2;
    484             break;
    485           }
    486         }
    487         #ifdef USE_WINDOWS_FILE
    488         if (f->AttribDefined)
    489           SetFileAttributesW(path, f->Attrib);
    490         #endif
    491       }
    492     }
    493 
    494     if (res == SZ_OK)
    495     {
    496       if (executeFileIndex == (UInt32)(Int32)-1)
    497       {
    498         errorMessage = "There is no file to execute";
    499         res = SZ_ERROR_FAIL;
    500       }
    501       else
    502       {
    503         WCHAR *temp = path + pathLen;
    504         UInt32 j;
    505         SzArEx_GetFileNameUtf16(&db, executeFileIndex, temp);
    506         for (j = 0; temp[j] != 0; j++)
    507           if (temp[j] == '/')
    508             temp[j] = CHAR_PATH_SEPARATOR;
    509       }
    510     }
    511     IAlloc_Free(&allocImp, outBuffer);
    512   }
    513   SzArEx_Free(&db, &allocImp);
    514 
    515   File_Close(&archiveStream.file);
    516 
    517   if (res == SZ_OK)
    518   {
    519     HANDLE hProcess = 0;
    520     if (useShellExecute)
    521     {
    522       SHELLEXECUTEINFO ei;
    523       UINT32 executeRes;
    524       BOOL success;
    525 
    526       memset(&ei, 0, sizeof(ei));
    527       ei.cbSize = sizeof(ei);
    528       ei.lpFile = path;
    529       ei.fMask = SEE_MASK_NOCLOSEPROCESS
    530           #ifndef UNDER_CE
    531           | SEE_MASK_FLAG_DDEWAIT
    532           #endif
    533           /* | SEE_MASK_NO_CONSOLE */
    534           ;
    535       if (wcslen(cmdLineParams) != 0)
    536         ei.lpParameters = cmdLineParams;
    537       ei.nShow = SW_SHOWNORMAL; /* SW_HIDE; */
    538       success = ShellExecuteEx(&ei);
    539       executeRes = (UINT32)(UINT_PTR)ei.hInstApp;
    540       if (!success || (executeRes <= 32 && executeRes != 0))  /* executeRes = 0 in Windows CE */
    541         res = SZ_ERROR_FAIL;
    542       else
    543         hProcess = ei.hProcess;
    544     }
    545     else
    546     {
    547       STARTUPINFOW si;
    548       PROCESS_INFORMATION pi;
    549       WCHAR cmdLine[MAX_PATH * 3];
    550 
    551       wcscpy(cmdLine, path);
    552       wcscat(cmdLine, cmdLineParams);
    553       memset(&si, 0, sizeof(si));
    554       si.cb = sizeof(si);
    555       if (CreateProcessW(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) == 0)
    556         res = SZ_ERROR_FAIL;
    557       else
    558       {
    559         CloseHandle(pi.hThread);
    560         hProcess = pi.hProcess;
    561       }
    562     }
    563     if (hProcess != 0)
    564     {
    565       WaitForSingleObject(hProcess, INFINITE);
    566       CloseHandle(hProcess);
    567     }
    568   }
    569 
    570   path[pathLen] = L'\0';
    571   RemoveDirWithSubItems(path);
    572 
    573   if (res == SZ_OK)
    574     return 0;
    575 
    576   {
    577     if (res == SZ_ERROR_UNSUPPORTED)
    578       errorMessage = "Decoder doesn't support this archive";
    579     else if (res == SZ_ERROR_MEM)
    580       errorMessage = "Can't allocate required memory";
    581     else if (res == SZ_ERROR_CRC)
    582       errorMessage = "CRC error";
    583     else
    584     {
    585       if (!errorMessage)
    586         errorMessage = "ERROR";
    587     }
    588     if (errorMessage)
    589       PrintErrorMessage(errorMessage);
    590   }
    591   return 1;
    592 }
    593