/* SfxSetup.c - 7z SFX Setup | |
2014-12-07 : Igor Pavlov : Public domain */ | |
#include "Precomp.h" | |
#ifndef UNICODE | |
#define UNICODE | |
#endif | |
#ifndef _UNICODE | |
#define _UNICODE | |
#endif | |
#ifdef _CONSOLE | |
#include <stdio.h> | |
#endif | |
#include "../../7z.h" | |
#include "../../7zAlloc.h" | |
#include "../../7zCrc.h" | |
#include "../../7zFile.h" | |
#include "../../CpuArch.h" | |
#define k_EXE_ExtIndex 2 | |
static const char *kExts[] = | |
{ | |
"bat" | |
, "cmd" | |
, "exe" | |
, "inf" | |
, "msi" | |
#ifdef UNDER_CE | |
, "cab" | |
#endif | |
, "html" | |
, "htm" | |
}; | |
static const char *kNames[] = | |
{ | |
"setup" | |
, "install" | |
, "run" | |
, "start" | |
}; | |
static unsigned FindExt(const wchar_t *s, unsigned *extLen) | |
{ | |
unsigned len = (unsigned)wcslen(s); | |
unsigned i; | |
for (i = len; i > 0; i--) | |
{ | |
if (s[i - 1] == '.') | |
{ | |
*extLen = len - i; | |
return i - 1; | |
} | |
} | |
*extLen = 0; | |
return len; | |
} | |
#define MAKE_CHAR_UPPER(c) ((((c) >= 'a' && (c) <= 'z') ? (c) -= 0x20 : (c))) | |
static unsigned FindItem(const char **items, unsigned num, const wchar_t *s, unsigned len) | |
{ | |
unsigned i; | |
for (i = 0; i < num; i++) | |
{ | |
const char *item = items[i]; | |
unsigned itemLen = (unsigned)strlen(item); | |
unsigned j; | |
if (len != itemLen) | |
continue; | |
for (j = 0; j < len; j++) | |
{ | |
unsigned c = item[j]; | |
if (c != s[j] && MAKE_CHAR_UPPER(c) != s[j]) | |
break; | |
} | |
if (j == len) | |
return i; | |
} | |
return i; | |
} | |
#ifdef _CONSOLE | |
static BOOL WINAPI HandlerRoutine(DWORD ctrlType) | |
{ | |
ctrlType = ctrlType; | |
return TRUE; | |
} | |
#endif | |
static void PrintErrorMessage(const char *message) | |
{ | |
#ifdef _CONSOLE | |
printf("\n7-Zip Error: %s\n", message); | |
#else | |
#ifdef UNDER_CE | |
WCHAR messageW[256 + 4]; | |
unsigned i; | |
for (i = 0; i < 256 && message[i] != 0; i++) | |
messageW[i] = message[i]; | |
messageW[i] = 0; | |
MessageBoxW(0, messageW, L"7-Zip Error", MB_ICONERROR); | |
#else | |
MessageBoxA(0, message, "7-Zip Error", MB_ICONERROR); | |
#endif | |
#endif | |
} | |
static WRes MyCreateDir(const WCHAR *name) | |
{ | |
return CreateDirectoryW(name, NULL) ? 0 : GetLastError(); | |
} | |
#ifdef UNDER_CE | |
#define kBufferSize (1 << 13) | |
#else | |
#define kBufferSize (1 << 15) | |
#endif | |
#define kSignatureSearchLimit (1 << 22) | |
static Bool FindSignature(CSzFile *stream, UInt64 *resPos) | |
{ | |
Byte buf[kBufferSize]; | |
size_t numPrevBytes = 0; | |
*resPos = 0; | |
for (;;) | |
{ | |
size_t processed, pos; | |
if (*resPos > kSignatureSearchLimit) | |
return False; | |
processed = kBufferSize - numPrevBytes; | |
if (File_Read(stream, buf + numPrevBytes, &processed) != 0) | |
return False; | |
processed += numPrevBytes; | |
if (processed < k7zStartHeaderSize || | |
(processed == k7zStartHeaderSize && numPrevBytes != 0)) | |
return False; | |
processed -= k7zStartHeaderSize; | |
for (pos = 0; pos <= processed; pos++) | |
{ | |
for (; buf[pos] != '7' && pos <= processed; pos++); | |
if (pos > processed) | |
break; | |
if (memcmp(buf + pos, k7zSignature, k7zSignatureSize) == 0) | |
if (CrcCalc(buf + pos + 12, 20) == GetUi32(buf + pos + 8)) | |
{ | |
*resPos += pos; | |
return True; | |
} | |
} | |
*resPos += processed; | |
numPrevBytes = k7zStartHeaderSize; | |
memmove(buf, buf + processed, k7zStartHeaderSize); | |
} | |
} | |
static Bool DoesFileOrDirExist(const WCHAR *path) | |
{ | |
WIN32_FIND_DATAW fd; | |
HANDLE handle; | |
handle = FindFirstFileW(path, &fd); | |
if (handle == INVALID_HANDLE_VALUE) | |
return False; | |
FindClose(handle); | |
return True; | |
} | |
static WRes RemoveDirWithSubItems(WCHAR *path) | |
{ | |
WIN32_FIND_DATAW fd; | |
HANDLE handle; | |
WRes res = 0; | |
size_t len = wcslen(path); | |
wcscpy(path + len, L"*"); | |
handle = FindFirstFileW(path, &fd); | |
path[len] = L'\0'; | |
if (handle == INVALID_HANDLE_VALUE) | |
return GetLastError(); | |
for (;;) | |
{ | |
if (wcscmp(fd.cFileName, L".") != 0 && | |
wcscmp(fd.cFileName, L"..") != 0) | |
{ | |
wcscpy(path + len, fd.cFileName); | |
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) | |
{ | |
wcscat(path, WSTRING_PATH_SEPARATOR); | |
res = RemoveDirWithSubItems(path); | |
} | |
else | |
{ | |
SetFileAttributesW(path, 0); | |
if (DeleteFileW(path) == 0) | |
res = GetLastError(); | |
} | |
if (res != 0) | |
break; | |
} | |
if (!FindNextFileW(handle, &fd)) | |
{ | |
res = GetLastError(); | |
if (res == ERROR_NO_MORE_FILES) | |
res = 0; | |
break; | |
} | |
} | |
path[len] = L'\0'; | |
FindClose(handle); | |
if (res == 0) | |
{ | |
if (!RemoveDirectoryW(path)) | |
res = GetLastError(); | |
} | |
return res; | |
} | |
#ifdef _CONSOLE | |
int MY_CDECL main() | |
#else | |
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, | |
#ifdef UNDER_CE | |
LPWSTR | |
#else | |
LPSTR | |
#endif | |
lpCmdLine, int nCmdShow) | |
#endif | |
{ | |
CFileInStream archiveStream; | |
CLookToRead lookStream; | |
CSzArEx db; | |
SRes res = SZ_OK; | |
ISzAlloc allocImp; | |
ISzAlloc allocTempImp; | |
WCHAR sfxPath[MAX_PATH + 2]; | |
WCHAR path[MAX_PATH * 3 + 2]; | |
#ifndef UNDER_CE | |
WCHAR workCurDir[MAX_PATH + 32]; | |
#endif | |
size_t pathLen; | |
DWORD winRes; | |
const wchar_t *cmdLineParams; | |
const char *errorMessage = NULL; | |
Bool useShellExecute = True; | |
#ifdef _CONSOLE | |
SetConsoleCtrlHandler(HandlerRoutine, TRUE); | |
#else | |
hInstance = hInstance; | |
hPrevInstance = hPrevInstance; | |
lpCmdLine = lpCmdLine; | |
nCmdShow = nCmdShow; | |
#endif | |
CrcGenerateTable(); | |
allocImp.Alloc = SzAlloc; | |
allocImp.Free = SzFree; | |
allocTempImp.Alloc = SzAllocTemp; | |
allocTempImp.Free = SzFreeTemp; | |
FileInStream_CreateVTable(&archiveStream); | |
LookToRead_CreateVTable(&lookStream, False); | |
winRes = GetModuleFileNameW(NULL, sfxPath, MAX_PATH); | |
if (winRes == 0 || winRes > MAX_PATH) | |
return 1; | |
{ | |
cmdLineParams = GetCommandLineW(); | |
#ifndef UNDER_CE | |
{ | |
Bool quoteMode = False; | |
for (;; cmdLineParams++) | |
{ | |
wchar_t c = *cmdLineParams; | |
if (c == L'\"') | |
quoteMode = !quoteMode; | |
else if (c == 0 || (c == L' ' && !quoteMode)) | |
break; | |
} | |
} | |
#endif | |
} | |
{ | |
unsigned i; | |
DWORD d; | |
winRes = GetTempPathW(MAX_PATH, path); | |
if (winRes == 0 || winRes > MAX_PATH) | |
return 1; | |
pathLen = wcslen(path); | |
d = (GetTickCount() << 12) ^ (GetCurrentThreadId() << 14) ^ GetCurrentProcessId(); | |
for (i = 0;; i++, d += GetTickCount()) | |
{ | |
if (i >= 100) | |
{ | |
res = SZ_ERROR_FAIL; | |
break; | |
} | |
wcscpy(path + pathLen, L"7z"); | |
{ | |
wchar_t *s = path + wcslen(path); | |
UInt32 value = d; | |
unsigned k; | |
for (k = 0; k < 8; k++) | |
{ | |
unsigned t = value & 0xF; | |
value >>= 4; | |
s[7 - k] = (char)((t < 10) ? ('0' + t) : ('A' + (t - 10))); | |
} | |
s[k] = '\0'; | |
} | |
if (DoesFileOrDirExist(path)) | |
continue; | |
if (CreateDirectoryW(path, NULL)) | |
{ | |
wcscat(path, WSTRING_PATH_SEPARATOR); | |
pathLen = wcslen(path); | |
break; | |
} | |
if (GetLastError() != ERROR_ALREADY_EXISTS) | |
{ | |
res = SZ_ERROR_FAIL; | |
break; | |
} | |
} | |
#ifndef UNDER_CE | |
wcscpy(workCurDir, path); | |
#endif | |
if (res != SZ_OK) | |
errorMessage = "Can't create temp folder"; | |
} | |
if (res != SZ_OK) | |
{ | |
if (!errorMessage) | |
errorMessage = "Error"; | |
PrintErrorMessage(errorMessage); | |
return 1; | |
} | |
if (InFile_OpenW(&archiveStream.file, sfxPath) != 0) | |
{ | |
errorMessage = "can not open input file"; | |
res = SZ_ERROR_FAIL; | |
} | |
else | |
{ | |
UInt64 pos = 0; | |
if (!FindSignature(&archiveStream.file, &pos)) | |
res = SZ_ERROR_FAIL; | |
else if (File_Seek(&archiveStream.file, (Int64 *)&pos, SZ_SEEK_SET) != 0) | |
res = SZ_ERROR_FAIL; | |
if (res != 0) | |
errorMessage = "Can't find 7z archive"; | |
} | |
if (res == SZ_OK) | |
{ | |
lookStream.realStream = &archiveStream.s; | |
LookToRead_Init(&lookStream); | |
} | |
SzArEx_Init(&db); | |
if (res == SZ_OK) | |
{ | |
res = SzArEx_Open(&db, &lookStream.s, &allocImp, &allocTempImp); | |
} | |
if (res == SZ_OK) | |
{ | |
UInt32 executeFileIndex = (UInt32)(Int32)-1; | |
UInt32 minPrice = 1 << 30; | |
UInt32 i; | |
UInt32 blockIndex = 0xFFFFFFFF; /* it can have any value before first call (if outBuffer = 0) */ | |
Byte *outBuffer = 0; /* it must be 0 before first call for each new archive. */ | |
size_t outBufferSize = 0; /* it can have any value before first call (if outBuffer = 0) */ | |
for (i = 0; i < db.NumFiles; i++) | |
{ | |
size_t offset = 0; | |
size_t outSizeProcessed = 0; | |
size_t len; | |
WCHAR *temp; | |
len = SzArEx_GetFileNameUtf16(&db, i, NULL); | |
if (len >= MAX_PATH) | |
{ | |
res = SZ_ERROR_FAIL; | |
break; | |
} | |
temp = path + pathLen; | |
SzArEx_GetFileNameUtf16(&db, i, temp); | |
{ | |
res = SzArEx_Extract(&db, &lookStream.s, i, | |
&blockIndex, &outBuffer, &outBufferSize, | |
&offset, &outSizeProcessed, | |
&allocImp, &allocTempImp); | |
if (res != SZ_OK) | |
break; | |
} | |
{ | |
CSzFile outFile; | |
size_t processedSize; | |
size_t j; | |
size_t nameStartPos = 0; | |
for (j = 0; temp[j] != 0; j++) | |
{ | |
if (temp[j] == '/') | |
{ | |
temp[j] = 0; | |
MyCreateDir(path); | |
temp[j] = CHAR_PATH_SEPARATOR; | |
nameStartPos = j + 1; | |
} | |
} | |
if (SzArEx_IsDir(&db, i)) | |
{ | |
MyCreateDir(path); | |
continue; | |
} | |
else | |
{ | |
unsigned extLen; | |
const WCHAR *name = temp + nameStartPos; | |
unsigned len = (unsigned)wcslen(name); | |
unsigned nameLen = FindExt(temp + nameStartPos, &extLen); | |
unsigned extPrice = FindItem(kExts, sizeof(kExts) / sizeof(kExts[0]), name + len - extLen, extLen); | |
unsigned namePrice = FindItem(kNames, sizeof(kNames) / sizeof(kNames[0]), name, nameLen); | |
unsigned price = namePrice + extPrice * 64 + (nameStartPos == 0 ? 0 : (1 << 12)); | |
if (minPrice > price) | |
{ | |
minPrice = price; | |
executeFileIndex = i; | |
useShellExecute = (extPrice != k_EXE_ExtIndex); | |
} | |
if (DoesFileOrDirExist(path)) | |
{ | |
errorMessage = "Duplicate file"; | |
res = SZ_ERROR_FAIL; | |
break; | |
} | |
if (OutFile_OpenW(&outFile, path)) | |
{ | |
errorMessage = "Can't open output file"; | |
res = SZ_ERROR_FAIL; | |
break; | |
} | |
} | |
processedSize = outSizeProcessed; | |
if (File_Write(&outFile, outBuffer + offset, &processedSize) != 0 || processedSize != outSizeProcessed) | |
{ | |
errorMessage = "Can't write output file"; | |
res = SZ_ERROR_FAIL; | |
} | |
#ifdef USE_WINDOWS_FILE | |
if (SzBitWithVals_Check(&db.MTime, i)) | |
{ | |
const CNtfsFileTime *t = db.MTime.Vals + i; | |
FILETIME mTime; | |
mTime.dwLowDateTime = t->Low; | |
mTime.dwHighDateTime = t->High; | |
SetFileTime(outFile.handle, NULL, NULL, &mTime); | |
} | |
#endif | |
{ | |
SRes res2 = File_Close(&outFile); | |
if (res != SZ_OK) | |
break; | |
if (res2 != SZ_OK) | |
{ | |
res = res2; | |
break; | |
} | |
} | |
#ifdef USE_WINDOWS_FILE | |
if (SzBitWithVals_Check(&db.Attribs, i)) | |
SetFileAttributesW(path, db.Attribs.Vals[i]); | |
#endif | |
} | |
} | |
if (res == SZ_OK) | |
{ | |
if (executeFileIndex == (UInt32)(Int32)-1) | |
{ | |
errorMessage = "There is no file to execute"; | |
res = SZ_ERROR_FAIL; | |
} | |
else | |
{ | |
WCHAR *temp = path + pathLen; | |
UInt32 j; | |
SzArEx_GetFileNameUtf16(&db, executeFileIndex, temp); | |
for (j = 0; temp[j] != 0; j++) | |
if (temp[j] == '/') | |
temp[j] = CHAR_PATH_SEPARATOR; | |
} | |
} | |
IAlloc_Free(&allocImp, outBuffer); | |
} | |
SzArEx_Free(&db, &allocImp); | |
File_Close(&archiveStream.file); | |
if (res == SZ_OK) | |
{ | |
HANDLE hProcess = 0; | |
#ifndef UNDER_CE | |
WCHAR oldCurDir[MAX_PATH + 2]; | |
oldCurDir[0] = 0; | |
{ | |
DWORD needLen = GetCurrentDirectory(MAX_PATH + 1, oldCurDir); | |
if (needLen == 0 || needLen > MAX_PATH) | |
oldCurDir[0] = 0; | |
SetCurrentDirectory(workCurDir); | |
} | |
#endif | |
if (useShellExecute) | |
{ | |
SHELLEXECUTEINFO ei; | |
UINT32 executeRes; | |
BOOL success; | |
memset(&ei, 0, sizeof(ei)); | |
ei.cbSize = sizeof(ei); | |
ei.lpFile = path; | |
ei.fMask = SEE_MASK_NOCLOSEPROCESS | |
#ifndef UNDER_CE | |
| SEE_MASK_FLAG_DDEWAIT | |
#endif | |
/* | SEE_MASK_NO_CONSOLE */ | |
; | |
if (wcslen(cmdLineParams) != 0) | |
ei.lpParameters = cmdLineParams; | |
ei.nShow = SW_SHOWNORMAL; /* SW_HIDE; */ | |
success = ShellExecuteEx(&ei); | |
executeRes = (UINT32)(UINT_PTR)ei.hInstApp; | |
if (!success || (executeRes <= 32 && executeRes != 0)) /* executeRes = 0 in Windows CE */ | |
res = SZ_ERROR_FAIL; | |
else | |
hProcess = ei.hProcess; | |
} | |
else | |
{ | |
STARTUPINFOW si; | |
PROCESS_INFORMATION pi; | |
WCHAR cmdLine[MAX_PATH * 3]; | |
wcscpy(cmdLine, path); | |
wcscat(cmdLine, cmdLineParams); | |
memset(&si, 0, sizeof(si)); | |
si.cb = sizeof(si); | |
if (CreateProcessW(NULL, cmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi) == 0) | |
res = SZ_ERROR_FAIL; | |
else | |
{ | |
CloseHandle(pi.hThread); | |
hProcess = pi.hProcess; | |
} | |
} | |
if (hProcess != 0) | |
{ | |
WaitForSingleObject(hProcess, INFINITE); | |
CloseHandle(hProcess); | |
} | |
#ifndef UNDER_CE | |
SetCurrentDirectory(oldCurDir); | |
#endif | |
} | |
path[pathLen] = L'\0'; | |
RemoveDirWithSubItems(path); | |
if (res == SZ_OK) | |
return 0; | |
{ | |
if (res == SZ_ERROR_UNSUPPORTED) | |
errorMessage = "Decoder doesn't support this archive"; | |
else if (res == SZ_ERROR_MEM) | |
errorMessage = "Can't allocate required memory"; | |
else if (res == SZ_ERROR_CRC) | |
errorMessage = "CRC error"; | |
else | |
{ | |
if (!errorMessage) | |
errorMessage = "ERROR"; | |
} | |
if (errorMessage) | |
PrintErrorMessage(errorMessage); | |
} | |
return 1; | |
} |