mirror of
https://github.com/ladislav-zezula/FileTest
synced 2026-06-08 15:24:12 +00:00
1143 lines
38 KiB
C++
1143 lines
38 KiB
C++
/*****************************************************************************/
|
|
/* Page11Streams.cpp Copyright (c) Ladislav Zezula 2005 */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Description : */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Date Ver Who Comment */
|
|
/* -------- ---- --- ------- */
|
|
/* 15.08.05 1.00 Lad The first version of Page11Streams.cpp */
|
|
/*****************************************************************************/
|
|
|
|
#include "FileTest.h"
|
|
#include "resource.h"
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Local structures
|
|
|
|
#define MAX_STREAM_LENGTH 0x10000000
|
|
|
|
#define STREAM_TYPE_NONE 0
|
|
#define STREAM_TYPE_ADS 1
|
|
#define STREAM_TYPE_EA 2
|
|
|
|
struct TFileStream
|
|
{
|
|
LIST_ENTRY Entry;
|
|
LPTSTR szStreamName; // Pointer to stream name (variable length)
|
|
LPBYTE pbStreamData; // Pointer to stream data (variable length)
|
|
DWORD cbStreamData; // Length of the stream data
|
|
DWORD dwStreamType; // Stream type
|
|
|
|
BYTE Buffer[1];
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Local variables
|
|
|
|
static TListViewColumns Columns[] =
|
|
{
|
|
{IDS_STREAM_TYPE, 60},
|
|
{IDS_STREAM_NAME, 100},
|
|
{IDS_STREAM_DATA, -1},
|
|
{0, 0}
|
|
};
|
|
|
|
static LPCTSTR StreamTypes[] =
|
|
{
|
|
_T("NONE"),
|
|
_T("ADS"),
|
|
_T("EA")
|
|
};
|
|
|
|
static TAnchors * pAnchors = NULL;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Local functions
|
|
|
|
static LPTSTR StringFromAnsi(LPCSTR szStringA, ULONG cchLength)
|
|
{
|
|
LPTSTR szStringT;
|
|
|
|
szStringT = new TCHAR[cchLength+1];
|
|
if(szStringT != NULL)
|
|
{
|
|
#ifdef _UNICODE
|
|
MultiByteToWideChar(CP_ACP, 0, szStringA, cchLength, szStringT, cchLength);
|
|
szStringT[cchLength] = 0;
|
|
#else
|
|
StringCchCopyA(szStringT, szStringA, cchLength);
|
|
szStringT[cchLength] = 0;
|
|
#endif
|
|
}
|
|
|
|
return szStringT;
|
|
}
|
|
|
|
static LPTSTR StringFromWide(LPCWSTR szStringW, ULONG cchLength)
|
|
{
|
|
LPTSTR szStringT;
|
|
|
|
szStringT = new TCHAR[cchLength+1];
|
|
if(szStringT != NULL)
|
|
{
|
|
#ifdef _UNICODE
|
|
StringCchCopyW(szStringT, cchLength+1, szStringW);
|
|
szStringT[cchLength] = 0;
|
|
#else
|
|
WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, szStringW, cchLength, szStringT, cchLength, 0, 0);
|
|
szStringT[cchLength] = 0;
|
|
#endif
|
|
}
|
|
|
|
return szStringT;
|
|
}
|
|
/*
|
|
static void NormalizeStreamName(LPTSTR szStreamName)
|
|
{
|
|
LPTSTR szSrc = szStreamName;
|
|
LPTSTR szTrg = szStreamName;
|
|
|
|
// Skip the initial ':'
|
|
if(szSrc[0] == _T(':'))
|
|
szSrc++;
|
|
|
|
// Copy everything until a colon or end of string
|
|
while(szSrc[0] != 0 && szSrc[0] != _T(':'))
|
|
*szTrg++ = *szSrc++;
|
|
*szTrg = 0;
|
|
}
|
|
*/
|
|
static void ItemDataToString(
|
|
LPTSTR szStreamData,
|
|
size_t nMaxChars,
|
|
LPBYTE pbStreamData,
|
|
ULONG cbStreamData,
|
|
NTSTATUS Status)
|
|
{
|
|
LPBYTE pbStreamDataEnd = pbStreamData + cbStreamData;
|
|
LPTSTR szStreamDataEnd = szStreamData + nMaxChars;
|
|
|
|
// Iterate over all data
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
// Preset with default string
|
|
LoadString(g_hInst, IDS_EMPTY_STREAM, szStreamData, (int)nMaxChars);
|
|
|
|
// Fill the stream data
|
|
while(pbStreamData < pbStreamDataEnd)
|
|
{
|
|
// Is there enough space for the text data?
|
|
if(szStreamData + 3 > szStreamDataEnd)
|
|
break;
|
|
|
|
// Print the data bytes
|
|
StringCchPrintf(szStreamData, (szStreamDataEnd - szStreamData), _T("%02X "), (DWORD)pbStreamData[0]);
|
|
szStreamData += 3;
|
|
pbStreamData++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StringCchPrintf(szStreamData, nMaxChars, _T("Error %08X when reading data"), Status);
|
|
}
|
|
}
|
|
|
|
static int InsertStreamToListView(
|
|
HWND hListView,
|
|
LPCTSTR szStreamName,
|
|
LPBYTE pbStreamData,
|
|
DWORD dwStreamType,
|
|
ULONG cbStreamData,
|
|
NTSTATUS Status = STATUS_SUCCESS)
|
|
{
|
|
LVITEM lvi;
|
|
TCHAR szStreamData[0x100];
|
|
|
|
// Insert the item type
|
|
ZeroMemory(&lvi, sizeof(LVITEM));
|
|
lvi.mask = LVIF_TEXT;
|
|
lvi.iItem = 0x7FFFFFFF;
|
|
lvi.iSubItem = 0;
|
|
lvi.pszText = (LPTSTR)StreamTypes[dwStreamType];
|
|
lvi.iItem = ListView_InsertItem(hListView, &lvi);
|
|
lvi.iSubItem++;
|
|
|
|
// Insert the stream name
|
|
lvi.pszText = (LPTSTR)((szStreamName[0] != 0) ? szStreamName : _T("<default>"));
|
|
ListView_SetItem(hListView, &lvi);
|
|
lvi.iSubItem++;
|
|
|
|
// Insert the item data
|
|
ItemDataToString(szStreamData, _countof(szStreamData), pbStreamData, cbStreamData, Status);
|
|
lvi.pszText = (LPTSTR)szStreamData;
|
|
ListView_SetItem(hListView, &lvi);
|
|
|
|
return lvi.iItem;
|
|
}
|
|
|
|
/*
|
|
static TFileStream * CreateStream(PFILE_FULL_EA_INFORMATION pFileEa)
|
|
{
|
|
TFileStream * pStream;
|
|
size_t nSize;
|
|
ULONG i;
|
|
|
|
// Allocate stream
|
|
nSize = sizeof(TFileStream) + (pFileEa->EaNameLength + 1) * sizeof(TCHAR) + pFileEa->EaValueLength;
|
|
pStream = (TFileStream *)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, nSize);
|
|
if(pStream != NULL)
|
|
{
|
|
// Copy the stream name
|
|
pStream->szStreamName = (LPTSTR)&pStream->Buffer;
|
|
for(i = 0; i < pFileEa->EaNameLength; i++)
|
|
pStream->szStreamName[i] = pFileEa->EaName[i];
|
|
pStream->szStreamName[i] = 0;
|
|
|
|
// Copy the EA data
|
|
memcpy(&pStream->szStreamName[i+1], pFileEa->
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|
|
*/
|
|
|
|
NTSTATUS NtCreateFileStream(PUNICODE_STRING SourceName, TFileStream * pStream)
|
|
{
|
|
OBJECT_ATTRIBUTES ObjAttr;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
UNICODE_STRING FileName;
|
|
NTSTATUS Status;
|
|
LPWSTR szStreamName;
|
|
HANDLE FileHandle = NULL;
|
|
|
|
// Create the full name of the stream
|
|
FileName.MaximumLength = SourceName->MaximumLength;
|
|
FileName.Length = SourceName->Length;
|
|
FileName.Buffer = SourceName->Buffer;
|
|
if(pStream->szStreamName[0] != 0)
|
|
{
|
|
szStreamName = (LPWSTR)((LPBYTE)FileName.Buffer + FileName.Length);
|
|
StringCchPrintfW(szStreamName, (FileName.MaximumLength - FileName.Length) / sizeof(WCHAR), L":%s", pStream->szStreamName);
|
|
FileName.Length = (USHORT)(wcslen(FileName.Buffer) * sizeof(WCHAR));
|
|
}
|
|
|
|
// Open the stream
|
|
InitializeObjectAttributes(&ObjAttr, &FileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
Status = NtCreateFile(&FileHandle,
|
|
FILE_WRITE_DATA | SYNCHRONIZE,
|
|
&ObjAttr,
|
|
&IoStatus,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_OPEN_IF,
|
|
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_ALERT,
|
|
NULL,
|
|
0);
|
|
|
|
// Write the stream data
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
Status = NtWriteFile(FileHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatus,
|
|
pStream->pbStreamData,
|
|
pStream->cbStreamData,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
// Close the handle
|
|
if(FileHandle != NULL)
|
|
NtClose(FileHandle);
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS ConvertStreamsToEaList(
|
|
PLIST_ENTRY pHeadEntry,
|
|
PFILE_FULL_EA_INFORMATION * PtrFileEa,
|
|
LPDWORD pcbFileEa)
|
|
{
|
|
PFILE_FULL_EA_INFORMATION pFileEa = NULL;
|
|
PFILE_FULL_EA_INFORMATION pLastEa = NULL;
|
|
PFILE_FULL_EA_INFORMATION pEa = NULL;
|
|
TFileStream * pStream;
|
|
PLIST_ENTRY pListEntry;
|
|
DWORD cbEntryLength = 0;
|
|
DWORD cbEaLength = 0;
|
|
size_t i;
|
|
|
|
// Calculate total size needed for holding all EAs
|
|
for(pListEntry = pHeadEntry->Flink; pListEntry != pHeadEntry; pListEntry = pListEntry->Flink)
|
|
{
|
|
pStream = CONTAINING_RECORD(pListEntry, TFileStream, Entry);
|
|
if(pStream->dwStreamType == STREAM_TYPE_EA)
|
|
{
|
|
cbEntryLength = (DWORD)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + _tcslen(pStream->szStreamName) + 1 + pStream->cbStreamData);
|
|
cbEaLength = cbEaLength + ((cbEntryLength + 7) & ~7);
|
|
}
|
|
}
|
|
|
|
// If there are no EAs, do nothing
|
|
if(cbEaLength != 0)
|
|
{
|
|
// Allocate space for complete EAs
|
|
pFileEa = pEa = (PFILE_FULL_EA_INFORMATION)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, cbEaLength);
|
|
if(pFileEa == NULL)
|
|
return STATUS_INSUFFICIENT_RESOURCES;
|
|
|
|
cbEaLength = 0;
|
|
|
|
// Parse the list again and store all EAs
|
|
for(pListEntry = pHeadEntry->Flink; pListEntry != pHeadEntry; pListEntry = pListEntry->Flink)
|
|
{
|
|
// Get the entry and the next entry
|
|
pStream = CONTAINING_RECORD(pListEntry, TFileStream, Entry);
|
|
if(pStream->dwStreamType == STREAM_TYPE_EA)
|
|
{
|
|
// Convert to the EA
|
|
pEa->NextEntryOffset = 0;
|
|
pEa->Flags = 0;
|
|
pEa->EaNameLength = (UCHAR)_tcslen(pStream->szStreamName);
|
|
pEa->EaValueLength = (USHORT)pStream->cbStreamData;
|
|
|
|
// Copy the file name
|
|
for(i = 0; pStream->szStreamName[i] != 0; i++)
|
|
pEa->EaName[i] = (CHAR)pStream->szStreamName[i];
|
|
pEa->EaName[i] = 0;
|
|
|
|
// Copy the EA data
|
|
memcpy(&pEa->EaName[i + 1], pStream->pbStreamData, pStream->cbStreamData);
|
|
|
|
// Calculate the length of the entry
|
|
cbEntryLength = GetEaEntrySize(pEa);
|
|
cbEaLength += cbEntryLength;
|
|
|
|
// If this is not the last entry, put the next entry offset
|
|
pLastEa = pEa;
|
|
pEa->NextEntryOffset = cbEntryLength;
|
|
pEa = (PFILE_FULL_EA_INFORMATION)((LPBYTE)pEa + cbEntryLength);
|
|
}
|
|
}
|
|
|
|
// Clear next entry offset for the last EA
|
|
if(pLastEa != NULL)
|
|
pLastEa->NextEntryOffset = 0;
|
|
}
|
|
|
|
// Give EA list and its size to the caller
|
|
if(PtrFileEa != NULL)
|
|
PtrFileEa[0] = pFileEa;
|
|
if(pcbFileEa != NULL)
|
|
pcbFileEa[0] = cbEaLength;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
static NTSTATUS LoadStreamFromFile(
|
|
PUNICODE_STRING FileName,
|
|
LPCWSTR szPlainName,
|
|
TFileStream ** ppStream)
|
|
{
|
|
FILE_STANDARD_INFORMATION StdInfo = {0};
|
|
OBJECT_ATTRIBUTES ObjAttr;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
TFileStream * pStream = NULL;
|
|
NTSTATUS Status;
|
|
LPCTSTR szStreamType = NULL;
|
|
LPCTSTR szStreamName = NULL;
|
|
HANDLE FileHandle;
|
|
DWORD dwStreamType = STREAM_TYPE_NONE;
|
|
size_t cchStreamName = 0;
|
|
size_t nSize;
|
|
|
|
// Extract the stream type from the file name
|
|
szStreamType = _tcschr(szPlainName, _T('#'));
|
|
if(szStreamType == NULL)
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
szStreamType++;
|
|
|
|
// Extract the stream name from the file name
|
|
szStreamName = _tcschr(szStreamType, _T('#'));
|
|
if(szStreamName == NULL)
|
|
return STATUS_OBJECT_NAME_INVALID;
|
|
szStreamName++;
|
|
|
|
// Verify the stream types
|
|
if(!_wcsnicmp(szStreamType, L"ADS", 3))
|
|
{
|
|
dwStreamType = STREAM_TYPE_ADS;
|
|
}
|
|
else if(!_wcsnicmp(szStreamType, L"EA", 2))
|
|
{
|
|
dwStreamType = STREAM_TYPE_EA;
|
|
}
|
|
else
|
|
return STATUS_UNSUCCESSFUL;
|
|
|
|
// Open the file and get its size
|
|
InitializeObjectAttributes(&ObjAttr, FileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
Status = NtOpenFile(&FileHandle,
|
|
FILE_READ_DATA | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
|
|
&ObjAttr,
|
|
&IoStatus,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_SYNCHRONOUS_IO_ALERT);
|
|
|
|
// Query the file size
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
Status = NtQueryInformationFile(FileHandle,
|
|
&IoStatus,
|
|
&StdInfo,
|
|
sizeof(FILE_STANDARD_INFORMATION),
|
|
FileStandardInformation);
|
|
}
|
|
|
|
// Check against the maximum stream size
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
if(StdInfo.EndOfFile.HighPart != 0 || StdInfo.EndOfFile.LowPart > MAX_STREAM_LENGTH)
|
|
Status = STATUS_BAD_FILE_TYPE;
|
|
}
|
|
|
|
// Create new stream
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
cchStreamName = _tcslen(szStreamName) + 1;
|
|
nSize = sizeof(TFileStream) + cchStreamName * sizeof(TCHAR) + StdInfo.EndOfFile.LowPart;
|
|
pStream = (TFileStream *)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, nSize);
|
|
if(pStream == NULL)
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
|
|
// Fill the stream structure
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
pStream->szStreamName = (LPTSTR)(pStream + 1);
|
|
StringCchCopy(pStream->szStreamName, cchStreamName, szStreamName);
|
|
|
|
pStream->pbStreamData = (LPBYTE)(pStream->szStreamName + _tcslen(pStream->szStreamName) + 1);
|
|
pStream->cbStreamData = StdInfo.EndOfFile.LowPart;
|
|
pStream->dwStreamType = dwStreamType;
|
|
|
|
Status = NtReadFile(FileHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatus,
|
|
pStream->pbStreamData,
|
|
pStream->cbStreamData,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
RtlFreeHeap(RtlProcessHeap(), 0, pStream);
|
|
pStream = NULL;
|
|
}
|
|
|
|
if(FileHandle != NULL)
|
|
NtClose(FileHandle);
|
|
if(ppStream != NULL)
|
|
*ppStream = pStream;
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS SaveStreamToFile(
|
|
LPTSTR szMainFileName,
|
|
LPCTSTR szStreamName,
|
|
LPBYTE pbStreamData,
|
|
DWORD dwStreamType,
|
|
DWORD dwStreamIndex,
|
|
DWORD cbStreamData)
|
|
{
|
|
OBJECT_ATTRIBUTES ObjAttr;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
UNICODE_STRING FileName;
|
|
NTSTATUS Status;
|
|
HANDLE FileHandle = NULL;
|
|
TCHAR szFileName[MAX_PATH];
|
|
|
|
// Construct the new file name
|
|
StringCchPrintf(szFileName, _countof(szFileName), _T("%s#%s%02u#%s"), szMainFileName, StreamTypes[dwStreamType], dwStreamIndex, szStreamName);
|
|
Status = FileNameToUnicodeString(&FileName, szFileName);
|
|
|
|
// Create the stream file
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
InitializeObjectAttributes(&ObjAttr, &FileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
Status = NtCreateFile(&FileHandle,
|
|
GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
|
|
&ObjAttr,
|
|
&IoStatus,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_OVERWRITE_IF,
|
|
FILE_SYNCHRONOUS_IO_ALERT,
|
|
NULL,
|
|
0);
|
|
}
|
|
|
|
// Write the data
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
Status = NtWriteFile(FileHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatus,
|
|
pbStreamData,
|
|
cbStreamData,
|
|
NULL,
|
|
NULL);
|
|
}
|
|
|
|
if(FileHandle != NULL)
|
|
NtClose(FileHandle);
|
|
return Status;
|
|
}
|
|
|
|
NTSTATUS LoadStreamsFromDirectory(
|
|
PLIST_ENTRY pStreamLinks,
|
|
PUNICODE_STRING SourceName)
|
|
{
|
|
PFILE_DIRECTORY_INFORMATION DirBuffer = NULL;
|
|
PFILE_DIRECTORY_INFORMATION DirEntry = NULL;
|
|
OBJECT_ATTRIBUTES ObjAttr;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
UNICODE_STRING FolderName = {0, 0, NULL};
|
|
UNICODE_STRING FileName;
|
|
UNICODE_STRING FileMask;
|
|
TFileStream * pStream;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
HANDLE DirHandle = NULL;
|
|
LPWSTR szPlainName = NULL;
|
|
ULONG Length = 0x8000;
|
|
|
|
// Now get pointer to plain file name
|
|
szPlainName = wcsrchr(SourceName->Buffer, L'\\');
|
|
if(szPlainName == NULL)
|
|
Status = STATUS_OBJECT_PATH_SYNTAX_BAD;
|
|
else
|
|
szPlainName++;
|
|
|
|
// Now get pointer to directory name and to file name
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
// Get the folder name
|
|
FolderName.MaximumLength = SourceName->MaximumLength;
|
|
FolderName.Length = (USHORT)((szPlainName - SourceName->Buffer) * sizeof(WCHAR));
|
|
FolderName.Buffer = SourceName->Buffer;
|
|
|
|
// Get the file mask
|
|
FileMask.MaximumLength = SourceName->MaximumLength - FolderName.Length;
|
|
FileMask.Length = SourceName->Length - FolderName.Length + 4;
|
|
FileMask.Buffer = SourceName->Buffer + (FolderName.Length / sizeof(WCHAR));
|
|
StringCchCat(szPlainName, SourceName->MaximumLength / sizeof(WCHAR), L"#*");
|
|
|
|
// Open the directory
|
|
InitializeObjectAttributes(&ObjAttr, &FolderName, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
Status = NtOpenFile(&DirHandle,
|
|
FILE_LIST_DIRECTORY | SYNCHRONIZE,
|
|
&ObjAttr,
|
|
&IoStatus,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_SYNCHRONOUS_IO_ALERT);
|
|
}
|
|
|
|
// Query the directory
|
|
while(NT_SUCCESS(Status))
|
|
{
|
|
// Allocate space for directory info
|
|
DirBuffer = DirEntry = (PFILE_DIRECTORY_INFORMATION)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, Length);
|
|
if(DirBuffer == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
// Query the directory
|
|
Status = NtQueryDirectoryFile(DirHandle,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
&IoStatus,
|
|
DirBuffer,
|
|
Length,
|
|
FileDirectoryInformation,
|
|
FALSE,
|
|
&FileMask,
|
|
FALSE);
|
|
if(NT_SUCCESS(Status))
|
|
break;
|
|
|
|
// If the buffer is not enough, reallocate the buffer and do again
|
|
if(Status == STATUS_BUFFER_OVERFLOW || Status == STATUS_BUFFER_TOO_SMALL)
|
|
{
|
|
Length = Length << 1;
|
|
RtlFreeHeap(RtlProcessHeap(), 0, DirBuffer);
|
|
DirBuffer = NULL;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
// Now read each file
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
for(;;)
|
|
{
|
|
// Skip directories
|
|
if((DirEntry->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
|
|
{
|
|
// Construct the full file name for that directory entry
|
|
FileName.MaximumLength = FolderName.MaximumLength;
|
|
FileName.Length = (USHORT)(FolderName.Length + DirEntry->FileNameLength);
|
|
FileName.Buffer = FolderName.Buffer;
|
|
memcpy(szPlainName, DirEntry->FileName, DirEntry->FileNameLength);
|
|
FileName.Buffer[FileName.Length / sizeof(WCHAR)] = 0;
|
|
|
|
// Load that stream from the file
|
|
Status = LoadStreamFromFile(&FileName, szPlainName, &pStream);
|
|
if(!NT_SUCCESS(Status))
|
|
break;
|
|
|
|
// Insert the stream from the list
|
|
InsertTailList(pStreamLinks, &pStream->Entry);
|
|
}
|
|
|
|
// Move to the next directory entry
|
|
if(DirEntry->NextEntryOffset == 0)
|
|
break;
|
|
DirEntry = (PFILE_DIRECTORY_INFORMATION)((LPBYTE)DirEntry + DirEntry->NextEntryOffset);
|
|
}
|
|
}
|
|
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
if(IsListEmpty(pStreamLinks))
|
|
Status = STATUS_NO_SUCH_FILE;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS EaListToListView(
|
|
TFileTestData * pData,
|
|
HWND hDlg,
|
|
HWND hListView,
|
|
PFILE_FULL_EA_INFORMATION EaBuffer,
|
|
bool bExportAsWell)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LPTSTR szEaName;
|
|
LPBYTE pbEaValue;
|
|
DWORD dwStreamIndex = 1;
|
|
|
|
// Iterate over all file EAs and show them/save them
|
|
for(;;)
|
|
{
|
|
// Get the name and value from the EA
|
|
szEaName = StringFromAnsi(EaBuffer->EaName, EaBuffer->EaNameLength);
|
|
pbEaValue = (LPBYTE)(&EaBuffer->EaName[EaBuffer->EaNameLength + 1]);
|
|
|
|
// Fill them into the list view
|
|
InsertStreamToListView(hListView, szEaName, pbEaValue, STREAM_TYPE_EA, EaBuffer->EaValueLength);
|
|
|
|
// If we shall also save to the file, do it
|
|
if(bExportAsWell)
|
|
{
|
|
Status = SaveStreamToFile(pData->szFileName1,
|
|
szEaName,
|
|
pbEaValue,
|
|
STREAM_TYPE_EA,
|
|
dwStreamIndex++,
|
|
EaBuffer->EaValueLength);
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
MessageBoxError(hDlg, IDS_FAILED_TO_SAVE_FILE, RtlNtStatusToDosError(Status), _T("EA"));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Free the converted name
|
|
delete [] szEaName;
|
|
|
|
// Move to tne next entry
|
|
if(EaBuffer->NextEntryOffset == 0)
|
|
break;
|
|
EaBuffer = (PFILE_FULL_EA_INFORMATION)((LPBYTE)EaBuffer + EaBuffer->NextEntryOffset);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
static NTSTATUS StreamsToListView(
|
|
TFileTestData * pData,
|
|
HWND hDlg,
|
|
HWND hListView,
|
|
PFILE_STREAM_INFORMATION StrmBuffer,
|
|
bool bExportAsWell)
|
|
{
|
|
OBJECT_ATTRIBUTES ObjAttr;
|
|
IO_STATUS_BLOCK IoStatus;
|
|
UNICODE_STRING FileName;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
LPCTSTR szFormat;
|
|
TCHAR szFileName[MAX_PATH * 2];
|
|
HANDLE hFile;
|
|
LPTSTR szStreamName;
|
|
LPBYTE pbStreamData;
|
|
DWORD dwFlagsAndAttributes = FILE_SYNCHRONOUS_IO_ALERT;
|
|
DWORD cbStreamData;
|
|
DWORD dwStreamIndex = 1;
|
|
|
|
//
|
|
// If the parent file was open with FILE_OPEN_REPARSE_POINT,
|
|
// we need to propagate this flag to the streams too.
|
|
// This allows us to open file created with alternate streams that are links to non existing file
|
|
//
|
|
// Example: mklink.exe C:\sample_file.exe:byebye \??\nul
|
|
//
|
|
|
|
dwFlagsAndAttributes |= (pData->OpenFile.dwFlagsAndAttributes & FILE_FLAG_OPEN_REPARSE_POINT);
|
|
dwFlagsAndAttributes |= (pData->OpenFile.dwCreateOptions & FILE_OPEN_REPARSE_POINT);
|
|
|
|
// Iterate over all file streams and show them/save them
|
|
for(;;)
|
|
{
|
|
// Get the name and value from the EA
|
|
szStreamName = StringFromWide(StrmBuffer->StreamName, StrmBuffer->StreamNameLength / sizeof(WCHAR));
|
|
// NormalizeStreamName(szStreamName);
|
|
|
|
// Get the stream size
|
|
cbStreamData = StrmBuffer->StreamSize.LowPart;
|
|
if(StrmBuffer->StreamSize.QuadPart > MAX_STREAM_LENGTH)
|
|
cbStreamData = StrmBuffer->StreamSize.LowPart;
|
|
|
|
// Allocate buffer for stream data
|
|
pbStreamData = (LPBYTE)RtlAllocateHeap(RtlProcessHeap(), 0, cbStreamData);
|
|
if(pbStreamData != NULL)
|
|
{
|
|
// Prepare the ADS name
|
|
szFormat = (szStreamName[0] != 0) ? _T("%s%s") : _T("%s");
|
|
StringCchPrintf(szFileName, _countof(szFileName), szFormat, pData->szFileName1, szStreamName);
|
|
Status = FileNameToUnicodeString(&FileName, szFileName);
|
|
if(!NT_SUCCESS(Status))
|
|
break;
|
|
|
|
// Open the ADS
|
|
InitializeObjectAttributes(&ObjAttr, &FileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
Status = NtOpenFile(&hFile,
|
|
FILE_READ_DATA | SYNCHRONIZE,
|
|
&ObjAttr,
|
|
&IoStatus,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
dwFlagsAndAttributes);
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
// Read the data from the stream
|
|
Status = NtReadFile(hFile, NULL, NULL, NULL, &IoStatus, pbStreamData, cbStreamData, NULL, NULL);
|
|
NtClose(hFile);
|
|
}
|
|
|
|
// Fill them into the list view
|
|
InsertStreamToListView(hListView, szStreamName, pbStreamData, STREAM_TYPE_ADS, cbStreamData, Status);
|
|
FreeFileNameString(&FileName);
|
|
|
|
// If we shall also save to the file, do it
|
|
if(NT_SUCCESS(Status) && bExportAsWell)
|
|
{
|
|
Status = SaveStreamToFile(pData->szFileName1,
|
|
szStreamName,
|
|
pbStreamData,
|
|
STREAM_TYPE_ADS,
|
|
dwStreamIndex++,
|
|
cbStreamData);
|
|
if(!NT_SUCCESS(Status))
|
|
{
|
|
MessageBoxError(hDlg, IDS_FAILED_TO_SAVE_FILE, RtlNtStatusToDosError(Status), _T("ADS"));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Free the converted name
|
|
delete [] szStreamName;
|
|
|
|
// Move to tne next entry
|
|
if(StrmBuffer->NextEntryOffset == 0)
|
|
break;
|
|
StrmBuffer = (PFILE_STREAM_INFORMATION)((LPBYTE)StrmBuffer + StrmBuffer->NextEntryOffset);
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Message handlers
|
|
|
|
static int OnInitDialog(HWND hDlg, LPARAM lParam)
|
|
{
|
|
PROPSHEETPAGE * pPage = (PROPSHEETPAGE *)lParam;
|
|
TFileTestData * pData = (TFileTestData *)pPage->lParam;
|
|
HWND hListView = GetDlgItem(hDlg, IDC_STREAMS_LIST);
|
|
|
|
UNREFERENCED_PARAMETER(lParam);
|
|
UNREFERENCED_PARAMETER(hDlg);
|
|
|
|
SetDialogData(hDlg, pPage->lParam);
|
|
|
|
// Initialize the listview
|
|
ListView_CreateColumns(hListView, Columns);
|
|
ListView_SetExtendedListViewStyle(hListView, LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT);
|
|
|
|
// Configure dialog resizing
|
|
if(pData->bEnableResizing)
|
|
{
|
|
pAnchors = new TAnchors(hDlg);
|
|
pAnchors->AddAnchor(hDlg, IDC_STREAMS_TITLE, akLeft | akTop | akRight);
|
|
pAnchors->AddAnchor(hDlg, IDC_STREAMS_LIST, akAll);
|
|
pAnchors->AddAnchor(hDlg, IDC_QUERY_STREAMS, akLeft | akBottom);
|
|
pAnchors->AddAnchor(hDlg, IDC_EXPORT_STREAMS, akLeftCenter | akBottom);
|
|
pAnchors->AddAnchor(hDlg, IDC_IMPORT_STREAMS, akRight | akBottom);
|
|
pAnchors->AddAnchor(hDlg, IDC_RESULT_FRAME, akLeft | akRight | akBottom);
|
|
pAnchors->AddAnchor(hDlg, IDC_ERROR_CODE_TITLE, akLeft | akBottom);
|
|
pAnchors->AddAnchor(hDlg, IDC_ERROR_CODE, akLeft | akRight | akBottom);
|
|
pAnchors->AddAnchor(hDlg, IDC_INFORMATION_TITLE, akLeft | akBottom);
|
|
pAnchors->AddAnchor(hDlg, IDC_INFORMATION, akLeft | akRight | akBottom);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int OnSetActive(HWND hDlg)
|
|
{
|
|
TFileTestData * pData = GetDialogData(hDlg);
|
|
BOOL bEnabled;
|
|
|
|
if((pData->OpenFile.dwCreateOptions & FILE_OPEN_BY_FILE_ID) == 0)
|
|
{
|
|
SetDlgItemText(hDlg, IDC_FILE_NAME, pData->szFileName1);
|
|
ConvertToWin32Name(hDlg, IDC_FILE_NAME);
|
|
}
|
|
|
|
// Enable/disable the buttons
|
|
bEnabled = (IsHandleValid(pData->hFile)) ? TRUE : FALSE;
|
|
EnableDlgItems(hDlg, bEnabled, IDC_QUERY_STREAMS, IDC_EXPORT_STREAMS, IDC_IMPORT_STREAMS, 0);
|
|
|
|
bEnabled = (IsHandleInvalid(pData->hFile) && pData->szFileName1[0] != 0) ? TRUE : FALSE;
|
|
EnableDlgItems(hDlg, bEnabled, IDC_IMPORT_STREAMS, 0);
|
|
return TRUE;
|
|
}
|
|
|
|
static int OnExportStreams(HWND hDlg, bool bExportAsWell)
|
|
{
|
|
PFILE_FULL_EA_INFORMATION EaBuffer = NULL;
|
|
PFILE_STREAM_INFORMATION StrmBuffer = NULL;
|
|
FILE_EA_INFORMATION FileEaInfo;
|
|
TFileTestData * pData = GetDialogData(hDlg);
|
|
IO_STATUS_BLOCK IoStatus = {0};
|
|
NTSTATUS Status;
|
|
ULONG StrmBufferSize = sizeof(FILE_STREAM_INFORMATION);
|
|
ULONG EaBufferSize;
|
|
HWND hListView = GetDlgItem(hDlg, IDC_STREAMS_LIST);
|
|
|
|
// Delete all items from the list view
|
|
ListView_DeleteAllItems(hListView);
|
|
|
|
// Retrieve the initial guess of the EAs on the file
|
|
Status = NtQueryInformationFile(pData->hFile, &IoStatus, &FileEaInfo, sizeof(FILE_EA_INFORMATION), FileEaInformation);
|
|
EaBufferSize = FileEaInfo.EaSize;
|
|
|
|
// Query the extended attributes file
|
|
while(Status == STATUS_SUCCESS)
|
|
{
|
|
// Allocate buffer for extended attributes
|
|
EaBuffer = (PFILE_FULL_EA_INFORMATION)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, EaBufferSize);
|
|
if(EaBuffer == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
// Try to query the extended attributes
|
|
Status = NtQueryEaFile(pData->hFile,
|
|
&IoStatus,
|
|
EaBuffer,
|
|
EaBufferSize,
|
|
FALSE,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
TRUE);
|
|
if(NT_SUCCESS(Status) || Status == STATUS_NO_EAS_ON_FILE)
|
|
break;
|
|
|
|
// If the buffer is not enough, reallocate the buffer and do again
|
|
if(Status == STATUS_BUFFER_OVERFLOW || Status == STATUS_BUFFER_TOO_SMALL)
|
|
{
|
|
EaBufferSize = EaBufferSize << 1;
|
|
RtlFreeHeap(RtlProcessHeap(), 0, EaBuffer);
|
|
EaBuffer = NULL;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
// Put the EAs in the list view
|
|
if(NT_SUCCESS(Status) && EaBuffer != NULL)
|
|
EaListToListView(pData, hDlg, hListView, EaBuffer, bExportAsWell);
|
|
Status = STATUS_SUCCESS;
|
|
|
|
// Now query the stream list
|
|
while(Status == STATUS_SUCCESS)
|
|
{
|
|
// Allocate buffer for extended attributes
|
|
StrmBuffer = (PFILE_STREAM_INFORMATION)RtlAllocateHeap(RtlProcessHeap(), HEAP_ZERO_MEMORY, StrmBufferSize);
|
|
if(StrmBuffer == NULL)
|
|
{
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
break;
|
|
}
|
|
|
|
// Try to query the extended attributes
|
|
Status = NtQueryInformationFile(pData->hFile,
|
|
&IoStatus,
|
|
StrmBuffer,
|
|
StrmBufferSize,
|
|
FileStreamInformation);
|
|
if(NT_SUCCESS(Status))
|
|
break;
|
|
|
|
// If the buffer is not enough, reallocate the buffer and do again
|
|
if(Status == STATUS_BUFFER_OVERFLOW || Status == STATUS_BUFFER_TOO_SMALL)
|
|
{
|
|
StrmBufferSize = StrmBufferSize << 1;
|
|
RtlFreeHeap(RtlProcessHeap(), 0, StrmBuffer);
|
|
StrmBuffer = NULL;
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
// If succeeded, we insert the stream list to the list view
|
|
if(NT_SUCCESS(Status) && StrmBuffer != NULL)
|
|
StreamsToListView(pData, hDlg, hListView, StrmBuffer, bExportAsWell);
|
|
|
|
// Set the status info
|
|
SetResultInfo(hDlg, RSI_NTSTATUS | RSI_NOINFO, Status);
|
|
|
|
// Free buffers
|
|
if(StrmBuffer != NULL)
|
|
RtlFreeHeap(RtlProcessHeap(), 0, StrmBuffer);
|
|
if(EaBuffer != NULL)
|
|
RtlFreeHeap(RtlProcessHeap(), 0, EaBuffer);
|
|
return TRUE;
|
|
}
|
|
|
|
static int OnImportStreams(HWND hDlg)
|
|
{
|
|
PFILE_FULL_EA_INFORMATION pFileEa = NULL;
|
|
OBJECT_ATTRIBUTES ObjAttr = {0};
|
|
IO_STATUS_BLOCK IoStatus;
|
|
TFileTestData * pData = GetDialogData(hDlg);
|
|
UNICODE_STRING FileName;
|
|
TFileStream * pStream;
|
|
PLIST_ENTRY pHeadEntry;
|
|
PLIST_ENTRY pListEntry;
|
|
LIST_ENTRY StreamLinks;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
HANDLE FileHandle = NULL;
|
|
DWORD cbFileEa = 0;
|
|
HWND hListView = GetDlgItem(hDlg, IDC_STREAMS_LIST);
|
|
DWORD dwErrCode = ERROR_SUCCESS;
|
|
|
|
// Delete all items from the list view
|
|
ListView_DeleteAllItems(hListView);
|
|
InitializeListHead(&StreamLinks);
|
|
pHeadEntry = &StreamLinks;
|
|
|
|
// Prepare the file name
|
|
InitializeObjectAttributes(&ObjAttr, &FileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
Status = FileNameToUnicodeString(&FileName, pData->szFileName1);
|
|
|
|
// Now reallocate the buffer so we have enough space
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
PWSTR NewBuffer;
|
|
ULONG NewMaxLengh = (ULONG)FileName.MaximumLength + 255;
|
|
|
|
// Don't exceet the maximum file name length
|
|
if(NewMaxLengh > 0xFFFE)
|
|
NewMaxLengh = 0xFFFE;
|
|
NewBuffer = (PWSTR)RtlReAllocateHeap(RtlProcessHeap(), 0, FileName.Buffer, NewMaxLengh);
|
|
|
|
// If the allocation failed, we need to free the old buffer
|
|
if(NewBuffer != NULL)
|
|
{
|
|
FileName.MaximumLength = (USHORT)NewMaxLengh;
|
|
FileName.Buffer = NewBuffer;
|
|
}
|
|
else
|
|
{
|
|
RtlFreeHeap(RtlProcessHeap(), 0, FileName.Buffer);
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
FileName.Buffer = NULL;
|
|
}
|
|
}
|
|
|
|
// Query the directory and load all streams from all files
|
|
if(NT_SUCCESS(Status))
|
|
Status = LoadStreamsFromDirectory(&StreamLinks, &FileName);
|
|
|
|
// Delete the file
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
Status = NtDeleteFile(&ObjAttr);
|
|
if(Status == STATUS_OBJECT_NAME_NOT_FOUND || dwErrCode == STATUS_OBJECT_PATH_INVALID)
|
|
Status = STATUS_SUCCESS;
|
|
}
|
|
|
|
// Now we have to create the main data stream
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
// Write all ADSs to the file
|
|
for(pListEntry = pHeadEntry->Flink; pListEntry != pHeadEntry; pListEntry = pListEntry->Flink)
|
|
{
|
|
// Get the file stream
|
|
pStream = CONTAINING_RECORD(pListEntry, TFileStream, Entry);
|
|
|
|
// Write the entry. After this, the entry no longer exists!!!
|
|
if(pStream->dwStreamType == STREAM_TYPE_ADS)
|
|
{
|
|
Status = NtCreateFileStream(&FileName, pStream);
|
|
if(!NT_SUCCESS(Status))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now open the file
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
Status = NtCreateFile(&FileHandle,
|
|
FILE_WRITE_EA | SYNCHRONIZE,
|
|
&ObjAttr,
|
|
&IoStatus,
|
|
NULL,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
FILE_OPEN_IF,
|
|
FILE_SYNCHRONOUS_IO_ALERT,
|
|
NULL,
|
|
0);
|
|
}
|
|
|
|
// Convert the remaining streams to list of extended attributes
|
|
if(NT_SUCCESS(Status))
|
|
{
|
|
Status = ConvertStreamsToEaList(&StreamLinks, &pFileEa, &cbFileEa);
|
|
}
|
|
|
|
// Now apply the EAs to the file
|
|
if(NT_SUCCESS(Status) && pFileEa && cbFileEa)
|
|
{
|
|
Status = NtSetEaFile(FileHandle, &IoStatus, pFileEa, cbFileEa);
|
|
}
|
|
|
|
// Set the streams to the list view
|
|
while(!IsListEmpty(&StreamLinks))
|
|
{
|
|
// Get the file stream
|
|
pStream = CONTAINING_RECORD(StreamLinks.Flink, TFileStream, Entry);
|
|
|
|
// Insert the stream to the listview
|
|
if(dwErrCode == ERROR_SUCCESS)
|
|
{
|
|
InsertStreamToListView(hListView, pStream->szStreamName,
|
|
pStream->pbStreamData,
|
|
pStream->dwStreamType,
|
|
pStream->cbStreamData);
|
|
}
|
|
|
|
// Free the stream structure
|
|
RemoveEntryList(&pStream->Entry);
|
|
RtlFreeHeap(RtlProcessHeap(), 0, pStream);
|
|
}
|
|
|
|
// Close the file
|
|
if(FileHandle != NULL)
|
|
NtClose(FileHandle);
|
|
FreeFileNameString(&FileName);
|
|
|
|
// Set the result of the operation
|
|
SetResultInfo(hDlg, RSI_NTSTATUS | RSI_NOINFO, Status);
|
|
return TRUE;
|
|
}
|
|
|
|
static int OnCommand(HWND hDlg, UINT nNotify, UINT nIDCtrl)
|
|
{
|
|
if(nNotify == BN_CLICKED)
|
|
{
|
|
switch(nIDCtrl)
|
|
{
|
|
case IDC_QUERY_STREAMS:
|
|
return OnExportStreams(hDlg, false);
|
|
|
|
case IDC_EXPORT_STREAMS:
|
|
return OnExportStreams(hDlg, true);
|
|
|
|
case IDC_IMPORT_STREAMS:
|
|
return OnImportStreams(hDlg);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int OnNotify(HWND hDlg, NMHDR * pNMHDR)
|
|
{
|
|
switch(pNMHDR->code)
|
|
{
|
|
case PSN_SETACTIVE:
|
|
return OnSetActive(hDlg);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Public functions
|
|
|
|
INT_PTR CALLBACK PageProc11(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
// Handlers specific to our dialog
|
|
switch(uMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
OnInitDialog(hDlg, lParam);
|
|
return TRUE;
|
|
|
|
case WM_SIZE:
|
|
case WM_GETMINMAXINFO:
|
|
return pAnchors->OnMessage(uMsg, wParam, lParam);
|
|
|
|
case WM_COMMAND:
|
|
return OnCommand(hDlg, HIWORD(wParam), LOWORD(wParam));
|
|
|
|
case WM_NOTIFY:
|
|
return OnNotify(hDlg, (NMHDR *)lParam);
|
|
|
|
case WM_DESTROY:
|
|
if(pAnchors != NULL)
|
|
delete pAnchors;
|
|
pAnchors = NULL;
|
|
return FALSE;
|
|
}
|
|
return FALSE;
|
|
}
|