diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..6fbed0cf7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "C_Cpp.clang_format_style": "file", + "C_Cpp.clang_format_fallbackStyle": "Microsoft", + "C_Cpp.clang_format_path": "${workspaceFolder}/node_modules/clang-format/bin/win32/clang-format.exe", + "[cpp]": { + "editor.defaultFormatter": "ms-vscode.cpptools", + "editor.formatOnSave": true + }, + "[c]": { + "editor.defaultFormatter": "ms-vscode.cpptools", + "editor.formatOnSave": true + } +} diff --git a/UI/file/exporter.cpp b/UI/file/exporter.cpp index 65e20ecff..34452030a 100644 --- a/UI/file/exporter.cpp +++ b/UI/file/exporter.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -23,15 +24,15 @@ namespace file dialog::editor::CEditor MyData( pParentWnd, IDS_SAVEMESSAGETOFILE, IDS_SAVEMESSAGETOFILEPROMPT, CEDITOR_BUTTON_OK | CEDITOR_BUTTON_CANCEL); - UINT uidDropDown[] = { - IDS_DDTEXTFILE, - IDS_DDMSGFILEANSI, - IDS_DDMSGFILEUNICODE, - IDS_DDEMLFILE, - IDS_DDEMLFILEUSINGICONVERTERSESSION, - IDS_DDTNEFFILE}; - MyData.AddPane( - viewpane::DropDownPane::Create(0, IDS_FORMATTOSAVEMESSAGE, _countof(uidDropDown), uidDropDown, true)); + std::vector uidDropDown{IDS_DDTEXTFILE, IDS_DDMSGFILEANSI, IDS_DDMSGFILEUNICODE}; + if (registry::enableLegacyFeatures) + { + uidDropDown.push_back(IDS_DDEMLFILEUSINGICONVERTERSESSION); + } + uidDropDown.push_back(IDS_DDTNEFFILE); + + MyData.AddPane(viewpane::DropDownPane::Create( + 0, IDS_FORMATTOSAVEMESSAGE, static_cast(uidDropDown.size()), uidDropDown.data(), true)); if (bMultiSelect) { MyData.AddPane(viewpane::CheckPane::Create(1, IDS_EXPORTPROMPTLOCATION, false, false)); @@ -39,7 +40,25 @@ namespace file if (!MyData.DisplayDialog()) return false; - exportType = static_cast(MyData.GetDropDown(0)); + switch (MyData.GetDropDownValue(0)) + { + case IDS_DDTEXTFILE: + exportType = exportType::text; + break; + case IDS_DDMSGFILEANSI: + exportType = exportType::msgAnsi; + break; + case IDS_DDMSGFILEUNICODE: + exportType = exportType::msgUnicode; + break; + case IDS_DDEMLFILEUSINGICONVERTERSESSION: + exportType = exportType::emlIConverter; + break; + case IDS_DDTNEFFILE: + exportType = exportType::tnef; + break; + } + bPrompt = !bMultiSelect || MyData.GetCheck(1); switch (exportType) @@ -55,7 +74,6 @@ namespace file szDotExt = L".msg"; szFilter = strings::loadstring(IDS_MSGFILES); break; - case exportType::eml: case exportType::emlIConverter: szExt = L"eml"; szDotExt = L".eml"; @@ -110,9 +128,6 @@ namespace file case exportType::msgUnicode: return EC_H(file::SaveToMSG(lpMessage, filename, true, pParentWnd->GetSafeHwnd(), true)); break; - case exportType::eml: - return EC_H(file::SaveToEML(lpMessage, filename)); - break; case exportType::emlIConverter: { auto ulConvertFlags = CCSF_SMTP; @@ -146,4 +161,4 @@ namespace file return hRes; } -} // namespace file \ No newline at end of file +} // Namespace file \ No newline at end of file diff --git a/UI/file/exporter.h b/UI/file/exporter.h index f02d16b42..2bc0c155e 100644 --- a/UI/file/exporter.h +++ b/UI/file/exporter.h @@ -7,9 +7,8 @@ namespace file text = 0, msgAnsi = 1, msgUnicode = 2, - eml = 3, - emlIConverter = 4, - tnef = 5 + emlIConverter = 3, + tnef = 4 }; class exporter diff --git a/core/mapi/mapiFile.cpp b/core/mapi/mapiFile.cpp index 4a20a0a14..a14b827f6 100644 --- a/core/mapi/mapiFile.cpp +++ b/core/mapi/mapiFile.cpp @@ -326,41 +326,6 @@ namespace file return hRes; } - _Check_return_ HRESULT SaveToEML(_In_ LPMESSAGE lpMessage, _In_ const std::wstring& szFileName) - { - LPSTREAM pStrmSrc = nullptr; - - if (!lpMessage || szFileName.empty()) return MAPI_E_INVALID_PARAMETER; - output::DebugPrint(output::dbgLevel::Generic, L"SaveToEML: Saving message to \"%ws\"\n", szFileName.c_str()); - - // Open the property of the attachment - // containing the file data - auto hRes = EC_MAPI(lpMessage->OpenProperty( - PR_INTERNET_CONTENT, // TODO: There's a modern property for this... - const_cast(&IID_IStream), - 0, - NULL, // MAPI_MODIFY is not needed - reinterpret_cast(&pStrmSrc))); - if (FAILED(hRes)) - { - if (hRes == MAPI_E_NOT_FOUND) - { - output::DebugPrint(output::dbgLevel::Generic, L"No internet content found\n"); - } - } - else - { - if (pStrmSrc) - { - hRes = WC_H(WriteStreamToFile(pStrmSrc, szFileName)); - - pStrmSrc->Release(); - } - } - - return hRes; - } - _Check_return_ HRESULT STDAPICALLTYPE MyStgCreateStorageEx( _In_ const std::wstring& pName, DWORD grfMode, diff --git a/core/mapi/mapiFile.h b/core/mapi/mapiFile.h index 2ab593617..14b019cef 100644 --- a/core/mapi/mapiFile.h +++ b/core/mapi/mapiFile.h @@ -15,7 +15,6 @@ namespace file bool bAssoc, bool bUnicode, HWND hWnd); - _Check_return_ HRESULT SaveToEML(_In_ LPMESSAGE lpMessage, _In_ const std::wstring& szFileName); _Check_return_ HRESULT CreateNewMSG( _In_ const std::wstring& szFileName, bool bUnicode, diff --git a/core/res/MFCMapi.rc2 b/core/res/MFCMapi.rc2 index 6858b92b3..185cbe53d 100644 --- a/core/res/MFCMapi.rc2 +++ b/core/res/MFCMapi.rc2 @@ -995,6 +995,7 @@ IDS_REGKEY_FORCEOUTLOOKMAPI "Force the use of Outlook's MAPI files" IDS_REGKEY_FORCESYSTEMMAPI "Force the use of the system's MAPI files" IDS_REGKEY_PREFER_OLMAPI32 "Prefer olmapi32.dll over other MAPI files" IDS_REGKEY_UIDIAG "Show UI diagnostics" +IDS_REGKEY_ENABLE_LEGACY_FEATURES "Enable legacy features" // RestrictEditor.cpp IDS_RELOP "relop" @@ -1200,10 +1201,9 @@ IDS_DDDISPLAYPROPSONLY "Only display properties from MSG file" IDS_DDENTERFORMCLASS "Manually enter form class" IDS_DDFOLDERFORMLIBRARY "Folder Form library" IDS_DDORGFORMLIBRARY "Organization Forms and Application Forms libraries" -IDS_DDTEXTFILE "Text file (saves all properties of message to a text file)" +IDS_DDTEXTFILE "XML file (saves all properties of message to an XML file)" IDS_DDMSGFILEANSI "MSG file (ANSI)" IDS_DDMSGFILEUNICODE "MSG file (UNICODE)" -IDS_DDEMLFILE "EML file (using PR_INTERNET_CONTENT)" IDS_DDEMLFILEUSINGICONVERTERSESSION "EML file (using IConverterSession)" IDS_DDTNEFFILE "TNEF file (same format as winmail.dat)" IDS_DDCOPYPROPS "IMAPIProp::CopyProps" diff --git a/core/res/Resource.h b/core/res/Resource.h index d6007cc2c..6a2c79487 100644 --- a/core/res/Resource.h +++ b/core/res/Resource.h @@ -807,7 +807,6 @@ #define IDS_DDORGFORMLIBRARY 35246 #define IDS_DDTEXTFILE 35247 #define IDS_DDMSGFILEANSI 35248 -#define IDS_DDEMLFILE 35249 #define IDS_DDTNEFFILE 35250 #define IDS_DDCOPYPROPS 35251 #define IDS_DDGETSETPROPS 35252 @@ -1235,3 +1234,4 @@ #define IDS_CAPABILITIES_FOLDER 35872 #define IDS_CAPABILITIES_RESTRICTION 35873 #define IDS_REGKEY_PREFER_OLMAPI32 35874 +#define IDS_REGKEY_ENABLE_LEGACY_FEATURES 35875 diff --git a/core/utility/output.cpp b/core/utility/output.cpp index 28563e97c..93c184705 100644 --- a/core/utility/output.cpp +++ b/core/utility/output.cpp @@ -15,7 +15,10 @@ namespace output { - std::wstring g_szXMLHeader = L"\n"; + // Files are opened with the CRT "ccs=UNICODE" mode, which writes a UTF-16 LE BOM + // and encodes each character as two little-endian bytes. The declared encoding + // must match, otherwise XML parsers reject the file with an encoding mismatch. + std::wstring g_szXMLHeader = L"\n"; std::function outputToDbgView; FILE* g_fDebugFile = nullptr; diff --git a/core/utility/registry.cpp b/core/utility/registry.cpp index 09f268c1f..0aefa4dc2 100644 --- a/core/utility/registry.cpp +++ b/core/utility/registry.cpp @@ -67,6 +67,7 @@ namespace registry boolRegKey preferOlmapi32{L"PreferOlmapi32", true, false, IDS_REGKEY_PREFER_OLMAPI32}; boolRegKey uiDiag{L"UIDiag", false, false, IDS_REGKEY_UIDIAG}; boolRegKey displayAboutDialog{L"DisplayAboutDialog", true, false, NULL}; + boolRegKey enableLegacyFeatures{L"EnableLegacyFeatures", false, false, IDS_REGKEY_ENABLE_LEGACY_FEATURES}; wstringRegKey propertyColumnOrder{L"PropertyColumnOrder", L"", false, NULL}; dwordRegKey namedPropBatchSize{L"NamedPropBatchSize", regOptionType::stringDec, 400, false, NULL}; @@ -101,6 +102,7 @@ namespace registry &preferOlmapi32, &uiDiag, &displayAboutDialog, + &enableLegacyFeatures, &propertyColumnOrder, &namedPropBatchSize}; diff --git a/core/utility/registry.h b/core/utility/registry.h index 338ffe57c..14fcd11fd 100644 --- a/core/utility/registry.h +++ b/core/utility/registry.h @@ -198,6 +198,7 @@ namespace registry extern boolRegKey preferOlmapi32; extern boolRegKey uiDiag; extern boolRegKey displayAboutDialog; + extern boolRegKey enableLegacyFeatures; extern wstringRegKey propertyColumnOrder; extern dwordRegKey namedPropBatchSize; } // namespace registry \ No newline at end of file diff --git a/mapistub b/mapistub index ccaa6ada0..679c14abf 160000 --- a/mapistub +++ b/mapistub @@ -1 +1 @@ -Subproject commit ccaa6ada037418453c3f762f607e7bf8f1b844a5 +Subproject commit 679c14abf04fcf094704169917004057694634ea diff --git a/package.json b/package.json index aa5b26631..3a544330f 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,9 @@ "test:debug:arm64": "npm run vstest -- bin/ARM64/UnitTest/Debug_Unicode/UnitTest.dll /logger:console", "test:release:arm64": "npm run vstest -- bin/ARM64/UnitTest/Release_Unicode/UnitTest.dll /logger:console", "test:debug:ansi:arm64": "npm run vstest -- bin/ARM64/UnitTest/Debug/UnitTest.dll /logger:console", - "test:release:ansi:arm64": "npm run vstest -- bin/ARM64/UnitTest/Release/UnitTest.dll /logger:console" + "test:release:ansi:arm64": "npm run vstest -- bin/ARM64/UnitTest/Release/UnitTest.dll /logger:console", + "format": "pwsh scripts/clang.ps1", + "format:check": "pwsh scripts/clang.ps1 -Check" }, "devDependencies": { "clang-format": "^1.8.0" diff --git a/scripts/clang.ps1 b/scripts/clang.ps1 new file mode 100644 index 000000000..f5616a322 --- /dev/null +++ b/scripts/clang.ps1 @@ -0,0 +1,56 @@ +param( + [switch]$Check, + [string[]]$Files +) + +$projectRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +$clang = Join-Path $projectRoot "node_modules\clang-format\bin\win32\clang-format.exe" + +if (-not (Test-Path $clang)) { + Write-Error "clang-format not found at $clang. Run 'npm install' first." + exit 1 +} + +Write-Host "clang-format found at $clang" +& $clang --version + +Push-Location $projectRoot + +if ($Files) { + # Resolve provided paths to absolute + $files = $Files | ForEach-Object { Resolve-Path $_ | Select-Object -ExpandProperty Path } +} else { + $files = Get-ChildItem -Recurse -Include *.cpp, *.h, *.c | + Where-Object { $_.FullName -notlike "*\mapistub\*" -and $_.FullName -notlike "*\.git\*" } | + Select-Object -ExpandProperty FullName +} + +if (-not $files) { + Write-Host "No C/C++ files found." + Pop-Location + exit 0 +} + +Write-Host "Found $($files.Count) files." + +# Batch into chunks of 30 to avoid command-line length limits +$chunkSize = 30 +$exitCode = 0 + +$baseArgs = @('--style=file', '--fallback-style=Microsoft', '--verbose') +if ($Check) { + Write-Host "Checking formatting..." + $baseArgs += @('--dry-run', '-Werror') +} else { + Write-Host "Formatting files..." + $baseArgs += '-i' +} + +for ($i = 0; $i -lt $files.Count; $i += $chunkSize) { + $chunk = $files[$i..([Math]::Min($i + $chunkSize - 1, $files.Count - 1))] + & $clang @baseArgs @chunk + if ($LASTEXITCODE -ne 0) { $exitCode = $LASTEXITCODE } +} + +Pop-Location +exit $exitCode