VeraCrypt
aboutsummaryrefslogtreecommitdiff
path: root/src/Format/InPlace.c
diff options
context:
space:
mode:
authorMounir IDRASSI <mounir.idrassi@idrix.fr>2015-05-17 12:14:58 +0200
committerMounir IDRASSI <mounir.idrassi@idrix.fr>2015-05-17 12:16:16 +0200
commitf72125ea71f98d0147bb742b48d1097844949c9e (patch)
tree836b63959937bb846553f7f851be9e5e405c1ea0 /src/Format/InPlace.c
parent4695920b41e969bed138bfe2fe0d877b2abb36f6 (diff)
downloadVeraCrypt-f72125ea71f98d0147bb742b48d1097844949c9e.tar.gz
VeraCrypt-f72125ea71f98d0147bb742b48d1097844949c9e.zip
Windows: First implementation of non-system volumes decryption.
Diffstat (limited to 'src/Format/InPlace.c')
-rw-r--r--src/Format/InPlace.c578
1 files changed, 570 insertions, 8 deletions
diff --git a/src/Format/InPlace.c b/src/Format/InPlace.c
index 3998c2a5..408f6252 100644
--- a/src/Format/InPlace.c
+++ b/src/Format/InPlace.c
@@ -40,6 +40,18 @@ IMPORTANT: Due to this issue, functions in this file must not directly interact
using namespace std;
using namespace VeraCrypt;
+#if TC_VOLUME_DATA_OFFSET != 131072
+# error TC_VOLUME_DATA_OFFSET != 131072
+#endif
+
+#if TC_VOLUME_HEADER_EFFECTIVE_SIZE != 512
+# error TC_VOLUME_HEADER_EFFECTIVE_SIZE != 512
+#endif
+
+#if TC_TOTAL_VOLUME_HEADERS_SIZE != 262144
+# error TC_TOTAL_VOLUME_HEADERS_SIZE != 262144
+#endif
+
#define TC_MAX_NONSYS_INPLACE_ENC_WORK_CHUNK_SIZE (2048 * BYTES_PER_KB)
#define TC_INITIAL_NTFS_CONCEAL_PORTION_SIZE (2 * TC_MAX_VOLUME_SECTOR_SIZE)
#define TC_NTFS_CONCEAL_CONSTANT 0xFF
@@ -312,6 +324,42 @@ BOOL CheckRequirementsForNonSysInPlaceEnc (HWND hwndDlg, const char *devicePath,
return TRUE;
}
+BOOL CheckRequirementsForNonSysInPlaceDec (HWND hwndDlg, const char *devicePath, BOOL silent)
+{
+ int partitionNumber = -1, driveNumber = -1;
+
+ /* ---------- Checks that do not require admin rights ----------- */
+
+ /* Volume type (must be a partition or a dynamic volume) */
+ if ((sscanf (devicePath, "\\Device\\HarddiskVolume%d", &partitionNumber) != 1
+ && sscanf (devicePath, "\\Device\\Harddisk%d\\Partition%d", &driveNumber, &partitionNumber) != 2)
+ || partitionNumber == 0)
+ {
+ if (!silent)
+ Error ("INPLACE_ENC_INVALID_PATH", hwndDlg);
+
+ return FALSE;
+ }
+
+
+ /* Admin rights */
+ if (!IsAdmin())
+ {
+ // We rely on the wizard process to call us only when the whole wizard process has been elevated (so UAC
+ // status can be ignored). In case the IsAdmin() detection somehow fails, we allow the user to continue.
+
+ if (!silent)
+ Warning ("ADMIN_PRIVILEGES_WARN_DEVICES", hwndDlg);
+ }
+
+
+ /* ---------- Checks that may require admin rights ----------- */
+
+ // [Currently none]
+
+ return TRUE;
+}
+
int EncryptPartitionInPlaceBegin (volatile FORMAT_VOL_PARAMETERS *volParams, volatile HANDLE *outHandle, WipeAlgorithmId wipeAlgorithm)
{
@@ -606,7 +654,7 @@ int EncryptPartitionInPlaceBegin (volatile FORMAT_VOL_PARAMETERS *volParams, vol
// In the config file, increase the number of partitions where in-place encryption is in progress
- SaveNonSysInPlaceEncSettings (1, wipeAlgorithm);
+ SaveNonSysInPlaceEncSettings (1, wipeAlgorithm, FALSE);
// Add the wizard to the system startup sequence if appropriate
@@ -1033,7 +1081,7 @@ inplace_enc_read:
// Update the configuration files
- SaveNonSysInPlaceEncSettings (-1, wipeAlgorithm);
+ SaveNonSysInPlaceEncSettings (-1, wipeAlgorithm, FALSE);
@@ -1126,6 +1174,513 @@ closing_seq:
return nStatus;
}
+int DecryptPartitionInPlace (volatile FORMAT_VOL_PARAMETERS *volParams, volatile BOOL *DiscardUnreadableEncryptedSectors)
+{
+ HANDLE dev = INVALID_HANDLE_VALUE;
+ PCRYPTO_INFO masterCryptoInfo = NULL, headerCryptoInfo = NULL;
+ UINT64_STRUCT unitNo;
+ char *buf = NULL;
+ byte *tmpSectorBuf = NULL;
+ char dosDev[TC_MAX_PATH] = {0};
+ char devName[MAX_PATH] = {0};
+ WCHAR deviceName[MAX_PATH];
+ int nStatus = ERR_SUCCESS;
+ __int64 deviceSize;
+ uint64 remainingBytes, workChunkStartByteOffset, lastHeaderUpdateDistance = 0, skippedBadSectorCount = 0;
+ uint32 workChunkSize;
+ DWORD dwError, dwResult;
+ BOOL bPause = FALSE, bEncryptedAreaSizeChanged = FALSE;
+ LARGE_INTEGER offset;
+ int sectorSize;
+ int i;
+ DWORD n;
+ char *devicePath = volParams->volumePath;
+ Password *password = volParams->password;
+ HWND hwndDlg = volParams->hwndDlg;
+ int pkcs5_prf = volParams->pkcs5;
+ DISK_GEOMETRY driveGeometry;
+
+
+ buf = (char *) TCalloc (TC_MAX_NONSYS_INPLACE_ENC_WORK_CHUNK_SIZE);
+ if (!buf)
+ {
+ nStatus = ERR_OUTOFMEMORY;
+ goto closing_seq;
+ }
+
+ headerCryptoInfo = crypto_open();
+
+ if (headerCryptoInfo == NULL)
+ {
+ nStatus = ERR_OUTOFMEMORY;
+ goto closing_seq;
+ }
+
+ deviceSize = GetDeviceSize (devicePath);
+ if (deviceSize < 0)
+ {
+ // Cannot determine the size of the partition
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+
+
+ // The wizard should have dismounted the TC volume if it was mounted, but for extra safety we will check this again.
+ if (IsMountedVolume (devicePath))
+ {
+ int driveLetter = GetMountedVolumeDriveNo (devicePath);
+
+ if (driveLetter == -1
+ || !UnmountVolume (hwndDlg, driveLetter, TRUE))
+ {
+ handleWin32Error (hwndDlg);
+ AbortProcess ("CANT_DISMOUNT_VOLUME");
+ }
+ }
+
+
+ StringCbCopyA ((char *)deviceName, sizeof(deviceName), devicePath);
+ ToUNICODE ((char *)deviceName, sizeof(deviceName));
+
+ if (FakeDosNameForDevice (devicePath, dosDev, sizeof(dosDev), devName, sizeof(devName), FALSE) != 0)
+ {
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+
+ dev = OpenPartitionVolume (hwndDlg, devName,
+ TRUE, // Require exclusive access
+ FALSE, // Do not require shared access
+ TRUE, // Ask the user to confirm shared access (if exclusive fails)
+ FALSE, // Do not append alternative instructions how to encrypt the data (to applicable error messages)
+ FALSE); // Non-silent mode
+
+ if (dev == INVALID_HANDLE_VALUE)
+ {
+ nStatus = ERR_DONT_REPORT;
+ goto closing_seq;
+ }
+
+
+
+ // This should never be needed, but is still performed for extra safety (without checking the result)
+ DeviceIoControl (dev,
+ FSCTL_ALLOW_EXTENDED_DASD_IO,
+ NULL,
+ 0,
+ NULL,
+ 0,
+ &dwResult,
+ NULL);
+
+
+ if (!DeviceIoControl (dev, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &driveGeometry, sizeof (driveGeometry), &dwResult, NULL))
+ {
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+
+ sectorSize = driveGeometry.BytesPerSector;
+
+
+ tmpSectorBuf = (byte *) TCalloc (sectorSize);
+ if (!tmpSectorBuf)
+ {
+ nStatus = ERR_OUTOFMEMORY;
+ goto closing_seq;
+ }
+
+
+ nStatus = OpenBackupHeader (dev, devicePath, password, pkcs5_prf, &masterCryptoInfo, headerCryptoInfo, deviceSize);
+
+ if (nStatus != ERR_SUCCESS)
+ goto closing_seq;
+
+
+ if (masterCryptoInfo->LegacyVolume)
+ {
+ Error ("NONSYS_INPLACE_DECRYPTION_BAD_VOL_FORMAT", hwndDlg);
+ nStatus = ERR_DONT_REPORT;
+ goto closing_seq;
+ }
+
+ if (masterCryptoInfo->hiddenVolume)
+ {
+ Error ("NONSYS_INPLACE_DECRYPTION_CANT_DECRYPT_HID_VOL", hwndDlg);
+ nStatus = ERR_DONT_REPORT;
+ goto closing_seq;
+ }
+
+ if (!bInPlaceEncNonSysResumed
+ && masterCryptoInfo->VolumeSize.Value == masterCryptoInfo->EncryptedAreaLength.Value)
+ {
+ /* Decryption started (not resumed) */
+
+ if ((masterCryptoInfo->HeaderFlags & TC_HEADER_FLAG_NONSYS_INPLACE_ENC) == 0)
+ {
+ // The volume has not been encrypted in-place so it may contain a hidden volume.
+ // Ask the user to confirm it does not.
+
+ char *tmpStr[] = {0,
+ "CONFIRM_VOL_CONTAINS_NO_HIDDEN_VOL",
+ "VOL_CONTAINS_NO_HIDDEN_VOL",
+ "VOL_CONTAINS_A_HIDDEN_VOL",
+ 0};
+
+ switch (AskMultiChoice ((void **) tmpStr, FALSE, hwndDlg))
+ {
+ case 1:
+ // NOP
+ break;
+ case 2:
+ default:
+ // Cancel
+ nStatus = ERR_DONT_REPORT;
+ goto closing_seq;
+ }
+ }
+
+ // Update config files and app data
+
+ // In the config file, increase the number of partitions where in-place decryption is in progress
+ SaveNonSysInPlaceEncSettings (1, TC_WIPE_NONE, TRUE);
+
+ // Add the wizard to the system startup sequence if appropriate
+ if (!IsNonInstallMode ())
+ ManageStartupSeqWiz (FALSE, "/prinplace");
+ }
+
+
+
+ bInPlaceEncNonSysResumed = TRUE;
+ bFirstNonSysInPlaceEncResumeDone = TRUE;
+
+
+ remainingBytes = masterCryptoInfo->EncryptedAreaLength.Value;
+
+ lastHeaderUpdateDistance = 0;
+
+
+ ExportProgressStats (masterCryptoInfo->EncryptedAreaLength.Value, masterCryptoInfo->VolumeSize.Value);
+
+ SetNonSysInplaceEncUIStatus (NONSYS_INPLACE_ENC_STATUS_DECRYPTING);
+
+
+
+ /* The in-place decryption core */
+
+ while (remainingBytes > 0)
+ {
+ workChunkSize = (uint32) min (remainingBytes, TC_MAX_NONSYS_INPLACE_ENC_WORK_CHUNK_SIZE);
+
+ if (workChunkSize % ENCRYPTION_DATA_UNIT_SIZE != 0)
+ {
+ nStatus = ERR_PARAMETER_INCORRECT;
+ goto closing_seq;
+ }
+
+ workChunkStartByteOffset = masterCryptoInfo->EncryptedAreaStart.Value;
+
+ unitNo.Value = workChunkStartByteOffset / ENCRYPTION_DATA_UNIT_SIZE;
+
+
+ // Read the ciphertext into RAM
+
+ offset.QuadPart = workChunkStartByteOffset;
+
+ if (SetFilePointerEx (dev, offset, NULL, FILE_BEGIN) == 0)
+ {
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+
+ if (ReadFile (dev, buf, workChunkSize, &n, NULL) == 0)
+ {
+ // Read error
+
+ DWORD dwTmpErr = GetLastError ();
+
+ if (IsDiskReadError (dwTmpErr) && !bVolTransformThreadCancel)
+ {
+ // Physical defect or data corruption
+
+ if (!*DiscardUnreadableEncryptedSectors)
+ {
+ *DiscardUnreadableEncryptedSectors = (AskWarnYesNo ("DISCARD_UNREADABLE_ENCRYPTED_SECTORS", hwndDlg) == IDYES);
+ }
+
+ if (*DiscardUnreadableEncryptedSectors)
+ {
+ // Read the work chunk again, but this time each sector individually and skiping each bad sector
+
+ LARGE_INTEGER tmpSectorOffset;
+ uint64 tmpSectorCount;
+ uint64 tmpBufOffset = 0;
+ DWORD tmpNbrReadBytes = 0;
+
+ tmpSectorOffset.QuadPart = offset.QuadPart;
+
+ for (tmpSectorCount = workChunkSize / sectorSize; tmpSectorCount > 0; --tmpSectorCount)
+ {
+ if (SetFilePointerEx (dev, tmpSectorOffset, NULL, FILE_BEGIN) == 0)
+ {
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+
+ if (ReadFile (dev, tmpSectorBuf, sectorSize, &tmpNbrReadBytes, NULL) == 0
+ || tmpNbrReadBytes != (DWORD) sectorSize)
+ {
+ // Read error
+
+ // Clear the buffer so the content of each unreadable sector is replaced with decrypted all-zero blocks (producing pseudorandom data)
+ memset (tmpSectorBuf, 0, sectorSize);
+
+ skippedBadSectorCount++;
+ }
+
+ memcpy (buf + tmpBufOffset, tmpSectorBuf, sectorSize);
+
+ tmpSectorOffset.QuadPart += sectorSize;
+ tmpBufOffset += sectorSize;
+ }
+ }
+ else
+ {
+ SetLastError (dwTmpErr); // Preserve the original error code
+
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+ }
+ else
+ {
+ SetLastError (dwTmpErr); // Preserve the original error code
+
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+ }
+
+ // Decrypt the ciphertext in RAM
+
+ DecryptDataUnits ((byte *) buf, &unitNo, workChunkSize / ENCRYPTION_DATA_UNIT_SIZE, masterCryptoInfo);
+
+
+
+ // Conceal initial portion of the filesystem
+
+ if (workChunkStartByteOffset - TC_VOLUME_DATA_OFFSET < TC_INITIAL_NTFS_CONCEAL_PORTION_SIZE)
+ {
+ // We are decrypting the initial TC_INITIAL_NTFS_CONCEAL_PORTION_SIZE bytes of the filesystem. We will
+ // conceal this portion to prevent Windows and applications from interfering with the volume.
+
+ for (i = 0; i < min (TC_INITIAL_NTFS_CONCEAL_PORTION_SIZE, workChunkStartByteOffset - TC_VOLUME_DATA_OFFSET + workChunkSize); i++)
+ buf[i] ^= TC_NTFS_CONCEAL_CONSTANT;
+ }
+
+
+ // Write the plaintext
+
+ offset.QuadPart = workChunkStartByteOffset - TC_VOLUME_DATA_OFFSET;
+
+ if (SetFilePointerEx (dev, offset, NULL, FILE_BEGIN) == 0)
+ {
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+
+ if (WriteFile (dev, buf, workChunkSize, &n, NULL) == 0)
+ {
+ // Write error
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+
+
+ masterCryptoInfo->EncryptedAreaStart.Value += workChunkSize;
+ masterCryptoInfo->EncryptedAreaLength.Value -= workChunkSize;
+
+ remainingBytes -= workChunkSize;
+ lastHeaderUpdateDistance += workChunkSize;
+
+ bEncryptedAreaSizeChanged = TRUE;
+
+ if (lastHeaderUpdateDistance >= TC_NONSYS_INPLACE_ENC_HEADER_UPDATE_INTERVAL)
+ {
+ nStatus = FastVolumeHeaderUpdate (dev, headerCryptoInfo, masterCryptoInfo, deviceSize);
+
+ if (nStatus != ERR_SUCCESS)
+ {
+ // Possible write error
+ goto closing_seq;
+ }
+
+ lastHeaderUpdateDistance = 0;
+ }
+
+ ExportProgressStats (masterCryptoInfo->EncryptedAreaLength.Value, masterCryptoInfo->VolumeSize.Value);
+
+ if (bVolTransformThreadCancel)
+ {
+ bPause = TRUE;
+ break;
+ }
+ }
+
+ nStatus = FastVolumeHeaderUpdate (dev, headerCryptoInfo, masterCryptoInfo, deviceSize);
+
+
+ if (nStatus != ERR_SUCCESS)
+ {
+ // Possible write error
+ goto closing_seq;
+ }
+
+
+ if (!bPause)
+ {
+ /* Volume has been fully decrypted. */
+
+
+ // Prevent attempts to update volume header during the closing sequence
+ bEncryptedAreaSizeChanged = FALSE;
+
+
+ SetNonSysInplaceEncUIStatus (NONSYS_INPLACE_ENC_STATUS_FINALIZING);
+
+
+
+ /* Undo concealing of the filesystem */
+
+ nStatus = ConcealNTFS (dev);
+
+ if (nStatus != ERR_SUCCESS)
+ goto closing_seq;
+
+
+
+ /* Ovewrite the backup header and the remaining ciphertext with all-zero blocks (the primary header was overwritten with the decrypted data). */
+
+ memset (tmpSectorBuf, 0, sectorSize);
+
+ for (offset.QuadPart = masterCryptoInfo->VolumeSize.Value;
+ offset.QuadPart <= deviceSize - sectorSize;
+ offset.QuadPart += sectorSize)
+ {
+ if (SetFilePointerEx (dev, offset, NULL, FILE_BEGIN) == 0)
+ {
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+
+ if (WriteFile (dev, tmpSectorBuf, sectorSize, &n, NULL) == 0)
+ {
+ // Write error
+ dwError = GetLastError();
+
+ SetLastError (dwError);
+ nStatus = ERR_OS_ERROR;
+ goto closing_seq;
+ }
+ }
+
+
+
+ /* Update the configuration files */
+
+ SaveNonSysInPlaceEncSettings (-1, TC_WIPE_NONE, TRUE);
+
+
+
+ SetNonSysInplaceEncUIStatus (NONSYS_INPLACE_ENC_STATUS_FINISHED);
+
+ nStatus = ERR_SUCCESS;
+
+ }
+ else
+ {
+ // The process has been paused by the user or aborted by the wizard (e.g. on app exit)
+
+ nStatus = ERR_USER_ABORT;
+
+ SetNonSysInplaceEncUIStatus (NONSYS_INPLACE_ENC_STATUS_PAUSED);
+ }
+
+ if (dev != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle (dev);
+ dev = INVALID_HANDLE_VALUE;
+ }
+
+
+closing_seq:
+
+ dwError = GetLastError();
+
+ if (bEncryptedAreaSizeChanged
+ && dev != INVALID_HANDLE_VALUE
+ && masterCryptoInfo != NULL
+ && headerCryptoInfo != NULL
+ && deviceSize > 0)
+ {
+ // Execution of the core loop may have been interrupted due to an error or user action without updating the header
+ FastVolumeHeaderUpdate (dev, headerCryptoInfo, masterCryptoInfo, deviceSize);
+ }
+
+ if (dev != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle (dev);
+ dev = INVALID_HANDLE_VALUE;
+ }
+
+ if (masterCryptoInfo != NULL)
+ {
+ crypto_close (masterCryptoInfo);
+ masterCryptoInfo = NULL;
+ }
+
+ if (headerCryptoInfo != NULL)
+ {
+ crypto_close (headerCryptoInfo);
+ headerCryptoInfo = NULL;
+ }
+
+ if (dosDev[0])
+ RemoveFakeDosName (devicePath, dosDev);
+
+ if (buf != NULL)
+ {
+ TCfree (buf);
+ buf = NULL;
+ }
+
+ if (tmpSectorBuf != NULL)
+ {
+ TCfree (tmpSectorBuf);
+ tmpSectorBuf = NULL;
+ }
+
+ if (skippedBadSectorCount > 0)
+ {
+ wchar_t msg[30000] = {0};
+ wchar_t sizeStr[500] = {0};
+
+ GetSizeString (skippedBadSectorCount * sectorSize, sizeStr, sizeof(sizeStr));
+
+ StringCbPrintfW (msg, sizeof(msg),
+ GetString ("SKIPPED_BAD_SECTOR_COUNT"),
+ skippedBadSectorCount,
+ sizeStr);
+
+ WarningDirect (msg, hwndDlg);
+ }
+
+ if (nStatus != ERR_SUCCESS && nStatus != ERR_USER_ABORT)
+ SetLastError (dwError);
+
+ return nStatus;
+}
int FastVolumeHeaderUpdate (HANDLE dev, CRYPTO_INFO *headerCryptoInfo, CRYPTO_INFO *masterCryptoInfo, __int64 deviceSize)
{
@@ -1168,6 +1723,13 @@ int FastVolumeHeaderUpdate (HANDLE dev, CRYPTO_INFO *headerCryptoInfo, CRYPTO_IN
mputInt64 (fieldPos, (masterCryptoInfo->EncryptedAreaStart.Value));
mputInt64 (fieldPos, (masterCryptoInfo->EncryptedAreaLength.Value));
+ // We need to ensure the TC_HEADER_FLAG_NONSYS_INPLACE_ENC flag bit is set, because if volumes created by TC-format
+ // were decrypted in place, it would be possible to mount them partially encrypted and it wouldn't be possible
+ // to resume interrupted decryption after the wizard exits.
+ masterCryptoInfo->HeaderFlags |= TC_HEADER_FLAG_NONSYS_INPLACE_ENC;
+ fieldPos = (byte *) header + TC_HEADER_OFFSET_FLAGS;
+ mputLong (fieldPos, (masterCryptoInfo->HeaderFlags));
+
headerCrc32 = GetCrc32 (header + TC_HEADER_OFFSET_MAGIC, TC_HEADER_OFFSET_HEADER_CRC - TC_HEADER_OFFSET_MAGIC);
fieldPos = (byte *) header + TC_HEADER_OFFSET_HEADER_CRC;
@@ -1419,7 +1981,7 @@ void SetNonSysInplaceEncUIStatus (int nonSysInplaceEncStatus)
}
-BOOL SaveNonSysInPlaceEncSettings (int delta, WipeAlgorithmId newWipeAlgorithm)
+BOOL SaveNonSysInPlaceEncSettings (int delta, WipeAlgorithmId newWipeAlgorithm, BOOL bDecrypt)
{
int count;
char str[32];
@@ -1435,7 +1997,7 @@ BOOL SaveNonSysInPlaceEncSettings (int delta, WipeAlgorithmId newWipeAlgorithm)
RemoveNonSysInPlaceEncNotifications();
return TRUE;
}
- else
+ else if (!bDecrypt)
{
if (newWipeAlgorithm != TC_WIPE_NONE)
{
@@ -1447,11 +2009,11 @@ BOOL SaveNonSysInPlaceEncSettings (int delta, WipeAlgorithmId newWipeAlgorithm)
{
remove (GetConfigPath (TC_APPD_FILENAME_NONSYS_INPLACE_ENC_WIPE));
}
-
- StringCbPrintfA (str, sizeof(str), "%d", count);
-
- return SaveBufferToFile (str, GetConfigPath (TC_APPD_FILENAME_NONSYS_INPLACE_ENC), strlen(str), FALSE);
}
+
+ StringCbPrintfA (str, sizeof(str), "%d", count);
+
+ return SaveBufferToFile (str, GetConfigPath (TC_APPD_FILENAME_NONSYS_INPLACE_ENC), strlen(str), FALSE);
}