-
Notifications
You must be signed in to change notification settings - Fork 0
/
wcx_msi.cpp
462 lines (389 loc) · 16 KB
/
wcx_msi.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
/*****************************************************************************/
/* wcx_msi.cpp Copyright (c) Ladislav Zezula 2023 */
/*---------------------------------------------------------------------------*/
/* Main file for Total Commander MSI plugin */
/*---------------------------------------------------------------------------*/
/* Date Ver Who Comment */
/* -------- ---- --- ------- */
/* 22.07.23 1.00 Lad Created */
/*****************************************************************************/
#include "wcx_msi.h" // Our functions
#include "resource.h" // Resource symbols
#pragma comment(lib, "Msi.lib")
//-----------------------------------------------------------------------------
// Local variables
PFN_PROCESS_DATAA PfnProcessDataA; // Process data procedure (ANSI)
PFN_PROCESS_DATAW PfnProcessDataW; // Process data procedure (UNICODE)
PFN_CHANGE_VOLUMEA PfnChangeVolA; // Change volume procedure (ANSI)
PFN_CHANGE_VOLUMEW PfnChangeVolW; // Change volume procedure (UNICODE)
TCHAR g_szIniFile[MAX_PATH];
//-----------------------------------------------------------------------------
// CanYouHandleThisFile(W) allows the plugin to handle files with different
// extensions than the one defined in Total Commander
// https://www.ghisler.ch/wiki/index.php?title=CanYouHandleThisFile
BOOL WINAPI CanYouHandleThisFileW(LPCWSTR szFileName)
{
MSIHANDLE hMsiDb = NULL;
// Just try to open the database. If it succeeds,
// then we can handle this file
if(MsiOpenDatabase(szFileName, MSIDBOPEN_READONLY, &hMsiDb) == ERROR_SUCCESS)
{
// Log the handle for diagnostics
MSI_LOG_OPEN_HANDLE(hMsiDb);
// Close the handle
MSI_CLOSE_HANDLE(hMsiDb);
}
return (hMsiDb != NULL) ? TRUE : FALSE;
}
BOOL WINAPI CanYouHandleThisFile(LPCSTR szFileName)
{
return CanYouHandleThisFileW(TAnsiToWide(szFileName));
}
//-----------------------------------------------------------------------------
// OpenArchive(W) should perform all necessary operations when an archive is
// to be opened
// https://www.ghisler.ch/wiki/index.php?title=OpenArchive
static void FileTimeToDosFTime(DOS_FTIME & DosTime, const FILETIME & ft)
{
SYSTEMTIME stUTC; // Local file time
SYSTEMTIME st; // Local file time
FileTimeToSystemTime(&ft, &stUTC);
SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &st);
DosTime.ft_year = st.wYear - 60;
DosTime.ft_month = st.wMonth;
DosTime.ft_day = st.wDay;
DosTime.ft_hour = st.wHour;
DosTime.ft_min = st.wMinute;
DosTime.ft_tsec = (st.wSecond / 2);
}
static HANDLE OpenArchiveAW(TOpenArchiveData * pArchiveData, LPCWSTR szArchiveName)
{
WIN32_FIND_DATA wf;
TMsiDatabase * pMsiDB = NULL;
MSIHANDLE hMsiDb = NULL;
HANDLE hFind;
// Set the default error code
pArchiveData->OpenResult = E_UNKNOWN_FORMAT;
// Check the valid parameters
if(pArchiveData && szArchiveName && szArchiveName[0])
{
// Check the valid archive access
if(pArchiveData->OpenMode == PK_OM_LIST || pArchiveData->OpenMode == PK_OM_EXTRACT)
{
// Perform file search to in order to get filetime
if((hFind = FindFirstFile(szArchiveName, &wf)) != INVALID_HANDLE_VALUE)
{
// Attempt to open the MSI
if(MsiOpenDatabase(szArchiveName, MSIDBOPEN_READONLY, &hMsiDb) == ERROR_SUCCESS)
{
// Log the handle for diagnostics
MSI_LOG_OPEN_HANDLE(hMsiDb);
// Create the TMsiDatabase object
if((pMsiDB = new TMsiDatabase(hMsiDb, wf.ftLastWriteTime)) != NULL)
{
pArchiveData->OpenResult = 0;
return (HANDLE)(pMsiDB);
}
else
{
pArchiveData->OpenResult = E_NO_MEMORY;
MSI_CLOSE_HANDLE(hMsiDb);
}
}
FindClose(hFind);
}
}
}
return NULL;
}
HANDLE WINAPI OpenArchiveW(TOpenArchiveData * pArchiveData)
{
return OpenArchiveAW(pArchiveData, pArchiveData->szArchiveNameW);
}
HANDLE WINAPI OpenArchive(TOpenArchiveData * pArchiveData)
{
return OpenArchiveAW(pArchiveData, TAnsiToWide(pArchiveData->szArchiveNameA));
}
//-----------------------------------------------------------------------------
// CloseArchive should perform all necessary operations when an archive
// is about to be closed.
// https://www.ghisler.ch/wiki/index.php?title=CloseArchive
int WINAPI CloseArchive(HANDLE hArchive)
{
TMsiDatabase * pMsiDb;
if((pMsiDb = TMsiDatabase::FromHandle(hArchive)) != NULL)
{
// Force-close all loaded files
pMsiDb->CloseAllFiles();
pMsiDb->UnlockAndRelease();
// Finally release the database
pMsiDb->Release();
}
return (pMsiDb != NULL) ? ERROR_SUCCESS : E_NOT_SUPPORTED;
}
//-----------------------------------------------------------------------------
// GetPackerCaps tells Totalcmd what features your packer plugin supports.
// https://www.ghisler.ch/wiki/index.php?title=GetPackerCaps
// GetPackerCaps tells Total Commander what features we support
int WINAPI GetPackerCaps()
{
return(PK_CAPS_MULTIPLE | // Archive can contain multiple files
//PK_CAPS_OPTIONS | // Has options dialog
PK_CAPS_BY_CONTENT | // Detect archive type by content
PK_CAPS_SEARCHTEXT // Allow searching for text in archives created with this plugin
);
}
//-----------------------------------------------------------------------------
// ProcessFile should unpack the specified file or test the integrity of the archive.
// https://www.ghisler.ch/wiki/index.php?title=ProcessFile
static void MergePath(LPWSTR szFullPath, size_t ccFullPath, LPCWSTR szPath, LPCWSTR szName)
{
// Always the buffer with zero
if(ccFullPath != 0)
{
szFullPath[0] = 0;
// Append destination path, if exists
if(szPath && szPath[0])
{
StringCchCopy(szFullPath, ccFullPath, szPath);
}
// Append the name, if any
if(szName && szName[0])
{
// Append backslash
AddBackslash(szFullPath, ccFullPath);
StringCchCat(szFullPath, ccFullPath, szName);
}
}
}
static int CallProcessDataProc(LPCWSTR szFullPath, int nSize)
{
// Prioritize UNICODE version of the callback, if exists.
// This leads to nicer progress dialog shown by Total Commander
if(PfnProcessDataW != NULL)
return PfnProcessDataW(szFullPath, nSize);
// Call ANSI version of callback, if needed
if(PfnProcessDataA != NULL)
return PfnProcessDataA(TWideToAnsi(szFullPath), nSize);
// Continue the operation
return TRUE;
}
int WINAPI ProcessFileW(HANDLE hArchive, PROCESS_FILE_OPERATION nOperation, LPCWSTR szDestPath, LPCWSTR szDestName)
{
TMsiDatabase * pMsiDb;
TMsiFile * pMsiFile;
HANDLE hLocFile = INVALID_HANDLE_VALUE;
DWORD dwBytesWritten;
DWORD dwFileOffset = 0;
WCHAR szFullPath[MAX_PATH];
int nResult = E_NOT_SUPPORTED; // Result reported to Total Commander
// Check whether it's the valid archive handle
if((pMsiDb = TMsiDatabase::FromHandle(hArchive)) != NULL)
{
// If verify or skip the file, do nothing
if(nOperation == PK_TEST || nOperation == PK_SKIP)
{
pMsiDb->UnlockAndRelease();
return 0;
}
// Do we have to extract the file? If yes, the file must be saved in TMsiDatabase
if(nOperation == PK_EXTRACT)
{
if((pMsiFile = pMsiDb->LastFile()) != NULL)
{
// Construct the full path name
MergePath(szFullPath, _countof(szFullPath), szDestPath, szDestName);
// Create the local file
hLocFile = CreateFile(szFullPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, 0, NULL);
if(hLocFile != INVALID_HANDLE_VALUE)
{
// Populate the cach with complete file data
if(pMsiFile->LoadFileData() == ERROR_SUCCESS)
{
const MSI_BLOB & FileData = pMsiFile->FileData();
// Tell Total Commader what we are doing
while(CallProcessDataProc(szFullPath, dwFileOffset))
{
DWORD dwBytesToWrite = 0x1000;
// Get the size of remaining data
if((dwFileOffset + dwBytesToWrite) > FileData.cbData)
dwBytesToWrite = FileData.cbData - dwFileOffset;
// Are we done?
if(dwBytesToWrite == 0)
{
nResult = 0;
break;
}
// Write the target file
if(!WriteFile(hLocFile, FileData.pbData + dwFileOffset, dwBytesToWrite, &dwBytesWritten, NULL))
{
nResult = E_EWRITE;
break;
}
// Increment the total bytes
dwFileOffset += dwBytesWritten;
}
}
// Close the local file
SetEndOfFile(hLocFile);
CloseHandle(hLocFile);
}
else
{
nResult = E_ECREATE;
}
// Dereference the file
pMsiFile->Release();
}
}
// Unlock and release the archive
pMsiDb->UnlockAndRelease();
}
return nResult;
}
int WINAPI ProcessFile(HANDLE hArchive, PROCESS_FILE_OPERATION nOperation, LPCSTR szDestPath, LPCSTR szDestName)
{
return ProcessFileW(hArchive, nOperation, TAnsiToWide(szDestPath), TUTF8ToWide(szDestName));
}
//-----------------------------------------------------------------------------
// Totalcmd calls ReadHeader to find out what files are in the archive.
// https://www.ghisler.ch/wiki/index.php?title=ReadHeader
template <typename HDR>
static void StoreFoundFile(TMsiFile * pMsiFile, HDR * pHeaderData, DOS_FTIME & fileTime)
{
// Store the file name
StringCchCopyX(pHeaderData->FileName, _countof(pHeaderData->FileName), pMsiFile->Name());
// Store the file time
pHeaderData->FileTime = fileTime;
// Store the file sizes
pHeaderData->PackSize = pMsiFile->FileSize();
pHeaderData->UnpSize = pMsiFile->FileSize();
// Store file attributes
pHeaderData->FileAttr = FILE_ATTRIBUTE_ARCHIVE;
}
template <typename HDR>
int ReadHeaderTemplate(HANDLE hArchive, HDR * pHeaderData)
{
TMsiDatabase * pMsiDb;
TMsiFile * pMsiFile;
DOS_FTIME fileTime;
DWORD dwErrCode = E_NOT_SUPPORTED;
// Check the proper parameters
if((pMsiDb = TMsiDatabase::FromHandle(hArchive)) != NULL)
{
// Convert the Windows filetime to DOS filetime
FileTimeToDosFTime(fileTime, pMsiDb->FileTime());
// Split the action
if((pMsiFile = pMsiDb->GetNextFile()) != NULL)
{
StoreFoundFile(pMsiFile, pHeaderData, fileTime);
pMsiFile->Release();
dwErrCode = 0;
}
pMsiDb->UnlockAndRelease();
}
return dwErrCode;
}
int WINAPI ReadHeader(HANDLE hArchive, THeaderData * pHeaderData)
{
// Use the common template function
return ReadHeaderTemplate(hArchive, pHeaderData);
}
int WINAPI ReadHeaderEx(HANDLE hArchive, THeaderDataEx * pHeaderData)
{
// Use the common template function
return ReadHeaderTemplate(hArchive, pHeaderData);
}
int WINAPI ReadHeaderExW(HANDLE hArchive, THeaderDataExW * pHeaderData)
{
// Use the common template function
return ReadHeaderTemplate(hArchive, pHeaderData);
}
//-----------------------------------------------------------------------------
// SetChangeVolProc(W) allows you to notify user about changing a volume when packing files
// https://www.ghisler.ch/wiki/index.php?title=SetChangeVolProc
// This function allows you to notify user
// about changing a volume when packing files
void WINAPI SetChangeVolProc(HANDLE /* hArchive */, PFN_CHANGE_VOLUMEA PfnChangeVol)
{
PfnChangeVolA = PfnChangeVol;
}
void WINAPI SetChangeVolProcW(HANDLE /* hArchive */, PFN_CHANGE_VOLUMEW PfnChangeVol)
{
PfnChangeVolW = PfnChangeVol;
}
//-----------------------------------------------------------------------------
// SetProcessDataProc(W) allows you to notify user about the progress when you un/pack files
// Note that Total Commander may use INVALID_HANDLE_VALUE for the hArchive parameter
// https://www.ghisler.ch/wiki/index.php?title=SetProcessDataProc
void WINAPI SetProcessDataProc(HANDLE /* hArchive */, PFN_PROCESS_DATAA PfnProcessData)
{
PfnProcessDataA = PfnProcessData;
}
void WINAPI SetProcessDataProcW(HANDLE /* hArchive */, PFN_PROCESS_DATAW PfnProcessData)
{
PfnProcessDataW = PfnProcessData;
}
//-----------------------------------------------------------------------------
// PackFiles(W) specifies what should happen when a user creates, or adds files to the archive
// https://www.ghisler.ch/wiki/index.php?title=PackFiles
int WINAPI PackFilesW(LPCWSTR szPackedFile, LPCWSTR szSubPath, LPCWSTR szSrcPath, LPCWSTR szAddList, int nFlags)
{
UNREFERENCED_PARAMETER(szPackedFile);
UNREFERENCED_PARAMETER(szSubPath);
UNREFERENCED_PARAMETER(szSrcPath);
UNREFERENCED_PARAMETER(szAddList);
UNREFERENCED_PARAMETER(nFlags);
return E_NOT_SUPPORTED;
}
// PackFiles adds file(s) to an archive
int WINAPI PackFiles(LPCSTR szPackedFile, LPCSTR szSubPath, LPCSTR szSrcPath, LPCSTR szAddList, int nFlags)
{
UNREFERENCED_PARAMETER(szPackedFile);
UNREFERENCED_PARAMETER(szSubPath);
UNREFERENCED_PARAMETER(szSrcPath);
UNREFERENCED_PARAMETER(szAddList);
UNREFERENCED_PARAMETER(nFlags);
return E_NOT_SUPPORTED;
}
//-----------------------------------------------------------------------------
// DeleteFiles(W) should delete the specified files from the archive
// https://www.ghisler.ch/wiki/index.php?title=DeleteFiles
int WINAPI DeleteFilesW(LPCWSTR szPackedFile, LPCWSTR szDeleteList)
{
UNREFERENCED_PARAMETER(szPackedFile);
UNREFERENCED_PARAMETER(szDeleteList);
return E_NOT_SUPPORTED;
}
int WINAPI DeleteFiles(LPCSTR szPackedFile, LPCSTR szDeleteList)
{
UNREFERENCED_PARAMETER(szPackedFile);
UNREFERENCED_PARAMETER(szDeleteList);
return E_NOT_SUPPORTED;
}
//-----------------------------------------------------------------------------
// ConfigurePacker gets called when the user clicks the Configure button
// from within "Pack files..." dialog box in Totalcmd.
// https://www.ghisler.ch/wiki/index.php?title=ConfigurePacker
void WINAPI ConfigurePacker(HWND hParent, HINSTANCE hDllInstance)
{
UNREFERENCED_PARAMETER(hDllInstance);
UNREFERENCED_PARAMETER(hParent);
}
//-----------------------------------------------------------------------------
// PackSetDefaultParams is called immediately after loading the DLL, before
// any other function. This function is new in version 2.1. It requires Total
// Commander >=5.51, but is ignored by older versions.
// https://www.ghisler.ch/wiki/index.php?title=PackSetDefaultParams
void WINAPI PackSetDefaultParams(TPackDefaultParamStruct * dps)
{
// Set default configuration.
//SetDefaultConfiguration();
g_szIniFile[0] = 0;
// If INI file, load it from it too.
if(dps != NULL && dps->DefaultIniName[0])
{
StringCchCopyX(g_szIniFile, _countof(g_szIniFile), dps->DefaultIniName);
//LoadConfiguration();
}
}