├── .editorconfig ├── .gitattributes ├── .gitignore ├── ClonezillaApps.sln ├── LICENSE ├── README.md ├── clonezilla-util ├── CL │ └── Verbs │ │ ├── BaseVerb.cs │ │ ├── ExtractFiles.cs │ │ ├── ExtractPartitionImage.cs │ │ ├── ListContents.cs │ │ ├── MountAsFiles.cs │ │ └── MountAsImageFiles.cs ├── Extensions.cs ├── Program.cs └── clonezilla-util.csproj ├── clonezilla-util_tests ├── GlobalSuppressions.cs ├── GlobalUsings.cs ├── ListContents │ ├── LargeClonezillaPartitions.cs │ ├── LargeDriveImages.cs │ ├── Partclone.cs │ ├── SmallClonezillaPartitions.cs │ ├── SmallPartitionImages.cs │ └── TestUtility.cs ├── Main.cs ├── Mount │ ├── AsFiles │ │ ├── Ext4.cs │ │ ├── LargeClonezillaImages.cs │ │ ├── LargeDriveImages.cs │ │ ├── LuksClonezillaImages.cs │ │ ├── LuksParcloneImages.cs │ │ ├── Misc.cs │ │ ├── Partclone.cs │ │ ├── SmallClonezillaPartitions.cs │ │ ├── SmallPartitionImages.cs │ │ └── UbuntuFileSystems.cs │ ├── AsImageFiles │ │ └── ImageFileTests.cs │ └── TestUtility.cs ├── Sparse │ └── SparseTests.cs ├── Train │ └── TrainTests.cs ├── Utilities │ └── Utility.cs └── clonezilla-util_tests.csproj ├── lib7Zip ├── ArchiveEntry.cs ├── GlobalSuppressions.cs ├── SevenZipExtractorUsing7zFM.cs ├── SevenZipExtractorUsingSevenZipExtractor.cs ├── SevenZipExtractorUsingSevenZipSharp.cs ├── SevenZipUtility.cs ├── UI │ ├── ListviewNative.cs │ └── WinAPI.cs ├── ext │ └── 7-Zip │ │ ├── win-x64 │ │ ├── 7z.dll │ │ ├── 7z.exe │ │ └── 7zFM.exe │ │ └── win-x86 │ │ ├── 7z.dll │ │ ├── 7z.exe │ │ └── 7zFM.exe └── lib7Zip.csproj ├── libBzip2 ├── BZip2BlockFinder.cs ├── Bzip2StreamSeekable.cs └── libBzip2.csproj ├── libClonezilla ├── Cache │ ├── ClonezillaCacheManager.cs │ ├── FileSystem │ │ ├── FileDetails.cs │ │ ├── FileSystemObject.cs │ │ └── FolderDetails.cs │ ├── IClonezillaCacheManager.cs │ ├── IPartitionCache.cs │ ├── PartitionCache.cs │ └── WholeFileCacheManager.cs ├── Decompressors │ ├── Decompressor.cs │ ├── DecompressorSelector.cs │ ├── GzDecompressor.cs │ ├── LZ4Decompressor.cs │ ├── LZipDecompressor.cs │ ├── NoChangeDecompressor.cs │ ├── bzip2Decompressor.cs │ ├── xzDecompressor.cs │ └── zstdDecompressor.cs ├── Extractors │ ├── CachedExtractor.cs │ ├── ExtractorUsing7z.cs │ ├── ExtractorUsing7zFM.cs │ ├── ExtractorUsingSevenZipExtractor.cs │ ├── ExtractorUsingSevenZipSharp.cs │ ├── IExtractor.cs │ ├── MultiExtractor.cs │ └── SynchronisedExtractor.cs ├── GlobalSuppressions.cs ├── PartitionContainers │ ├── ClonezillaImage.cs │ ├── ImageFiles │ │ ├── CompressedDriveImage.cs │ │ ├── CompressedImage.cs │ │ ├── CompressedPartitionImage.cs │ │ ├── ImageFile.cs │ │ ├── RawDriveImage.cs │ │ ├── RawImage.cs │ │ └── RawPartitionImage.cs │ ├── PartcloneFile.cs │ └── PartitionContainer.cs ├── Partitions │ ├── ImageFilePartition.cs │ ├── MountedContainer.cs │ ├── MountedPartitionImage.cs │ ├── PartclonePartition.cs │ └── Partition.cs ├── Utility.cs ├── VFS │ ├── IVFS.cs │ ├── OnDemandVFS.cs │ └── SevenZipBackedFileEntry.cs └── libClonezilla.csproj ├── libCommon ├── Buffers.cs ├── ChildProcessTracker.cs ├── DesktopUtility.cs ├── Extensions.cs ├── GlobalSuppressions.cs ├── Lists │ ├── IRangeComparer.cs │ └── LazyList.cs ├── Logging │ └── SuppressConsecutiveDuplicateFilter.cs ├── MainWindowFinder.cs ├── ProcessUtility.cs ├── Streams │ ├── CachingStream.cs │ ├── IReadSuggestor.cs │ ├── IndependentStream.cs │ ├── Multistream.cs │ ├── PositionTrackerStream.cs │ ├── Seekable │ │ ├── SeekableStreamUsingNearestActioner.cs │ │ └── SeekableStreamUsingRestarts.cs │ ├── SiphonStream.cs │ ├── Sparse │ │ ├── ISparseAwareReader.cs │ │ ├── ISparseAwareWriter.cs │ │ ├── SparesAwareReadStream.cs │ │ └── SparseAwareWriteStream.cs │ ├── StreamUtility.cs │ └── SubStream.cs ├── TempUtility.cs ├── Utility.cs ├── WindowHandleHelper.cs └── libCommon.csproj ├── libDecompression ├── Lists │ └── MappingComparer.cs ├── SeekableDecompressingStream.cs ├── Utilities │ └── BoyerMoore.cs └── libDecompression.csproj ├── libDokan ├── Extensions.cs ├── FindFilesPatternToRegex.cs ├── GlobalSuppressions.cs ├── Processes │ └── ProcInfo.cs ├── Utility.cs ├── VFS │ ├── DokanVFS.cs │ ├── FileSystemEntry.cs │ ├── Files │ │ ├── FileEntry.cs │ │ └── StreamBackedFileEntry.cs │ └── Folders │ │ ├── Folder.cs │ │ ├── RestrictedFolderByPID.cs │ │ ├── RootFolder.cs │ │ └── UnlistedFolder.cs └── libDokan.csproj ├── libGZip ├── GZipStreamSeekable.cs ├── ext │ └── gztool │ │ └── win-x86_64 │ │ └── gztool-Windows.x86_64.exe └── libGZip.csproj ├── libPartclone ├── Cache │ └── IPartcloneCache.cs ├── Extensions.cs ├── Lists │ └── ContiguousRangeComparer.cs ├── Metadata │ ├── FileSystemInfoV1.cs │ ├── FileSystemInfoV2.cs │ ├── ImageDescV1.cs │ ├── ImageDescV2.cs │ ├── ImageHeadV1.cs │ ├── ImageHeadV2.cs │ ├── ImageOptionsV1.cs │ └── ImageOptionsV2.cs ├── PartcloneImageInfo.cs ├── PartcloneStream.cs └── libPartclone.csproj └── libTrainCompress ├── Compressors ├── Compressor.cs ├── gzCompressor.cs ├── xzCompressor.cs └── zstdCompressor.cs ├── GlobalSuppressions.cs ├── Lists └── CarriageComparer.cs ├── Streams └── SubStream.cs ├── TrainCompressor.cs ├── TrainDecompressor.cs └── libTrainCompress.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{cs,vb}] 2 | 3 | # IDE0060: Remove unused parameter 4 | dotnet_code_quality_unused_parameters = non_public:suggestion 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Fidel Perez-Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clonezilla-util 2 | Extract individual files from a Clonezilla archive, without extracting the full archive. 3 | 4 | ## Supported formats 5 | Clonezilla archives 6 | Partclone images 7 | Drive images (eg. sda.img) 8 | Partition images (eg. sda1.img) 9 | Compressed versions of the above (bzip2, GZip, LZ4, LZip, xz, Zstandard) 10 | 11 | ## Where to download 12 | Releases can be found over in the [releases](https://github.com/fiddyschmitt/clonezilla-util/releases) section. 13 |
14 |
15 | 16 | ## Mount a Clonezilla archive in Windows 17 | 18 | First, install [this version](https://github.com/dokan-dev/dokany/releases/tag/v2.1.0.1000) of the Dokan Driver. This is required to create a Virtual Drive. 19 | 20 | Now run the following command: 21 | 22 | `clonezilla-util.exe mount --input --mount L:\` 23 | 24 | A virtual drive is created, containing all the files in the Clonezilla archive: 25 | 26 |
27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 |
35 | 36 | ## Mount a Clonezilla archive as partition image files 37 | 38 | First, install [this version](https://github.com/dokan-dev/dokany/releases/tag/v2.1.0.1000) of the Dokan Driver. This is required to create a Virtual Drive. 39 | 40 | Now run the following command: 41 | 42 | `clonezilla-util.exe mount-as-image-files --input --mount L:\` 43 | 44 | A virtual drive is created, containing a file for each partition: 45 | 46 |
47 | 48 | 49 | 50 |
51 | 52 |
53 |
54 | 55 | You can open them using 7-Zip, and extract individual files: 56 | 57 | 58 | 59 |
60 |
61 |
62 | 63 | # Advanced features 64 | 65 | ## Mount raw images 66 | 67 | Run the following command: 68 | 69 | `clonezilla-util.exe mount --input "sda.img" --mount L:\` 70 |
71 |
72 | 73 | ## Extract partition images from Clonezilla archive (Dokan not required) 74 | 75 | Run the following command: 76 | 77 | `clonezilla-util.exe extract-partition-image --input --output ` 78 | 79 | The program creates a file for each partition in the Clonezilla archive. 80 | 81 |
82 | 83 | 84 | 85 |
86 | 87 |
88 |
89 | 90 | If the images are extracted to an NTFS drive, they are created as sparse. Meaning they only take up the necessary space: 91 | 92 | 93 |
94 |
95 |
96 | 97 | # Thanks 98 | 99 | A special thanks to Roberto for creating [gztool](https://github.com/circulosmeos/gztool). It allows the gz files in the Clonezilla archive to be read randomly, which gz doesn't natively support. 100 | 101 | Thanks to the [Dokan team](https://github.com/dokan-dev/dokan-dotnet). Dokan is what is used to create the virtual drive. 102 | 103 | Thanks to Igor for [7-Zip](https://sourceforge.net/projects/sevenzip/). His versatile tool inspects the contents of the image files, which can be in many formats (eg. NTFS, FAT, etc.) 104 | 105 | Thanks to Steven for [Clonezilla](https://github.com/stevenshiau/clonezilla), the backup tool used by millions. 106 | -------------------------------------------------------------------------------- /clonezilla-util/CL/Verbs/BaseVerb.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace clonezilla_util.CL.Verbs 7 | { 8 | public class BaseVerb 9 | { 10 | [Option('i', "input", HelpText = "The folder containing the Clonezilla archive. Or, a partclone filename, or image filename.", Required = true, Min = 1)] 11 | public IEnumerable? InputPaths { get; set; } 12 | 13 | [Option("cache-folder", HelpText = "The folder to store cache files. Defaults to the 'cache' folder in the same location as the exe.", Required = false)] 14 | public string? CacheFolder { get; set; } 15 | 16 | [Option("temp-folder", HelpText = "The folder to store temporary files (if required)", Required = false)] 17 | public string? TempFolder { get; set; } 18 | 19 | [Option("process-trailing-nulls ", HelpText = "By default, the program skips trailing nulls to speed up processing of large files. Use this switch to force them to be processed.", Required = false)] 20 | public bool ProcessTrailingNulls { get; set; } = false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /clonezilla-util/CL/Verbs/ExtractFiles.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLine.Text; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace clonezilla_util.CL.Verbs 8 | { 9 | //[Verb("extract", HelpText = "Extract files (without using directory names)")] 10 | public class ExtractFiles : BaseVerb 11 | { 12 | public IEnumerable? Filenames; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /clonezilla-util/CL/Verbs/ExtractPartitionImage.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using CommandLine.Text; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace clonezilla_util.CL.Verbs 8 | { 9 | [Verb("extract-partition-image", HelpText = "Extract the uncompressed, original copy of the partition.")] 10 | public class ExtractPartitionImage : BaseVerb 11 | { 12 | [Option('o', "output", HelpText = "The folder to extract the partition image to.", Required = true)] 13 | public string? OutputFolder { get; set; } 14 | 15 | [Option('p', "partitions", HelpText = "The partition(s) to extract from the Clonezilla archive. Eg. sda1. If not provided, all partitions will be extracted.", Required = false)] 16 | public IEnumerable PartitionsToExtract { get; set; } = []; 17 | 18 | [Option("no-sparse-output", HelpText = "By default, the program produces a sparse output file which is faster to generate and takes less disk space, while being identical to the original. Using this option, a full file is produced rather than a sparse file.", Required = false)] 19 | public bool NoSparseOutput { get; set; } = false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /clonezilla-util/CL/Verbs/ListContents.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace clonezilla_util.CL.Verbs 7 | { 8 | [Verb("list", HelpText = "List files in the archive")] 9 | public class ListContents : BaseVerb 10 | { 11 | [Option('s', "separator", HelpText = "The seperator to use between results in the output", Default = "\n", Required = false)] 12 | public string OutputSeparator { get; set; } = Environment.NewLine; 13 | 14 | [Option("null-separator", HelpText = "Use a null character to seperate the results in the output", Default = false)] 15 | public bool UseNullSeparator { get; set; } = false; 16 | 17 | [Option('p', "partitions", HelpText = "The partition(s) to list contents of. Eg. sda1. If not provided, all partitions will be processed.", Required = false)] 18 | public IEnumerable PartitionsToInspect { get; set; } = []; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /clonezilla-util/CL/Verbs/MountAsFiles.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace clonezilla_util.CL.Verbs 9 | { 10 | [Verb("mount", HelpText = "Serve the file contents as using a Virtual File System. Requires Dokan to be installed. https://dokan-dev.github.io/")] 11 | public class MountAsFiles : BaseVerb 12 | { 13 | [Option('m', "mount", HelpText = "The drive to mount to, where the files will be presented. If not provided, a drive letter will automatically be chosen.", Required = false)] 14 | public string? MountPoint { get; set; } 15 | 16 | [Option('p', "partitions", HelpText = "The partition(s) to serve. Eg. sda1. If not provided, all partitions will be served.", Required = false)] 17 | public IEnumerable PartitionsToMount { get; set; } = []; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /clonezilla-util/CL/Verbs/MountAsImageFiles.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace clonezilla_util.CL.Verbs 9 | { 10 | [Verb("mount-as-image-files", HelpText = "Serve the original partition images using a Virtual File System. Requires Dokan to be installed. https://dokan-dev.github.io/")] 11 | public class MountAsImageFiles : BaseVerb 12 | { 13 | [Option('m', "mount", HelpText = "The drive to mount to, where the partition images will be presented. If not provided, a drive letter will automatically be chosen.", Required = true)] 14 | public string? MountPoint { get; set; } 15 | 16 | [Option('p', "partitions", HelpText = "The partition(s) to serve. Eg. sda1. If not provided, all partitions will be served.", Required = false)] 17 | public IEnumerable PartitionsToMount { get; set; } = []; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /clonezilla-util/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Linq; 5 | using System.IO; 6 | using System.Security.Cryptography; 7 | 8 | namespace clonezilla_util 9 | { 10 | public static class Extensions 11 | { 12 | public static bool IsEqualTo(this byte[] buffer1, byte[] otherBuffer) 13 | { 14 | var span1 = new ReadOnlySpan(buffer1, 0, buffer1.Length); 15 | var span2 = new ReadOnlySpan(otherBuffer, 0, otherBuffer.Length); 16 | var result = span1.SequenceEqual(span2); 17 | return result; 18 | } 19 | 20 | public static List> ChunkBy(this IEnumerable source, int chunkSize) 21 | { 22 | return source 23 | .Select((x, i) => new { Index = i, Value = x }) 24 | .GroupBy(x => x.Index / chunkSize) 25 | .Select(x => x.Select(v => v.Value).ToList()) 26 | .ToList(); 27 | } 28 | 29 | public static IEnumerable Range(ulong start, ulong length) 30 | { 31 | var end = start + length; 32 | for (ulong i = start; i < end; i++) 33 | { 34 | yield return i; 35 | } 36 | } 37 | 38 | public static string ReplaceFirst(this string text, string search, string replace) 39 | { 40 | int pos = text.IndexOf(search); 41 | if (pos < 0) 42 | { 43 | return text; 44 | } 45 | return string.Concat(text.AsSpan(0, pos), replace, text.AsSpan(pos + search.Length)); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /clonezilla-util/clonezilla-util.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | clonezilla_util 7 | x64 8 | enable 9 | 10 | 11 | 12 | full 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /clonezilla-util_tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "", Scope = "module")] 9 | [assembly: SuppressMessage("Interoperability", "SYSLIB1054:Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time", Justification = "", Scope = "module")] 10 | [assembly: SuppressMessage("Usage", "CA2211:Non-constant fields should not be visible", Justification = "", Scope = "module")] 11 | -------------------------------------------------------------------------------- /clonezilla-util_tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | [assembly: Parallelize(Workers = 4, Scope = ExecutionScope.ClassLevel)] -------------------------------------------------------------------------------- /clonezilla-util_tests/ListContents/LargeClonezillaPartitions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace clonezilla_util_tests.ListContents 8 | { 9 | [DoNotParallelize] 10 | [TestClass] 11 | public class LargeClonezillaPartitions 12 | { 13 | [TestMethod] 14 | public void Bzip2() 15 | { 16 | TestUtility.ConfirmContainsStrings( 17 | Main.ExeUnderTest, 18 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_bzip2""", 19 | [ 20 | @"2022-07-16-22-img_pb-devops1_bzip2\sda1\Recovery\Logs\Reload.xml", 21 | @"2022-07-16-22-img_pb-devops1_bzip2\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", 22 | @"2022-07-16-22-img_pb-devops1_bzip2\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg" 23 | ]); 24 | } 25 | 26 | [TestMethod] 27 | public void Gz() 28 | { 29 | TestUtility.ConfirmContainsStrings( 30 | Main.ExeUnderTest, 31 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz""", 32 | [ 33 | @"2022-07-17-16-img_pb-devops1_gz\sda1\Recovery\Logs\Reload.xml", 34 | @"2022-07-17-16-img_pb-devops1_gz\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", 35 | @"2022-07-17-16-img_pb-devops1_gz\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg" 36 | ]); 37 | } 38 | 39 | [TestMethod] 40 | public void Xz() 41 | { 42 | TestUtility.ConfirmContainsStrings( 43 | Main.ExeUnderTest, 44 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-12-img_pb-devops1_xz""", 45 | [ 46 | @"2022-07-17-12-img_pb-devops1_xz\sda1\Recovery\Logs\Reload.xml", 47 | @"2022-07-17-12-img_pb-devops1_xz\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", 48 | @"2022-07-17-12-img_pb-devops1_xz\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg" 49 | ]); 50 | } 51 | 52 | [TestMethod] 53 | public void Zst() 54 | { 55 | TestUtility.ConfirmContainsStrings( 56 | Main.ExeUnderTest, 57 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_zst""", 58 | [ 59 | @"2022-07-16-22-img_pb-devops1_zst\sda1\Recovery\Logs\Reload.xml", 60 | @"2022-07-16-22-img_pb-devops1_zst\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", 61 | @"2022-07-16-22-img_pb-devops1_zst\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg" 62 | ]); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /clonezilla-util_tests/ListContents/Partclone.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace clonezilla_util_tests.ListContents 8 | { 9 | [DoNotParallelize] 10 | [TestClass] 11 | public class Partclone 12 | { 13 | [TestMethod] 14 | public void MixedPartcloneFormats() 15 | { 16 | TestUtility.ConfirmContainsStrings( 17 | Main.ExeUnderTest, 18 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)" "E:\clonezilla-util-test resources\clonezilla images\2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)" """, 19 | [ 20 | @"2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)\sda1\sda1.txt", 21 | @"2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)\sda2\sda2.txt", 22 | 23 | @"2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)\sda1\sda1.txt", 24 | @"2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)\sda2\sda2.txt" 25 | ]); 26 | 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /clonezilla-util_tests/ListContents/SmallClonezillaPartitions.cs: -------------------------------------------------------------------------------- 1 | namespace clonezilla_util_tests.ListContents 2 | { 3 | [TestClass] 4 | [DoNotParallelize] 5 | public class SmallClonezillaPartitions 6 | { 7 | [TestMethod] 8 | public void Bzip2() 9 | { 10 | TestUtility.ConfirmContainsStrings( 11 | Main.ExeUnderTest, 12 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_bzip2" -p sda1 sdb1""", 13 | [ 14 | @"2022-07-16-22-img_pb-devops1_bzip2\sda1\Recovery\Logs\Reload.xml", 15 | @"2022-07-16-22-img_pb-devops1_bzip2\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg" 16 | ]); 17 | } 18 | 19 | [TestMethod] 20 | public void gz() 21 | { 22 | TestUtility.ConfirmContainsStrings( 23 | Main.ExeUnderTest, 24 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz" -p sda1 sdb1""", 25 | [ 26 | @"2022-07-17-16-img_pb-devops1_gz\sda1\Recovery\Logs\Reload.xml", 27 | @"2022-07-17-16-img_pb-devops1_gz\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg" 28 | ]); 29 | } 30 | 31 | [TestMethod] 32 | public void xz() 33 | { 34 | TestUtility.ConfirmContainsStrings( 35 | Main.ExeUnderTest, 36 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-12-img_pb-devops1_xz" -p sda1 sdb1""", 37 | [ 38 | @"2022-07-17-12-img_pb-devops1_xz\sda1\Recovery\Logs\Reload.xml", 39 | @"2022-07-17-12-img_pb-devops1_xz\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg" 40 | ]); 41 | } 42 | 43 | [TestMethod] 44 | public void zst() 45 | { 46 | TestUtility.ConfirmContainsStrings( 47 | Main.ExeUnderTest, 48 | """list --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_zst" -p sda1 sdb1""", 49 | [ 50 | @"2022-07-16-22-img_pb-devops1_zst\sda1\Recovery\Logs\Reload.xml", 51 | @"2022-07-16-22-img_pb-devops1_zst\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg" 52 | ]); 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /clonezilla-util_tests/ListContents/SmallPartitionImages.cs: -------------------------------------------------------------------------------- 1 | namespace clonezilla_util_tests.ListContents 2 | { 3 | [TestClass] 4 | [DoNotParallelize] 5 | public class SmallPartitionImages 6 | { 7 | [TestMethod] 8 | public void Bzip2() 9 | { 10 | TestUtility.ConfirmContainsStrings( 11 | Main.ExeUnderTest, 12 | """list --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.bz2""", 13 | [ 14 | @"2021-12-28_pb-devops1_sda1.img\partition0\Recovery\WindowsRE\ReAgent.xml", 15 | ]); 16 | } 17 | 18 | [TestMethod] 19 | public void gz() 20 | { 21 | TestUtility.ConfirmContainsStrings( 22 | Main.ExeUnderTest, 23 | """list --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.gz""", 24 | [ 25 | @"2021-12-28_pb-devops1_sda1.img\partition0\Recovery\WindowsRE\ReAgent.xml", 26 | ]); 27 | } 28 | 29 | [TestMethod] 30 | public void raw() 31 | { 32 | TestUtility.ConfirmContainsStrings( 33 | Main.ExeUnderTest, 34 | """list --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img""", 35 | [ 36 | @"2021-12-28_pb-devops1_sda1\partition0\Recovery\WindowsRE\ReAgent.xml", 37 | ]); 38 | } 39 | 40 | [TestMethod] 41 | public void xz() 42 | { 43 | TestUtility.ConfirmContainsStrings( 44 | Main.ExeUnderTest, 45 | """list --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.xz""", 46 | [ 47 | @"2021-12-28_pb-devops1_sda1.img\partition0\Recovery\WindowsRE\ReAgent.xml", 48 | ]); 49 | } 50 | 51 | [TestMethod] 52 | public void zst() 53 | { 54 | TestUtility.ConfirmContainsStrings( 55 | Main.ExeUnderTest, 56 | """list --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.zst""", 57 | [ 58 | @"2021-12-28_pb-devops1_sda1.img\partition0\Recovery\WindowsRE\ReAgent.xml", 59 | ]); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /clonezilla-util_tests/ListContents/TestUtility.cs: -------------------------------------------------------------------------------- 1 | using libCommon; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace clonezilla_util_tests.ListContents 10 | { 11 | public static class TestUtility 12 | { 13 | public static void ConfirmContainsStrings(string exeUnderTest, string args, IList expectedStrings) 14 | { 15 | var output = ProcessUtility.GetProgramOutput(exeUnderTest, args); 16 | 17 | expectedStrings 18 | .ToList() 19 | .ForEach(expectedString => 20 | { 21 | var contains = output.Contains(expectedString); 22 | Assert.IsTrue(contains, $"Could not find: {expectedString}"); 23 | }); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace clonezilla_util_tests 9 | { 10 | [TestClass] 11 | public static class Main 12 | { 13 | public const string ExeUnderTest = @"R:\Temp\clonezilla-util release\clonezilla-util.exe"; 14 | public static bool RunLargeTests = true; 15 | 16 | [AssemblyInitialize] 17 | public static void AssemblyInit(TestContext context) 18 | { 19 | Process 20 | .GetProcesses() 21 | .Where(pr => pr.ProcessName == "clonezilla-util") 22 | .ToList() 23 | .ForEach(p => 24 | { 25 | p.Kill(); 26 | p.WaitForExit(); 27 | }); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Mount/AsFiles/Ext4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static clonezilla_util_tests.Mount.TestUtility; 7 | 8 | namespace clonezilla_util_tests.Mount.AsFiles 9 | { 10 | [TestClass] 11 | [DoNotParallelize] 12 | public class Ext4 13 | { 14 | [TestMethod] 15 | public void ext4() 16 | { 17 | ConfirmFilesExist( 18 | Main.ExeUnderTest, 19 | """mount --input "E:\clonezilla-util-test resources\drive images\ext4\2022-08-16_ext4.img" --mount L:\ """, 20 | [ 21 | new FileDetails(@"L:\hello.txt", "bad9425ff652b1bd52b49720abecf0ba"), 22 | new FileDetails(@"L:\config_files\test.xml", "81dfa8e288df74cebc654c582a3abebc"), 23 | ]); 24 | } 25 | 26 | [TestMethod] 27 | public void ext4_zst() 28 | { 29 | //Takes a long time because 7z.exe tries to scan across the entire 33 GB file, requiring the zstandard stream (even though only 1 MB) to be opened over and over by SeekableStreamUsingRestarts 30 | 31 | ConfirmFilesExist( 32 | Main.ExeUnderTest, 33 | """mount --input "E:\clonezilla-util-test resources\drive images\ext4\2022-08-16_ext4.img.zst" --mount L:\ """, 34 | [ 35 | new FileDetails(@"L:\hello.txt", "bad9425ff652b1bd52b49720abecf0ba"), 36 | new FileDetails(@"L:\config_files\test.xml", "81dfa8e288df74cebc654c582a3abebc"), 37 | ]); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Mount/AsFiles/LargeClonezillaImages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static clonezilla_util_tests.Mount.TestUtility; 7 | 8 | namespace clonezilla_util_tests.Mount.AsFiles 9 | { 10 | [TestClass] 11 | [DoNotParallelize] 12 | public class LargeClonezillaImages 13 | { 14 | [TestMethod] 15 | public void bzip2() 16 | { 17 | ConfirmFilesExist( 18 | Main.ExeUnderTest, 19 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_bzip2" -m L:\""", 20 | [ 21 | new FileDetails(@"L:\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"), 22 | new FileDetails(@"L:\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", "97a4aa50fa682b00457810d010ff5852"), 23 | new FileDetails(@"L:\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"), 24 | ]); 25 | } 26 | 27 | [TestMethod] 28 | public void gz() 29 | { 30 | ConfirmFilesExist( 31 | Main.ExeUnderTest, 32 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz" -m L:\""", 33 | [ 34 | new FileDetails(@"L:\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"), 35 | new FileDetails(@"L:\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", "97a4aa50fa682b00457810d010ff5852"), 36 | new FileDetails(@"L:\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"), 37 | ]); 38 | } 39 | 40 | [TestMethod] 41 | public void xz() 42 | { 43 | ConfirmFilesExist( 44 | Main.ExeUnderTest, 45 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-12-img_pb-devops1_xz" -m L:\""", 46 | [ 47 | new FileDetails(@"L:\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"), 48 | new FileDetails(@"L:\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", "97a4aa50fa682b00457810d010ff5852"), 49 | new FileDetails(@"L:\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"), 50 | ]); 51 | } 52 | 53 | [TestMethod] 54 | public void zst() 55 | { 56 | ConfirmFilesExist( 57 | Main.ExeUnderTest, 58 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_zst" -m L:\""", 59 | [ 60 | new FileDetails(@"L:\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"), 61 | new FileDetails(@"L:\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", "97a4aa50fa682b00457810d010ff5852"), 62 | new FileDetails(@"L:\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"), 63 | ]); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Mount/AsFiles/LargeDriveImages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static clonezilla_util_tests.Mount.TestUtility; 7 | 8 | namespace clonezilla_util_tests.Mount.AsFiles 9 | { 10 | [TestClass] 11 | [DoNotParallelize] 12 | public class LargeDriveImages 13 | { 14 | [TestMethod] 15 | public void bzip2() 16 | { 17 | if (!Main.RunLargeTests) 18 | { 19 | Assert.Inconclusive($"Not run. ({nameof(Main.RunLargeTests)} = False)"); 20 | return; 21 | } 22 | 23 | ConfirmFilesExist( 24 | Main.ExeUnderTest, 25 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda.img.bz2" -m L:\""", 26 | [ 27 | new FileDetails(@"L:\partition0\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"), 28 | new FileDetails(@"L:\partition1\Windows\INF\cpu.inf", "b724a9d3590bab87235fe1de985e748c"), 29 | ]); 30 | } 31 | 32 | [TestMethod] 33 | public void gz() 34 | { 35 | ConfirmFilesExist( 36 | Main.ExeUnderTest, 37 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda.img.gz" -m L:\""", 38 | [ 39 | new FileDetails(@"L:\partition0\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"), 40 | new FileDetails(@"L:\partition1\Windows\INF\cpu.inf", "b724a9d3590bab87235fe1de985e748c"), 41 | ]); 42 | } 43 | 44 | [TestMethod] 45 | public void Raw() 46 | { 47 | ConfirmFilesExist( 48 | Main.ExeUnderTest, 49 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda.img" -m L:\""", 50 | [ 51 | new FileDetails(@"L:\partition0\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"), 52 | new FileDetails(@"L:\partition1\Windows\INF\cpu.inf", "b724a9d3590bab87235fe1de985e748c"), 53 | ]); 54 | } 55 | 56 | [TestMethod] 57 | public void xz() 58 | { 59 | ConfirmFilesExist( 60 | Main.ExeUnderTest, 61 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda.img.xz" -m L:\""", 62 | [ 63 | new FileDetails(@"L:\partition0\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"), 64 | new FileDetails(@"L:\partition1\Windows\INF\cpu.inf", "b724a9d3590bab87235fe1de985e748c"), 65 | ]); 66 | } 67 | 68 | [TestMethod] 69 | public void zst() 70 | { 71 | ConfirmFilesExist( 72 | Main.ExeUnderTest, 73 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda.img.zst" -m L:\""", 74 | [ 75 | new FileDetails(@"L:\partition0\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"), 76 | new FileDetails(@"L:\partition1\Windows\INF\cpu.inf", "b724a9d3590bab87235fe1de985e748c"), 77 | ]); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Mount/AsFiles/LuksClonezillaImages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static clonezilla_util_tests.Mount.TestUtility; 7 | 8 | namespace clonezilla_util_tests.Mount.AsFiles 9 | { 10 | [TestClass] 11 | [DoNotParallelize] 12 | public class LuksClonezillaImages 13 | { 14 | [TestMethod] 15 | public void luks_ntfs_20GB() 16 | { 17 | //20GB ntfs -> luks -> partclone -> zst 18 | ConfirmFilesExist( 19 | Main.ExeUnderTest, 20 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-15-img_luks_test_20GB_ntfs" --mount L:\ """, 21 | [ 22 | new FileDetails(@"L:\howdy.txt", "f521b93b9a4f632a537163f599ded439"), 23 | new FileDetails(@"L:\second_file.txt", "b1946ac92492d2347c6235b4d2611184"), 24 | ]); 25 | } 26 | 27 | [TestMethod] 28 | public void luks_ntfs_6GB() 29 | { 30 | //6GB ntfs -> luks -> partclone -> zst 31 | ConfirmFilesExist( 32 | Main.ExeUnderTest, 33 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-20-img_luks_test_6GB_ext4_zst" --mount L:\ """, 34 | [ 35 | new FileDetails(@"L:\another_file.txt", "42690a6bf443aa07821ccc51e58e950c"), 36 | new FileDetails(@"L:\hello.txt", "ce55c98ac24d4c7764877fa58ab441ef"), 37 | ]); 38 | } 39 | 40 | [TestMethod] 41 | public void luks_ext4_500GB_gz() 42 | { 43 | //500 GB ext4 -> luks -> partclone -> gz 44 | ConfirmFilesExist( 45 | Main.ExeUnderTest, 46 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-10-img_luks_test_500GB_ext4_gz" --mount L:\ """, 47 | [ 48 | new FileDetails(@"L:\file1.txt", "bad9425ff652b1bd52b49720abecf0ba"), 49 | new FileDetails(@"L:\file2.txt", "0f007fde795734c616b558bc6692c06a"), 50 | ]); 51 | } 52 | 53 | [TestMethod] 54 | public void luks_ext4_500GB_zst() 55 | { 56 | //500GB ext4 -> luks -> partclone -> zst 57 | ConfirmFilesExist( 58 | Main.ExeUnderTest, 59 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-09-img_luks_test_500GB_ext4_zst" --mount L:\ """, 60 | [ 61 | new FileDetails(@"L:\file1.txt", "bad9425ff652b1bd52b49720abecf0ba"), 62 | new FileDetails(@"L:\file2.txt", "0f007fde795734c616b558bc6692c06a"), 63 | ]); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Mount/AsFiles/LuksParcloneImages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static clonezilla_util_tests.Mount.TestUtility; 7 | 8 | namespace clonezilla_util_tests.Mount.AsFiles 9 | { 10 | [TestClass] 11 | [DoNotParallelize] 12 | public class LuksParcloneImages 13 | { 14 | [TestMethod] 15 | public void luks_ntfs_20GB() 16 | { 17 | //20GB ntfs -> luks -> partclone -> zst 18 | ConfirmFilesExist( 19 | Main.ExeUnderTest, 20 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-15-img_luks_test_20GB_ntfs\ocs_luks_esc.ntfs-ptcl-img.zst" --mount L:\ """, 21 | [ 22 | new FileDetails(@"L:\howdy.txt", "f521b93b9a4f632a537163f599ded439"), 23 | new FileDetails(@"L:\second_file.txt", "b1946ac92492d2347c6235b4d2611184"), 24 | ]); 25 | } 26 | 27 | [TestMethod] 28 | public void luks_ntfs_6GB() 29 | { 30 | //6GB ext4 -> luks -> partclone -> zst 31 | ConfirmFilesExist( 32 | Main.ExeUnderTest, 33 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-20-img_luks_test_6GB_ext4_zst\ocs_luks_0Yy.ext4-ptcl-img.zst" --mount L:\ """, 34 | [ 35 | new FileDetails(@"L:\another_file.txt", "42690a6bf443aa07821ccc51e58e950c"), 36 | new FileDetails(@"L:\hello.txt", "ce55c98ac24d4c7764877fa58ab441ef"), 37 | ]); 38 | } 39 | 40 | [TestMethod] 41 | public void luks_ext4_500GB_gz() 42 | { 43 | //500 GB ext4 -> luks->partclone->gz 44 | ConfirmFilesExist( 45 | Main.ExeUnderTest, 46 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-10-img_luks_test_500GB_ext4_gz\ocs_luks_OLi.ext4-ptcl-img.gz" --mount L:\ """, 47 | [ 48 | new FileDetails(@"L:\file1.txt", "bad9425ff652b1bd52b49720abecf0ba"), 49 | new FileDetails(@"L:\file2.txt", "0f007fde795734c616b558bc6692c06a"), 50 | ]); 51 | } 52 | 53 | [TestMethod] 54 | public void luks_ext4_500GB_zst() 55 | { 56 | //500GB ext4 -> luks->partclone->zst 57 | ConfirmFilesExist( 58 | Main.ExeUnderTest, 59 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-09-img_luks_test_500GB_ext4_zst\ocs_luks_pDw.ext4-ptcl-img.zst" --mount L:\ """, 60 | [ 61 | new FileDetails(@"L:\file1.txt", "bad9425ff652b1bd52b49720abecf0ba"), 62 | new FileDetails(@"L:\file2.txt", "0f007fde795734c616b558bc6692c06a"), 63 | ]); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /clonezilla-util_tests/Mount/AsFiles/Misc.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static clonezilla_util_tests.Mount.TestUtility; 7 | 8 | namespace clonezilla_util_tests.Mount.AsFiles 9 | { 10 | [TestClass] 11 | [DoNotParallelize] 12 | public class Misc 13 | { 14 | [TestMethod] 15 | public void MultipleContainers_MultiplePartitions() 16 | { 17 | ConfirmFilesExist( 18 | Main.ExeUnderTest, 19 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz" "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.zst" -p sda1 sdb1 partition0 --mount L:\ """, 20 | [ 21 | new FileDetails(@"L:\2022-07-17-16-img_pb-devops1_gz\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"), 22 | new FileDetails(@"L:\2022-07-17-16-img_pb-devops1_gz\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"), 23 | 24 | new FileDetails(@"L:\2021-12-28_pb-devops1_sda1.img\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e") 25 | ]); 26 | } 27 | 28 | [TestMethod] 29 | public void LastestClonezilla_2022_06_29() 30 | { 31 | ConfirmFilesExist( 32 | Main.ExeUnderTest, 33 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-06-27-20-img_small_drive" -m L:\ """, 34 | [ 35 | new FileDetails(@"L:\sda1\sda1.txt", "c3f38733914d360530455ba3b4073868"), 36 | ]); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Mount/AsFiles/Partclone.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static clonezilla_util_tests.Mount.TestUtility; 7 | 8 | namespace clonezilla_util_tests.Mount.AsFiles 9 | { 10 | [TestClass] 11 | [DoNotParallelize] 12 | public class Partclone 13 | { 14 | [TestMethod] 15 | public void PartcloneImage() 16 | { 17 | ConfirmFilesExist( 18 | Main.ExeUnderTest, 19 | """mount --input "E:\clonezilla-util-test resources\partclone images\2021-12-29 - partclone file\sdb1.ntfs-ptcl-img" -m L:\ """, 20 | [ 21 | new FileDetails(@"L:\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"), 22 | ]); 23 | } 24 | 25 | [TestMethod] 26 | public void gz() 27 | { 28 | ConfirmFilesExist( 29 | Main.ExeUnderTest, 30 | """mount --input "E:\clonezilla-util-test resources\partclone images\2022-07-17 - partclone file\sda1.ntfs-ptcl-img.gz" -m L:\ """, 31 | [ 32 | new FileDetails(@"L:\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"), 33 | ]); 34 | } 35 | 36 | [TestMethod] 37 | public void dd() 38 | { 39 | //clonezilla (partclone.dd + gz) 40 | ConfirmFilesExist( 41 | Main.ExeUnderTest, 42 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2023-11-17-00-img_PB-DEVOPS1_using_dd" -m L:\""", 43 | [ 44 | new FileDetails(@"L:\sda1\Recovery\Logs\Reload.xml", "f5a6df3c8f1ad69766afee3a25f7e376"), 45 | new FileDetails(@"L:\sda2\Program Files\PostgreSQL\13\share\information_schema.sql", "97a4aa50fa682b00457810d010ff5852"), 46 | new FileDetails(@"L:\sdb1\Kingsley\Prototype 1\Temp\Images\logo.jpg", "0217ff1926ec5f82e1a120676eff70c3"), 47 | ]); 48 | } 49 | 50 | [TestMethod] 51 | public void MixedPartcloneFormats() 52 | { 53 | //two different clonezilla formats 54 | ConfirmFilesExist( 55 | Main.ExeUnderTest, 56 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)" "E:\clonezilla-util-test resources\clonezilla images\2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)" -m L:\ """, 57 | [ 58 | new FileDetails(@"L:\2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)\sda1\sda1.txt", "c3f38733914d360530455ba3b4073868"), 59 | new FileDetails(@"L:\2020-05-23-23-img_small_drive_old_partclone (clonezilla-live-2.5.2-31)\sda2\sda2.txt", "b80328235f5d991c6dc8982e1d1876bc"), 60 | 61 | new FileDetails(@"L:\2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)\sda1\sda1.txt", "c3f38733914d360530455ba3b4073868"), 62 | new FileDetails(@"L:\2020-11-15-00-img_small_drive_new_partclone (clonezilla-live-2.6.6-15)\sda2\sda2.txt", "b80328235f5d991c6dc8982e1d1876bc"), 63 | ]); 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /clonezilla-util_tests/Mount/AsFiles/SmallPartitionImages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static clonezilla_util_tests.Mount.TestUtility; 7 | 8 | namespace clonezilla_util_tests.Mount.AsFiles 9 | { 10 | [TestClass] 11 | [DoNotParallelize] 12 | public class SmallPartitionImages 13 | { 14 | [TestMethod] 15 | public void Bzip2() 16 | { 17 | ConfirmFilesExist( 18 | Main.ExeUnderTest, 19 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.bz2" -m L:\""", 20 | [ 21 | new FileDetails(@"L:\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e"), 22 | ]); 23 | } 24 | 25 | [TestMethod] 26 | public void gz() 27 | { 28 | ConfirmFilesExist( 29 | Main.ExeUnderTest, 30 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.gz" -m L:\""", 31 | [ 32 | new FileDetails(@"L:\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e") 33 | ]); 34 | 35 | } 36 | 37 | [TestMethod] 38 | public void Raw() 39 | { 40 | ConfirmFilesExist( 41 | Main.ExeUnderTest, 42 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img" -m L:\""", 43 | [ 44 | new FileDetails(@"L:\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e") 45 | ]); 46 | } 47 | 48 | [TestMethod] 49 | public void xz() 50 | { 51 | ConfirmFilesExist( 52 | Main.ExeUnderTest, 53 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.xz" -m L:\""", 54 | [ 55 | new FileDetails(@"L:\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e") 56 | ]); 57 | } 58 | 59 | [TestMethod] 60 | public void zst() 61 | { 62 | ConfirmFilesExist( 63 | Main.ExeUnderTest, 64 | """mount --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img.zst" -m L:\""", 65 | [ 66 | new FileDetails(@"L:\Recovery\WindowsRE\ReAgent.xml", "464bd66c6443e55b791f16cb6bc28c2e") 67 | ]); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Mount/AsFiles/UbuntuFileSystems.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static clonezilla_util_tests.Mount.TestUtility; 7 | 8 | namespace clonezilla_util_tests.Mount.AsFiles 9 | { 10 | [TestClass] 11 | [DoNotParallelize] 12 | public class UbuntuFileSystems 13 | { 14 | [TestMethod] 15 | public void ext4() 16 | { 17 | //default Ubuntu file system (ext4) 18 | ConfirmFilesExist( 19 | Main.ExeUnderTest, 20 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-09-13-12-img_ubuntu_22.04_default_filesystem" --mount L:\ """, 21 | [ 22 | new FileDetails(@"L:\sda2\EFI\ubuntu\grub.cfg", "51f2a19ab5455fc3b7e2e2a0af00b9c0"), 23 | new FileDetails(@"L:\sda3\etc\fstab", "ea6ab8635f91425f7b1566a2d2f9675b"), 24 | ]); 25 | } 26 | 27 | [TestMethod] 28 | public void ext4_lvm() 29 | { 30 | //Ubuntu installed using LVM file system 31 | ConfirmFilesExist( 32 | Main.ExeUnderTest, 33 | """mount --input "E:\clonezilla-util-test resources\clonezilla images\2022-09-13-12-img_ubuntu_22.04_lvm_filesystem" --mount L:\ """, 34 | [ 35 | new FileDetails(@"L:\sda2\EFI\ubuntu\grub.cfg", "3d20729047129a9315aad73359090eab"), 36 | new FileDetails(@"L:\vgubuntu-root\etc\fstab", "8aa06a520842b37790b91ebe67ccba06"), 37 | ]); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Mount/AsImageFiles/ImageFileTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static clonezilla_util_tests.Mount.TestUtility; 7 | 8 | namespace clonezilla_util_tests.Mount.AsImageFiles 9 | { 10 | [TestClass] 11 | [DoNotParallelize] 12 | public class ImageFileTests 13 | { 14 | [TestMethod] 15 | public void Gz() 16 | { 17 | TestUtility.ConfirmFilesExist( 18 | Main.ExeUnderTest, 19 | """mount-as-image-files --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz" -p sda1 -m L:\ """, 20 | [ 21 | new FileDetails(@"L:\sda1.img", "d9a64d07801cb9f5878a72da4c5d53fd"), 22 | ]); 23 | } 24 | 25 | [TestMethod] 26 | public void Partclone() 27 | { 28 | //partclone image 29 | TestUtility.ConfirmFilesExist( 30 | Main.ExeUnderTest, 31 | """mount-as-image-files --input "E:\clonezilla-util-test resources\partclone images\2021-12-29 - partclone file\sdb1.ntfs-ptcl-img" -m L:\ """, 32 | [ 33 | new FileDetails(@"L:\sdb1.ntfs-ptcl-img.img", "82b4fbf40812fe3354c855ee10fdc839", 1024 * 1024 * 1024), 34 | ]); 35 | } 36 | 37 | [TestMethod] 38 | public void LuksNtfs6GB() 39 | { 40 | //6GB ext4 -> luks -> partclone -> zst 41 | TestUtility.ConfirmFilesExist( 42 | Main.ExeUnderTest, 43 | """mount-as-image-files --input "E:\clonezilla-util-test resources\clonezilla images\2022-08-16-20-img_luks_test_6GB_ext4_zst\ocs_luks_0Yy.ext4-ptcl-img.zst" --mount L:\ """, 44 | [ 45 | new FileDetails(@"L:\partition0.img", "a3217b31c6f2e5270174a9c536a90242"), 46 | ]); 47 | } 48 | 49 | [TestMethod] 50 | public void Zst() 51 | { 52 | TestUtility.ConfirmFilesExist( 53 | Main.ExeUnderTest, 54 | """mount-as-image-files --input "E:\clonezilla-util-test resources\clonezilla images\2022-07-16-22-img_pb-devops1_zst" -p sda1 -m L:\ """, 55 | [ 56 | new FileDetails(@"L:\sda1.img", "d9a64d07801cb9f5878a72da4c5d53fd"), 57 | ]); 58 | } 59 | 60 | [TestMethod] 61 | public void UncompressedPartitionImage_and_gzClonezillaImage() 62 | { 63 | //uncompressed partition image and gz clonezilla image 64 | TestUtility.ConfirmFilesExist( 65 | Main.ExeUnderTest, 66 | """mount-as-image-files --input "E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img" "E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz" -p sda1 partition0 -m L:\ """, 67 | [ 68 | new FileDetails(@"L:\2021-12-28_pb-devops1_sda1\partition0.img", "d4559685d050cb9682369aad7fa0ba24"), 69 | new FileDetails(@"L:\2022-07-17-16-img_pb-devops1_gz\sda1.img", "d9a64d07801cb9f5878a72da4c5d53fd"), 70 | ]); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Sparse/SparseTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace clonezilla_util_tests.Sparse 11 | { 12 | [TestClass] 13 | public class SparseTests 14 | { 15 | [TestMethod] 16 | public void ExtractAndSparsifyFile() 17 | { 18 | var outputFolder = Directory.CreateTempSubdirectory().FullName; 19 | 20 | var args = @$"extract-partition-image --input ""E:\clonezilla-util-test resources\clonezilla images\2022-07-17-16-img_pb-devops1_gz"" --output ""{outputFolder}"" -p sda2"; 21 | 22 | var psi = new ProcessStartInfo(Main.ExeUnderTest, args) 23 | { 24 | UseShellExecute = false, 25 | CreateNoWindow = true 26 | }; 27 | var process = Process.Start(psi); 28 | process?.WaitForExit(); 29 | 30 | var extractedFilename = Directory.GetFiles(outputFolder).First(); 31 | var fileSize = new FileInfo(extractedFilename).Length; 32 | var sizeOnDisk = GetFileSizeOnDisk(extractedFilename); 33 | Directory.Delete(outputFolder, true); 34 | 35 | var success = sizeOnDisk / (double)fileSize < 0.5; 36 | Assert.IsTrue(success, "Size On Disk is not smaller than file size"); 37 | } 38 | 39 | public static long GetFileSizeOnDisk(string file) 40 | { 41 | var info = new FileInfo(file); 42 | if (info == null) return -1; 43 | if (info.Directory == null) return -1; 44 | var result = GetDiskFreeSpaceW(info.Directory.Root.FullName, out uint sectorsPerCluster, out uint bytesPerSector, out _, out _); 45 | if (result == 0) throw new Win32Exception(); 46 | uint clusterSize = sectorsPerCluster * bytesPerSector; 47 | uint losize = GetCompressedFileSizeW(file, out uint hosize); 48 | long size; 49 | size = (long)hosize << 32 | losize; 50 | return ((size + clusterSize - 1) / clusterSize) * clusterSize; 51 | } 52 | 53 | [DllImport("kernel32.dll")] 54 | static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName, 55 | [Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh); 56 | 57 | [DllImport("kernel32.dll", SetLastError = true, PreserveSig = true)] 58 | static extern int GetDiskFreeSpaceW([In, MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName, 59 | out uint lpSectorsPerCluster, out uint lpBytesPerSector, out uint lpNumberOfFreeClusters, 60 | out uint lpTotalNumberOfClusters); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /clonezilla-util_tests/Train/TrainTests.cs: -------------------------------------------------------------------------------- 1 | using libTrainCompress; 2 | using libTrainCompress.Compressors; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using libCommon; 9 | using libCommon.Streams; 10 | using libCommon.Streams.Sparse; 11 | using System.Diagnostics; 12 | 13 | namespace clonezilla_util_tests.Train 14 | { 15 | [TestClass] 16 | public class TrainTests 17 | { 18 | [TestMethod] 19 | public void Zst() 20 | { 21 | TestTrain( 22 | @"E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img", 23 | [new zstdCompressor()] 24 | ); 25 | } 26 | 27 | [TestMethod] 28 | public void Gz() 29 | { 30 | TestTrain( 31 | @"E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img", 32 | [new gzCompressor()] 33 | ); 34 | } 35 | 36 | [TestMethod] 37 | public void Xz() 38 | { 39 | TestTrain( 40 | @"E:\clonezilla-util-test resources\drive images\ddrescue backups (even includes deleted)\2021-12-28_pb-devops1_sda1.img", 41 | [new xzCompressor()] 42 | ); 43 | } 44 | 45 | public static void TestTrain(string inputFilename, IList compressors) 46 | { 47 | var originalFileStream = File.OpenRead(inputFilename); 48 | 49 | var compressedStream = new MemoryStream(); 50 | using (var trainCompressor = new TrainCompressor(compressedStream, compressors, 10 * 1024 * 1024)) 51 | { 52 | originalFileStream.CopyTo(trainCompressor, 50 * 1024 * 1024, progress => 53 | { 54 | Debug.WriteLine($"Compressing {progress.Read.BytesToString()}"); 55 | }); 56 | } 57 | 58 | 59 | var uncompressedOutputStream = new MemoryStream(); 60 | compressedStream.Seek(0, SeekOrigin.Begin); 61 | using (var trainDecompressor = new TrainDecompressor(compressedStream, compressors)) 62 | { 63 | trainDecompressor.CopyTo(uncompressedOutputStream, 50 * 1024 * 1024, progress => 64 | { 65 | Debug.WriteLine($"Decompressing {progress.Read.BytesToString()}"); 66 | }); 67 | 68 | uncompressedOutputStream.Seek(0, SeekOrigin.Begin); 69 | } 70 | 71 | 72 | var originalMd5 = Utility.CalculateMD5(originalFileStream); 73 | var outputMd5 = Utility.CalculateMD5(uncompressedOutputStream); 74 | 75 | var success = originalMd5.Equals(outputMd5); 76 | Assert.IsTrue(success, "MD5 hashes do not match"); 77 | 78 | 79 | //test random seeking 80 | uncompressedOutputStream = new MemoryStream(); 81 | compressedStream.Seek(0, SeekOrigin.Begin); 82 | using (var trainDecompressor = new TrainDecompressor(compressedStream, compressors)) 83 | { 84 | Utilities.Utility.TestSeeking(trainDecompressor, uncompressedOutputStream); 85 | 86 | outputMd5 = Utility.CalculateMD5(uncompressedOutputStream); 87 | 88 | success = originalMd5.Equals(outputMd5); 89 | Assert.IsTrue(success, "MD5 hashes do not match after random seeking"); 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /clonezilla-util_tests/clonezilla-util_tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | clonezilla_util_tests 6 | enable 7 | enable 8 | 9 | false 10 | true 11 | x64 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib7Zip/ArchiveEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace lib7Zip 8 | { 9 | public class ArchiveEntry 10 | { 11 | public string Path; 12 | 13 | public ArchiveEntry(string path) 14 | { 15 | Path = path; 16 | } 17 | 18 | public bool IsFolder; 19 | public long Size; 20 | public DateTime Modified; 21 | public DateTime Created; 22 | public DateTime Accessed; 23 | public long? Offset; 24 | 25 | public override string ToString() 26 | { 27 | return Path; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib7Zip/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Interoperability", "SYSLIB1054:Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time", Justification = "", Scope = "module")] 9 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "module")] 10 | [assembly: SuppressMessage("Reliability", "CA2020:Prevent behavioral change", Justification = "", Scope = "module")] 11 | -------------------------------------------------------------------------------- /lib7Zip/SevenZipExtractorUsingSevenZipExtractor.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using SevenZip; 3 | using SevenZipExtractor; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace lib7Zip 11 | { 12 | public class SevenZipExtractorUsingSevenZipExtractor 13 | { 14 | readonly ArchiveFile archiveFile; 15 | 16 | public SevenZipExtractorUsingSevenZipExtractor(string archiveFilename) 17 | { 18 | archiveFile = new ArchiveFile(archiveFilename); 19 | } 20 | 21 | public SevenZipExtractorUsingSevenZipExtractor(Stream stream) 22 | { 23 | archiveFile = new ArchiveFile(stream); 24 | } 25 | 26 | public void ExtractFile(string archiveEntryPath, Stream stream) 27 | { 28 | var entry = archiveFile 29 | .Entries 30 | .FirstOrDefault(entry => entry.FileName.Equals(archiveEntryPath, StringComparison.OrdinalIgnoreCase)); 31 | 32 | entry?.Extract(stream); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib7Zip/UI/WinAPI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace lib7Zip.UI 9 | { 10 | static class WinAPI 11 | { 12 | 13 | public enum ListViewMessages 14 | { 15 | LVM_GETITEMTEXT = 0x104B 16 | } 17 | 18 | public enum ListViewItemFilters : uint 19 | { 20 | LVIF_TEXT = 0x0001, 21 | } 22 | 23 | public const uint MAX_LVMSTRING = 255; 24 | 25 | [StructLayoutAttribute(LayoutKind.Sequential)] 26 | public struct LVITEM 27 | { 28 | public uint mask; 29 | public int iItem; 30 | public int iSubItem; 31 | public uint state; 32 | public uint stateMask; 33 | public IntPtr pszText; 34 | public int cchTextMax; 35 | public int iImage; 36 | public IntPtr lParam; 37 | } 38 | 39 | [DllImport("user32.dll")] 40 | public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam); 41 | 42 | [DllImport("user32.dll")] 43 | public static extern uint GetWindowThreadProcessId(IntPtr hWnd, ref uint lpdwProcessId); 44 | 45 | 46 | [Flags] 47 | public enum ProcessAccessFlags : uint 48 | { 49 | VirtualMemoryOperation = 0x0008, 50 | VirtualMemoryRead = 0x0010, 51 | VirtualMemoryWrite = 0x0020, 52 | } 53 | 54 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)] 55 | public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect); 56 | 57 | [Flags] 58 | public enum AllocationType 59 | { 60 | Commit = 0x1000, 61 | Release = 0x8000, 62 | } 63 | 64 | [Flags] 65 | public enum MemoryProtection 66 | { 67 | ReadWrite = 0x0004, 68 | } 69 | 70 | [DllImport("kernel32.dll")] 71 | public static extern IntPtr OpenProcess(ProcessAccessFlags processAccess, bool bInheritHandle, uint processId); 72 | 73 | [DllImport("kernel32.dll")] 74 | public static extern bool CloseHandle(IntPtr hHandle); 75 | 76 | [DllImport("kernel32.dll")] 77 | public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, IntPtr lpBuffer, uint nSize, out int lpNumberOfBytesWritten); 78 | 79 | [DllImport("kernel32.dll")] 80 | public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] buffer, int dwSize, out IntPtr lpNumberOfBytesRead); 81 | 82 | [DllImport("kernel32.dll")] 83 | public static extern bool VirtualFreeEx(IntPtr hProcess, IntPtr lpAddress, int dwSize, AllocationType dwFreeType); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib7Zip/ext/7-Zip/win-x64/7z.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x64/7z.dll -------------------------------------------------------------------------------- /lib7Zip/ext/7-Zip/win-x64/7z.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x64/7z.exe -------------------------------------------------------------------------------- /lib7Zip/ext/7-Zip/win-x64/7zFM.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x64/7zFM.exe -------------------------------------------------------------------------------- /lib7Zip/ext/7-Zip/win-x86/7z.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x86/7z.dll -------------------------------------------------------------------------------- /lib7Zip/ext/7-Zip/win-x86/7z.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x86/7z.exe -------------------------------------------------------------------------------- /lib7Zip/ext/7-Zip/win-x86/7zFM.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/lib7Zip/ext/7-Zip/win-x86/7zFM.exe -------------------------------------------------------------------------------- /lib7Zip/lib7Zip.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | AnyCPU 8 | true 9 | 10 | 11 | 12 | 13 | 14 | PreserveNewest 15 | 16 | 17 | PreserveNewest 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | PreserveNewest 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /libBzip2/BZip2BlockFinder.cs: -------------------------------------------------------------------------------- 1 | using libCommon; 2 | using libDecompression.Utilities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace libBzip2 12 | { 13 | public static class BZip2BlockFinder 14 | { 15 | private static readonly byte[] StartOfBlockMagic = [0x31, 0x41, 0x59, 0x26, 0x53, 0x59]; //31 41 59 26 53 59 16 | //private static readonly byte[] EndOfBlockMagic = [0x17, 0x72, 0x45, 0x38, 0x50, 0x90]; //17 72 45 38 50 90 //This 'end of block' marker doesn't seem to appear in the file 17 | 18 | public static IEnumerable<(long Start, long End, bool IsLast)> FindBlocks(Stream stream) 19 | { 20 | long? startOfBlock = null; 21 | foreach (var foundPosition in FindInstances(stream, StartOfBlockMagic)) 22 | { 23 | if (startOfBlock != null) 24 | { 25 | var endOfBlock = foundPosition; 26 | //Debug.WriteLine($"{startOfBlock:N0} - {endOfBlock:N0}"); 27 | yield return (startOfBlock.Value, endOfBlock, false); 28 | startOfBlock = null; 29 | } 30 | 31 | startOfBlock = foundPosition; 32 | } 33 | 34 | if (startOfBlock != null) 35 | { 36 | yield return (startOfBlock.Value, stream.Length, true); 37 | } 38 | } 39 | 40 | public static IEnumerable FindInstances(this Stream stream, byte[] find) 41 | { 42 | var buff = new byte[1 * 1024 * 1024]; 43 | var ms = new MemoryStream(buff); 44 | 45 | while (true) 46 | { 47 | var bufferPositionInStream = stream.Position; 48 | 49 | ms.Seek(0, SeekOrigin.Begin); 50 | var bytesRead = (int)stream.CopyTo(ms, buff.Length, buff.Length); 51 | if (bytesRead == 0) break; 52 | 53 | var positionThroughCurrentBuffer = 0; 54 | while (true) 55 | { 56 | var bytesRemainingInBuffer = bytesRead - positionThroughCurrentBuffer; 57 | var subsectionToSearch = new Span(buff, positionThroughCurrentBuffer, bytesRemainingInBuffer); 58 | var foundPositionInBufferSubsection = BoyerMoore.IndexOf(subsectionToSearch, find); 59 | 60 | if (foundPositionInBufferSubsection == -1) 61 | { 62 | break; 63 | } 64 | else 65 | { 66 | var foundPositionInStream = bufferPositionInStream + positionThroughCurrentBuffer + foundPositionInBufferSubsection; 67 | yield return foundPositionInStream; 68 | 69 | positionThroughCurrentBuffer = positionThroughCurrentBuffer + foundPositionInBufferSubsection + 1; 70 | } 71 | } 72 | 73 | //just in case the search pattern is right on the border 74 | if (stream.Position == stream.Length) 75 | { 76 | break; 77 | } 78 | else 79 | { 80 | stream.Seek(-find.Length, SeekOrigin.Current); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /libBzip2/libBzip2.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /libClonezilla/Cache/ClonezillaCacheManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Linq; 6 | using libCommon; 7 | 8 | namespace libClonezilla.Cache 9 | { 10 | public class ClonezillaCacheManager : IClonezillaCacheManager 11 | { 12 | public ClonezillaCacheManager(string clonezillaFolder, string cacheRootFolder) 13 | { 14 | ClonezillaFolder = clonezillaFolder; 15 | CacheRootFolder = cacheRootFolder; 16 | } 17 | 18 | public string ClonezillaFolder { get; } 19 | public string CacheRootFolder { get; } 20 | 21 | public IPartitionCache GetPartitionCache(string partitionName) 22 | { 23 | string imgIdFilename = Path.Combine(ClonezillaFolder, "Info-img-id.txt"); 24 | 25 | string? uniqueIdForClonezillaImage; 26 | 27 | if (File.Exists(imgIdFilename)) 28 | { 29 | uniqueIdForClonezillaImage = File 30 | .ReadAllLines(imgIdFilename) 31 | .First(line => line.StartsWith("IMG_ID=")) 32 | .Split("=", StringSplitOptions.None)[1][..16]; 33 | } 34 | else 35 | { 36 | //The file we normally use to get a unique id isn't present. Let's calculate a hash based on small files. 37 | 38 | var smallFileHashes = Directory 39 | .GetFiles(ClonezillaFolder) 40 | .Where(filename => new FileInfo(filename).Length < 1024 * 1024) 41 | .Take(100) 42 | .Select(filename => libCommon.Utility.CalculateMD5(filename)) 43 | .ToString(Environment.NewLine); 44 | var smallFileHashesBytes = Encoding.UTF8.GetBytes(smallFileHashes); 45 | 46 | uniqueIdForClonezillaImage = libCommon.Utility.CalculateMD5(smallFileHashesBytes); 47 | } 48 | 49 | var clonezillaCacheFolder = Path.Combine(CacheRootFolder, uniqueIdForClonezillaImage); 50 | if (!Directory.Exists(clonezillaCacheFolder)) 51 | { 52 | Directory.CreateDirectory(clonezillaCacheFolder); 53 | } 54 | 55 | var result = new PartitionCache(clonezillaCacheFolder, partitionName); 56 | return result; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /libClonezilla/Cache/FileSystem/FileDetails.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace libClonezilla.Cache.FileSystem 6 | { 7 | public class FileDetails : FileSystemObject 8 | { 9 | public long Length; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libClonezilla/Cache/FileSystem/FileSystemObject.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace libClonezilla.Cache.FileSystem 7 | { 8 | public class FileSystemObject 9 | { 10 | public string? FullPath; 11 | public DateTime LastModifiedDate; 12 | public DateTime CreationDate; 13 | 14 | public override string ToString() 15 | { 16 | return FullPath ?? ""; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libClonezilla/Cache/FileSystem/FolderDetails.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace libClonezilla.Cache.FileSystem 7 | { 8 | public class FolderDetails : FileSystemObject 9 | { 10 | //[JsonIgnoreAttribute] 11 | //public List Children; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /libClonezilla/Cache/IClonezillaCacheManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace libClonezilla.Cache 6 | { 7 | public interface IClonezillaCacheManager 8 | { 9 | public IPartitionCache GetPartitionCache(string partitionName); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libClonezilla/Cache/IPartitionCache.cs: -------------------------------------------------------------------------------- 1 | using lib7Zip; 2 | using libClonezilla.Cache.FileSystem; 3 | using libClonezilla.Partitions; 4 | using libCommon.Streams; 5 | using libDokan.VFS.Files; 6 | using libDokan.VFS.Folders; 7 | using libPartclone; 8 | using libPartclone.Cache; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Text; 12 | 13 | namespace libClonezilla.Cache 14 | { 15 | public interface IPartitionCache 16 | { 17 | public string GetGztoolIndexFilename(); 18 | public string GetBZip2IndexFilename(); 19 | public List? GetFileList(); 20 | public void SetFileList(List filenames); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /libClonezilla/Cache/PartitionCache.cs: -------------------------------------------------------------------------------- 1 | using libClonezilla.Cache.FileSystem; 2 | using System.Linq; 3 | using libClonezilla.Partitions; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Text; 8 | using Newtonsoft.Json; 9 | using libPartclone; 10 | using libCommon.Streams; 11 | using System.Collections.ObjectModel; 12 | using libPartclone.Cache; 13 | using lib7Zip; 14 | using libDokan.VFS.Files; 15 | using libDokan.VFS.Folders; 16 | using Serilog; 17 | 18 | namespace libClonezilla.Cache 19 | { 20 | public class PartitionCache : IPartcloneCache, IPartitionCache 21 | { 22 | public string ClonezillaCacheFolder { get; } 23 | public string PartitionName { get; } 24 | 25 | private readonly string PartcloneContentMappingFilename; 26 | private readonly string FileListFilename; 27 | 28 | public PartitionCache(string clonezillaCacheFolder, string partitionName) 29 | { 30 | ClonezillaCacheFolder = clonezillaCacheFolder; 31 | PartitionName = partitionName; 32 | 33 | PartcloneContentMappingFilename = Path.Combine(ClonezillaCacheFolder, $"{partitionName}.PartcloneContentMapping.json"); 34 | FileListFilename = Path.Combine(ClonezillaCacheFolder, $"{partitionName}.Files.json"); 35 | } 36 | 37 | public string GetGztoolIndexFilename() 38 | { 39 | var result = Path.Combine(ClonezillaCacheFolder, $"{PartitionName}.gztool_index.gzi"); 40 | return result; 41 | } 42 | 43 | public string GetBZip2IndexFilename() 44 | { 45 | var result = Path.Combine(ClonezillaCacheFolder, $"{PartitionName}.bzip2_index.json"); 46 | return result; 47 | } 48 | 49 | 50 | public List? GetPartcloneContentMapping() 51 | { 52 | List? result = null; 53 | 54 | if (File.Exists(PartcloneContentMappingFilename)) 55 | { 56 | var json = File.ReadAllText(PartcloneContentMappingFilename); 57 | result = JsonConvert.DeserializeObject>(json); 58 | } 59 | 60 | return result; 61 | } 62 | 63 | public void SetPartcloneContentMapping(List contiguousRanges) 64 | { 65 | try 66 | { 67 | var json = JsonConvert.SerializeObject(contiguousRanges, Formatting.Indented); 68 | File.WriteAllText(PartcloneContentMappingFilename, json); 69 | } 70 | catch (Exception ex) 71 | { 72 | Log.Warning($"Non-fatal. Error while caching Partclone Content Mapping to {PartcloneContentMappingFilename}: {ex}"); 73 | } 74 | } 75 | 76 | public List? GetFileList() 77 | { 78 | List? result = null; 79 | 80 | if (File.Exists(FileListFilename)) 81 | { 82 | string json = File.ReadAllText(FileListFilename); 83 | result = JsonConvert.DeserializeObject>(json); 84 | } 85 | 86 | return result; 87 | } 88 | 89 | public void SetFileList(List filenames) 90 | { 91 | try 92 | { 93 | var json = JsonConvert.SerializeObject(filenames, Formatting.Indented); 94 | File.WriteAllText(FileListFilename, json); 95 | } 96 | catch (Exception ex) 97 | { 98 | Log.Warning($"Non-fatal. Error while caching File List to {FileListFilename}: {ex}"); 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /libClonezilla/Cache/WholeFileCacheManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libClonezilla.Cache 9 | { 10 | public static class WholeFileCacheManager 11 | { 12 | public static string RootCacheFolder { get; private set; } = ""; 13 | public static void Initialize(string cacheFolder) 14 | { 15 | RootCacheFolder = cacheFolder; 16 | Directory.CreateDirectory(RootCacheFolder); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libClonezilla/Decompressors/Decompressor.cs: -------------------------------------------------------------------------------- 1 | using libCommon; 2 | using libPartclone; 3 | using SharpCompress.Compressors.BZip2; 4 | using SharpCompress.Compressors.Xz; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.IO.Compression; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using ZstdNet; 13 | 14 | namespace libClonezilla.Decompressors 15 | { 16 | public abstract class Decompressor 17 | { 18 | public Stream CompressedStream { get; } 19 | 20 | public abstract Stream GetSequentialStream(); 21 | public abstract Stream? GetSeekableStream(); 22 | 23 | public Decompressor(Stream compressedStream) 24 | { 25 | CompressedStream = compressedStream; 26 | } 27 | 28 | public static Compression GetCompressionType(Stream compressedStream) 29 | { 30 | Compression result = Compression.None; 31 | 32 | var decompressors = new (Compression Compression, Func Stream)[] 33 | { 34 | (Compression.bzip2, () => new BZip2Stream(compressedStream, SharpCompress.Compressors.CompressionMode.Decompress, false)), 35 | (Compression.Gzip, () => new GZipStream(compressedStream, CompressionMode.Decompress)), 36 | (Compression.xz, () => new XZStream(compressedStream)), 37 | (Compression.Zstandard, () => new DecompressionStream(compressedStream)), 38 | }; 39 | 40 | foreach (var decompressor in decompressors) 41 | { 42 | compressedStream.Seek(0, SeekOrigin.Begin); 43 | 44 | try 45 | { 46 | //extract a little and see what happens 47 | decompressor.Stream().CopyTo(Stream.Null, 32, 32); 48 | result = decompressor.Compression; 49 | break; 50 | } 51 | catch { } 52 | } 53 | 54 | compressedStream.Seek(0, SeekOrigin.Begin); 55 | 56 | return result; 57 | } 58 | } 59 | 60 | public enum Compression 61 | { 62 | bzip2, 63 | Gzip, 64 | LZ4, 65 | LZip, 66 | None, 67 | xz, 68 | Zstandard 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /libClonezilla/Decompressors/GzDecompressor.cs: -------------------------------------------------------------------------------- 1 | using libClonezilla.Cache; 2 | using libCommon.Streams.Seekable; 3 | using libGZip; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.IO.Compression; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace libClonezilla.Decompressors 13 | { 14 | public class GzDecompressor(Stream compressedStream, IPartitionCache? partitionCache) : Decompressor(compressedStream) 15 | { 16 | public IPartitionCache? PartitionCache { get; } = partitionCache; 17 | 18 | public override Stream? GetSeekableStream() 19 | { 20 | if (PartitionCache == null) 21 | { 22 | return null; 23 | } 24 | else 25 | { 26 | //this is faster for random seeks (eg. serving the full image (or file contents) in a Virtual File System) 27 | //Uses gztool to create an index for fast seek, plus a cache layer to avoid using gztool for small reads 28 | 29 | var gztoolIndexFilename = PartitionCache.GetGztoolIndexFilename(); 30 | var tempgztoolIndexFilename = gztoolIndexFilename + ".wip"; 31 | 32 | var gzipStreamSeekable = new GZipStreamSeekable(CompressedStream, tempgztoolIndexFilename, gztoolIndexFilename); 33 | 34 | return gzipStreamSeekable; 35 | } 36 | 37 | //return null; 38 | } 39 | 40 | public override Stream GetSequentialStream() 41 | { 42 | CompressedStream.Seek(0, SeekOrigin.Begin); 43 | var uncompressedStream = new GZipStream(CompressedStream, CompressionMode.Decompress); 44 | return uncompressedStream; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /libClonezilla/Decompressors/LZ4Decompressor.cs: -------------------------------------------------------------------------------- 1 | using K4os.Compression.LZ4.Streams; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace libClonezilla.Decompressors 10 | { 11 | public class LZ4Decompressor : Decompressor 12 | { 13 | public LZ4Decompressor(Stream compressedStream) : base(compressedStream) 14 | { 15 | } 16 | 17 | public override Stream? GetSeekableStream() 18 | { 19 | return null; 20 | } 21 | 22 | public override Stream GetSequentialStream() 23 | { 24 | CompressedStream.Seek(0, SeekOrigin.Begin); 25 | var result = LZ4Stream.Decode(CompressedStream, null, true, false); 26 | return result; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /libClonezilla/Decompressors/LZipDecompressor.cs: -------------------------------------------------------------------------------- 1 | using K4os.Compression.LZ4.Streams; 2 | using SharpCompress.Compressors.LZMA; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace libClonezilla.Decompressors 11 | { 12 | public class LZipDecompressor : Decompressor 13 | { 14 | public LZipDecompressor(Stream compressedStream) : base(compressedStream) 15 | { 16 | } 17 | 18 | public override Stream? GetSeekableStream() 19 | { 20 | return null; 21 | } 22 | 23 | public override Stream GetSequentialStream() 24 | { 25 | CompressedStream.Seek(0, SeekOrigin.Begin); 26 | var result = new LZipStream(CompressedStream, SharpCompress.Compressors.CompressionMode.Decompress); 27 | return result; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /libClonezilla/Decompressors/NoChangeDecompressor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libClonezilla.Decompressors 9 | { 10 | public class NoChangeDecompressor : Decompressor 11 | { 12 | public NoChangeDecompressor(Stream compressedStream) : base(compressedStream) 13 | { 14 | } 15 | 16 | public override Stream? GetSeekableStream() 17 | { 18 | CompressedStream.Seek(0, SeekOrigin.Begin); 19 | return CompressedStream; 20 | } 21 | 22 | public override Stream GetSequentialStream() 23 | { 24 | CompressedStream.Seek(0, SeekOrigin.Begin); 25 | return CompressedStream; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /libClonezilla/Decompressors/bzip2Decompressor.cs: -------------------------------------------------------------------------------- 1 | using libBzip2; 2 | using libClonezilla.Cache; 3 | using libCommon; 4 | using libGZip; 5 | using Serilog; 6 | using SharpCompress.Compressors.BZip2; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace libClonezilla.Decompressors 15 | { 16 | public class Bzip2Decompressor(Stream compressedStream, IPartitionCache? partitionCache, bool processTrailingNulls) : Decompressor(compressedStream) 17 | { 18 | public IPartitionCache? PartitionCache { get; } = partitionCache; 19 | public bool ProcessTrailingNulls { get; } = processTrailingNulls; 20 | 21 | public override Stream? GetSeekableStream() 22 | { 23 | var indexFilename = PartitionCache?.GetBZip2IndexFilename(); 24 | 25 | var seekableStream = new Bzip2StreamSeekable(CompressedStream, indexFilename, ProcessTrailingNulls); 26 | 27 | return seekableStream; 28 | } 29 | 30 | public override Stream GetSequentialStream() 31 | { 32 | CompressedStream.Seek(0, SeekOrigin.Begin); 33 | var result = new BZip2Stream(CompressedStream, SharpCompress.Compressors.CompressionMode.Decompress, false); 34 | 35 | return result; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /libClonezilla/Decompressors/xzDecompressor.cs: -------------------------------------------------------------------------------- 1 | using libCommon.Streams; 2 | using SharpCompress.Compressors.Xz; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace libClonezilla.Decompressors 11 | { 12 | #pragma warning disable IDE1006 // Naming Styles 13 | public class xzDecompressor : Decompressor 14 | #pragma warning restore IDE1006 // Naming Styles 15 | { 16 | public xzDecompressor(Stream compressedStream) : base(compressedStream) 17 | { 18 | 19 | } 20 | 21 | public override Stream? GetSeekableStream() 22 | { 23 | return null; 24 | } 25 | 26 | public override Stream GetSequentialStream() 27 | { 28 | CompressedStream.Seek(0, SeekOrigin.Begin); 29 | var result = new XZStream(CompressedStream); 30 | return result; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libClonezilla/Decompressors/zstdDecompressor.cs: -------------------------------------------------------------------------------- 1 | using libCommon; 2 | using libCommon.Streams.Seekable; 3 | using Serilog; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using ZstdNet; 11 | 12 | namespace libClonezilla.Decompressors 13 | { 14 | public class ZstdDecompressor : Decompressor 15 | { 16 | public ZstdDecompressor(Stream compressedStream) : base(compressedStream) 17 | { 18 | 19 | } 20 | 21 | public override Stream? GetSeekableStream() 22 | { 23 | return null; 24 | //For now, let's extract it to a file so that we can have fast seeking 25 | /* 26 | Log.Information($"Zstandard doesn't support random seeking. Extracting to a temporary file."); 27 | 28 | var decompressor = new DecompressionStream(CompressedStream); 29 | 30 | var tempFilename = TempUtility.GetTempFilename(true); 31 | var tempFileStream = new FileStream(tempFilename, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose); 32 | 33 | decompressor.CopyTo(tempFileStream, Buffers.ARBITARY_HUGE_SIZE_BUFFER, 34 | totalCopied => 35 | { 36 | var totalCopiedStr = libCommon.Extensions.BytesToString(totalCopied); 37 | Log.Information($"Extracted {totalCopiedStr} of Zstandard-compressed file to {tempFilename}."); 38 | }); 39 | 40 | return tempFileStream; 41 | */ 42 | 43 | 44 | //FPS 04/01/2021: An experiment to park partially processed streams all over the file. Unfortunately this was still too slow 45 | /* 46 | var zstdDecompressorGenerator = new Func(() => 47 | { 48 | var compressedStream = compressedStreamGenerator(); 49 | var zstdDecompressorStream = new DecompressionStream(compressedStream); 50 | 51 | //the Zstandard decompressor doesn't track position in stream, so we have to do it for them 52 | var positionTrackerStream = new PositionTrackerStream(zstdDecompressorStream); 53 | 54 | return positionTrackerStream; 55 | }); 56 | 57 | uncompressedStream = new SeekableStreamUsingNearestActioner(zstdDecompressorGenerator, totalLength, 1 * 1024 * 1024); //stations should be within one second of an actioner. 58 | */ 59 | } 60 | public override Stream GetSequentialStream() 61 | { 62 | CompressedStream.Seek(0, SeekOrigin.Begin); 63 | var uncompressedStream = new DecompressionStream(CompressedStream); 64 | return uncompressedStream; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /libClonezilla/Extractors/CachedExtractor.cs: -------------------------------------------------------------------------------- 1 | using libCommon; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace libClonezilla.Extractors 11 | { 12 | public class CachedExtractor : IExtractor 13 | { 14 | readonly ConcurrentDictionary AlreadyExtracted = new(); 15 | 16 | public CachedExtractor(IExtractor baseExtractor) 17 | { 18 | BaseExtractor = baseExtractor; 19 | } 20 | 21 | public IExtractor BaseExtractor { get; } 22 | 23 | public Stream Extract(string path) 24 | { 25 | var result = AlreadyExtracted 26 | .GetOrAdd(path, path => 27 | { 28 | Stream stream = BaseExtractor.Extract(path); 29 | 30 | return stream; 31 | }); 32 | 33 | return result; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /libClonezilla/Extractors/ExtractorUsing7z.cs: -------------------------------------------------------------------------------- 1 | using lib7Zip; 2 | using libCommon; 3 | using MountDocushare.Streams; 4 | using Serilog; 5 | using System; 6 | using System.Collections.Concurrent; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace libClonezilla.Extractors 14 | { 15 | public class ExtractorUsing7z(string archiveFilename) : IExtractor 16 | { 17 | public string ArchiveFilename { get; } = archiveFilename; 18 | 19 | public Stream Extract(string pathInArchive) 20 | { 21 | /* 22 | var stream = new MemoryStream(); 23 | //lock (realImageFile) //No need to synchronise here, as the PartcloneStream already protects itself from concurrent access 24 | { 25 | SevenZipUtility.ExtractFileFromArchive(ArchiveFilename, pathInArchive, stream); 26 | } 27 | stream.Seek(0, SeekOrigin.Begin); 28 | return stream; 29 | */ 30 | 31 | var processStream = SevenZipUtility.ExtractFileFromArchive(ArchiveFilename, pathInArchive); 32 | 33 | //var tempStorageStream = new MemoryStream(); //can't use a MemoryStream because it has a limit of 2GB 34 | var tempFilename = TempUtility.GetTempFilename(true); 35 | var tempStorageStream = new FileStream(tempFilename, FileMode.Open, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose); 36 | 37 | var result = new SiphonStream(processStream, tempStorageStream); //this will return data as soon as it arrives from the process 38 | 39 | /* 40 | processStream.CopyTo(tempStorageStream); 41 | tempStorageStream.Seek(0, SeekOrigin.Begin); 42 | var result = tempStorageStream; 43 | */ 44 | 45 | //processStream.CopyTo(tempStorageStream); 46 | //var result = tempStorageStream; 47 | 48 | return result; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /libClonezilla/Extractors/ExtractorUsing7zFM.cs: -------------------------------------------------------------------------------- 1 | using lib7Zip; 2 | using libCommon; 3 | using System; 4 | using System.Collections.Concurrent; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace libClonezilla.Extractors 12 | { 13 | public class ExtractorUsing7zFM(string archiveFilename) : IExtractor 14 | { 15 | readonly SevenZipExtractorUsing7zFM FileManagerExtractor = new(archiveFilename); 16 | 17 | public Stream Extract(string pathInArchive) 18 | { 19 | var tempFolder = TempUtility.GetTemporaryDirectory(); 20 | 21 | FileManagerExtractor.ExtractFile(pathInArchive, tempFolder); 22 | 23 | var tempFilename = Directory.GetFiles(tempFolder).First(); 24 | 25 | Stream result = File.OpenRead(tempFilename); ; 26 | 27 | return result; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /libClonezilla/Extractors/ExtractorUsingSevenZipExtractor.cs: -------------------------------------------------------------------------------- 1 | using lib7Zip; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace libClonezilla.Extractors 10 | { 11 | public class ExtractorUsingSevenZipExtractor : IExtractor 12 | { 13 | readonly SevenZipExtractorUsingSevenZipExtractor sevenZipExtractorEx; 14 | 15 | public ExtractorUsingSevenZipExtractor(Stream archiveStream) 16 | { 17 | sevenZipExtractorEx = new SevenZipExtractorUsingSevenZipExtractor(archiveStream); 18 | } 19 | 20 | public ExtractorUsingSevenZipExtractor(string archiveFilename) 21 | { 22 | sevenZipExtractorEx = new SevenZipExtractorUsingSevenZipExtractor(archiveFilename); 23 | } 24 | 25 | public Stream Extract(string pathInArchive) 26 | { 27 | var stream = new MemoryStream(); 28 | sevenZipExtractorEx.ExtractFile(pathInArchive, stream); 29 | stream.Seek(0, SeekOrigin.Begin); 30 | return stream; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libClonezilla/Extractors/ExtractorUsingSevenZipSharp.cs: -------------------------------------------------------------------------------- 1 | using lib7Zip; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace libClonezilla.Extractors 10 | { 11 | public class ExtractorUsingSevenZipSharp : IExtractor 12 | { 13 | readonly SevenZipExtractorUsingSevenZipSharp sevenZipExtractorEx; 14 | 15 | public ExtractorUsingSevenZipSharp(Stream archiveStream) 16 | { 17 | sevenZipExtractorEx = new SevenZipExtractorUsingSevenZipSharp(archiveStream); 18 | } 19 | 20 | public ExtractorUsingSevenZipSharp(string archiveFilename) 21 | { 22 | sevenZipExtractorEx = new SevenZipExtractorUsingSevenZipSharp(archiveFilename); 23 | } 24 | 25 | public Stream Extract(string pathInArchive) 26 | { 27 | var stream = new MemoryStream(); 28 | sevenZipExtractorEx.ExtractFile(pathInArchive, stream); 29 | stream.Seek(0, SeekOrigin.Begin); 30 | return stream; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /libClonezilla/Extractors/IExtractor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libClonezilla.Extractors 9 | { 10 | public interface IExtractor 11 | { 12 | Stream Extract(string path); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libClonezilla/Extractors/MultiExtractor.cs: -------------------------------------------------------------------------------- 1 | using libCommon; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace libClonezilla.Extractors 11 | { 12 | public class MultiExtractor : IExtractor 13 | { 14 | public MultiExtractor(List extractors, bool forceFullRead) 15 | { 16 | Extractors = []; 17 | 18 | extractors 19 | .ForEach(extractor => Extractors.Add(extractor)); 20 | ForceFullRead = forceFullRead; 21 | } 22 | 23 | public BlockingCollection Extractors { get; } 24 | public bool ForceFullRead { get; } 25 | 26 | public Stream Extract(string archiveEntryPath) 27 | { 28 | //find an idle worker 29 | var worker = Extractors.GetConsumingEnumerable().First(); 30 | 31 | //do the work 32 | var result = worker.Extract(archiveEntryPath); 33 | 34 | if (ForceFullRead) 35 | { 36 | //some streams are non-blocking. We've been asked to read the stream in its entirety before calling it done 37 | result.CopyTo(Stream.Null, Buffers.ARBITARY_LARGE_SIZE_BUFFER); 38 | } 39 | 40 | //go to the back of the line 41 | Extractors.Add(worker); 42 | 43 | return result; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /libClonezilla/Extractors/SynchronisedExtractor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libClonezilla.Extractors 9 | { 10 | public class SynchronisedExtractor : IExtractor 11 | { 12 | public SynchronisedExtractor(IExtractor baseExtractor) 13 | { 14 | BaseExtractor = baseExtractor; 15 | } 16 | 17 | public IExtractor BaseExtractor { get; } 18 | 19 | public Stream Extract(string path) 20 | { 21 | Stream stream = BaseExtractor.Extract(path); 22 | 23 | //protect it from concurrent reads 24 | stream = Stream.Synchronized(stream); 25 | 26 | return stream; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /libClonezilla/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "module")] 9 | [assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "", Scope = "module")] 10 | -------------------------------------------------------------------------------- /libClonezilla/PartitionContainers/ImageFiles/CompressedDriveImage.cs: -------------------------------------------------------------------------------- 1 | using lib7Zip; 2 | using libClonezilla.Decompressors; 3 | using libClonezilla.Partitions; 4 | using libCommon; 5 | using libCommon.Streams; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace libClonezilla.PartitionContainers.ImageFiles 14 | { 15 | public class CompressedDriveImage : PartitionContainer 16 | { 17 | public CompressedDriveImage(string containerName, string filename, List partitionsToLoad, Compression compressionInuse, bool processTrailingNulls) 18 | { 19 | ContainerName = containerName; 20 | 21 | var partitionImageFiles = SevenZipUtility.GetArchiveEntries(filename, false, true).ToList(); 22 | 23 | var compressedStream = File.OpenRead(filename); 24 | var decompressorSelector = new DecompressorSelector(filename, ContainerName, compressedStream, null, compressionInuse, null, processTrailingNulls); 25 | 26 | var rawDriveStream = decompressorSelector.GetSeekableStream(); 27 | 28 | var container = new RawDriveImage(containerName, partitionsToLoad, rawDriveStream, partitionImageFiles, processTrailingNulls); 29 | 30 | Partitions = container.Partitions; 31 | } 32 | 33 | public override string ContainerName { get; protected set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /libClonezilla/PartitionContainers/ImageFiles/CompressedImage.cs: -------------------------------------------------------------------------------- 1 | using lib7Zip; 2 | using libClonezilla.Decompressors; 3 | using libCommon; 4 | using libDokan.VFS.Files; 5 | using libDokan.VFS.Folders; 6 | using libPartclone; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace libClonezilla.PartitionContainers.ImageFiles 15 | { 16 | public class CompressedImage : PartitionContainer 17 | { 18 | public CompressedImage(string filename, List partitionsToLoad, bool willPerformRandomSeeking, Folder tempFolder, bool processTrailingNulls) 19 | { 20 | ContainerName = Path.GetFileNameWithoutExtension(filename); 21 | 22 | var currentFilename = filename; 23 | 24 | while (true) 25 | { 26 | Stream streamToInspect = File.OpenRead(currentFilename); 27 | 28 | //protect this stream from concurrent access 29 | streamToInspect = Stream.Synchronized(streamToInspect); 30 | 31 | var isPartcloneStream = PartcloneImageInfo.IsPartclone(streamToInspect); 32 | 33 | var compression = Decompressor.GetCompressionType(streamToInspect); 34 | 35 | if (compression == Compression.None && !isPartcloneStream) 36 | { 37 | //finally dealing with uncompressed content 38 | break; 39 | } 40 | 41 | Stream decompressedStream; 42 | 43 | if (isPartcloneStream) 44 | { 45 | decompressedStream = new PartcloneStream("", "", streamToInspect, null); 46 | } 47 | else 48 | { 49 | var decompressorSelector = new DecompressorSelector(filename, ContainerName, streamToInspect, null, compression, null, processTrailingNulls); 50 | decompressedStream = decompressorSelector.GetSeekableStream(); 51 | } 52 | 53 | var tempName = Path.GetFileNameWithoutExtension(Path.GetFileName(TempUtility.GetTempFilename(false))); 54 | var virtualDecompressedFile = new StreamBackedFileEntry(tempName, tempFolder, decompressedStream); 55 | 56 | currentFilename = virtualDecompressedFile.FullPath; 57 | } 58 | 59 | var container = new RawImage(currentFilename, partitionsToLoad, ContainerName, willPerformRandomSeeking, processTrailingNulls); 60 | 61 | Partitions = container.Partitions; 62 | } 63 | 64 | public override string ContainerName { get; protected set; } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /libClonezilla/PartitionContainers/ImageFiles/CompressedPartitionImage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libClonezilla.PartitionContainers.ImageFiles 9 | { 10 | public class CompressedPartitionImage : PartitionContainer 11 | { 12 | public CompressedPartitionImage(string containerName, Stream rawStream) 13 | { 14 | ContainerName = containerName; 15 | } 16 | 17 | public override string ContainerName { get; protected set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libClonezilla/PartitionContainers/ImageFiles/ImageFile.cs: -------------------------------------------------------------------------------- 1 | using lib7Zip; 2 | using libClonezilla.Decompressors; 3 | using libClonezilla.Partitions; 4 | using libClonezilla.VFS; 5 | using libCommon; 6 | using libCommon.Streams; 7 | using libCommon.Streams.Seekable; 8 | using libDokan.VFS.Files; 9 | using libDokan.VFS.Folders; 10 | using libPartclone; 11 | using MountDocushare.Streams; 12 | using System; 13 | using System.Collections.Generic; 14 | using System.IO; 15 | using System.Linq; 16 | using System.Text; 17 | using System.Threading.Tasks; 18 | 19 | namespace libClonezilla.PartitionContainers.ImageFiles 20 | { 21 | public class ImageFile : PartitionContainer 22 | { 23 | public ImageFile(string filename, List partitionsToLoad, bool willPerformRandomSeeking, IVFS vfs, bool processTrailingNulls) 24 | { 25 | Stream mainFileStream = File.OpenRead(filename); 26 | 27 | //protect this stream from concurrent access 28 | mainFileStream = Stream.Synchronized(mainFileStream); 29 | 30 | ContainerName = Path.GetFileNameWithoutExtension(filename); 31 | 32 | //we have to work out if the image file is compressed or not 33 | 34 | PartitionContainer container; 35 | 36 | var isPartcloneStream = PartcloneImageInfo.IsPartclone(mainFileStream); 37 | 38 | if (isPartcloneStream) 39 | { 40 | container = new PartcloneFile(filename, partitionsToLoad, willPerformRandomSeeking, processTrailingNulls); 41 | } 42 | else 43 | { 44 | var compressionInUse = Decompressor.GetCompressionType(mainFileStream); 45 | 46 | if (compressionInUse == Compression.None) 47 | { 48 | container = new RawImage(filename, partitionsToLoad, ContainerName, willPerformRandomSeeking, processTrailingNulls); 49 | } 50 | else 51 | { 52 | //To inspect compressed images, we need a virtual temp folder. 53 | //Let's get one from the VFS. 54 | var tempFolder = vfs.CreateTempFolder(); 55 | container = new CompressedImage(filename, partitionsToLoad, willPerformRandomSeeking, tempFolder, processTrailingNulls); 56 | } 57 | } 58 | 59 | Partitions = container.Partitions; 60 | } 61 | 62 | public override string ContainerName { get; protected set; } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /libClonezilla/PartitionContainers/ImageFiles/RawImage.cs: -------------------------------------------------------------------------------- 1 | using lib7Zip; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace libClonezilla.PartitionContainers.ImageFiles 10 | { 11 | public class RawImage : PartitionContainer 12 | { 13 | public RawImage(string filename, List partitionsToLoad, string containerName, bool willPerformRandomSeeking, bool processTrailingNulls) 14 | { 15 | Filename = filename; 16 | PartitionsToLoad = partitionsToLoad; 17 | ContainerName = containerName; 18 | 19 | var archiveEntries = SevenZipUtility.GetArchiveEntries( 20 | filename, 21 | false, 22 | true); 23 | 24 | var rawImageStream = File.OpenRead(filename); 25 | SetupFromStream(rawImageStream, archiveEntries, willPerformRandomSeeking, processTrailingNulls); 26 | } 27 | 28 | public RawImage( 29 | string filename, 30 | Stream rawImageStream, 31 | List partitionsToLoad, 32 | string containerName, 33 | IEnumerable archiveEntries, 34 | bool willPerformRandomSeeking, 35 | bool processTrailingNulls) 36 | { 37 | Filename = filename; 38 | PartitionsToLoad = partitionsToLoad; 39 | ContainerName = containerName; 40 | SetupFromStream(rawImageStream, archiveEntries, willPerformRandomSeeking, processTrailingNulls); 41 | } 42 | 43 | public void SetupFromStream(Stream rawImageStream, IEnumerable archiveEntries, bool willPerformRandomSeeking, bool processTrailingNulls) 44 | { 45 | var firstArchiveEntry = archiveEntries.FirstOrDefault(); 46 | 47 | //we have to work out if this is a drive image, or a partition image 48 | 49 | var isDriveImage = firstArchiveEntry != null && !firstArchiveEntry.IsFolder && Path.GetFileNameWithoutExtension(firstArchiveEntry.Path).Equals("0") && firstArchiveEntry.Offset != null; 50 | 51 | PartitionContainer container; 52 | if (isDriveImage) 53 | { 54 | var partitionImageFiles = archiveEntries.ToList(); 55 | 56 | container = new RawDriveImage(ContainerName, PartitionsToLoad, rawImageStream, partitionImageFiles, processTrailingNulls); 57 | } 58 | else 59 | { 60 | container = new RawPartitionImage(Filename, ContainerName, PartitionsToLoad, "partition0", rawImageStream, processTrailingNulls); 61 | } 62 | 63 | Partitions = container.Partitions; 64 | } 65 | 66 | public string Filename { get; } 67 | public List PartitionsToLoad { get; } 68 | public override string ContainerName { get; protected set; } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /libClonezilla/PartitionContainers/ImageFiles/RawPartitionImage.cs: -------------------------------------------------------------------------------- 1 | using libClonezilla.Decompressors; 2 | using libClonezilla.Partitions; 3 | using libCommon.Streams; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace libClonezilla.PartitionContainers.ImageFiles 12 | { 13 | public class RawPartitionImage : PartitionContainer 14 | { 15 | public RawPartitionImage(string originFilename, string containerName, List partitionsToLoad, string partitionName, Stream rawStream, bool processTrailingNulls) 16 | { 17 | ContainerName = containerName; 18 | rawStream.Seek(0, SeekOrigin.Begin); 19 | 20 | var partition = new ImageFilePartition(originFilename, this, partitionName, rawStream, rawStream.Length, Compression.None, null, true, processTrailingNulls); 21 | 22 | Partitions = []; 23 | 24 | if (partitionsToLoad.Count == 0 || partitionsToLoad.Contains(partitionName)) 25 | { 26 | Partitions.Add(partition); 27 | }; 28 | } 29 | 30 | public override string ContainerName { get; protected set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /libClonezilla/PartitionContainers/PartcloneFile.cs: -------------------------------------------------------------------------------- 1 | using libClonezilla.Decompressors; 2 | using libClonezilla.Partitions; 3 | using libPartclone; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace libClonezilla.PartitionContainers 12 | { 13 | public class PartcloneFile : PartitionContainer 14 | { 15 | public string Filename { get; } 16 | 17 | public PartcloneFile(string filename, List partitionsToLoad, bool willPerformRandomSeeking, bool processTrailingNulls) 18 | { 19 | Filename = filename; 20 | 21 | var partitionName = Path.GetFileName(filename); 22 | 23 | var partcloneStream = File.OpenRead(filename); 24 | 25 | var partcloneInfo = new PartcloneImageInfo(ContainerName, partitionName, partcloneStream, null); 26 | var uncompressedLength = partcloneInfo.Length; 27 | 28 | Partitions = []; 29 | 30 | if (partitionsToLoad.Count == 0 || partitionsToLoad.Contains(partitionName)) 31 | { 32 | var partition = new PartclonePartition( 33 | filename, 34 | this, 35 | partitionName, 36 | partcloneStream, 37 | uncompressedLength, 38 | Compression.None, 39 | null, 40 | null, 41 | willPerformRandomSeeking, 42 | processTrailingNulls); 43 | 44 | Partitions.Add(partition); 45 | }; 46 | } 47 | 48 | string? containerName; 49 | public override string ContainerName 50 | { 51 | get 52 | { 53 | containerName ??= Path.GetFileName(Filename) ?? throw new Exception($"Could not get container name from path: {Filename}"); 54 | 55 | return containerName; 56 | } 57 | 58 | protected set 59 | { 60 | containerName = value; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /libClonezilla/PartitionContainers/PartitionContainer.cs: -------------------------------------------------------------------------------- 1 | using libClonezilla.Cache; 2 | using libClonezilla.Partitions; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | using System.Linq; 8 | using lib7Zip; 9 | using libClonezilla.PartitionContainers.ImageFiles; 10 | using libDokan.VFS.Folders; 11 | using libClonezilla.VFS; 12 | 13 | namespace libClonezilla.PartitionContainers 14 | { 15 | public abstract class PartitionContainer 16 | { 17 | public List Partitions { get; protected set; } = []; 18 | 19 | public abstract string ContainerName { get; protected set; } 20 | 21 | public static PartitionContainer FromPath(string path, string cacheFolder, List partitionsToLoad, bool willPerformRandomSeeking, IVFS vfs, bool processTrailingNulls) 22 | { 23 | PartitionContainer? result = null; 24 | 25 | if (Directory.Exists(path)) 26 | { 27 | var clonezillaMagicFile = Path.Combine(path, "clonezilla-img"); 28 | 29 | if (File.Exists(clonezillaMagicFile)) 30 | { 31 | var clonezillaCacheManager = new ClonezillaCacheManager(path, cacheFolder); 32 | result = new ClonezillaImage(path, clonezillaCacheManager, partitionsToLoad, willPerformRandomSeeking, processTrailingNulls); 33 | } 34 | } 35 | else if (File.Exists(path)) 36 | { 37 | result = new ImageFile(path, partitionsToLoad, willPerformRandomSeeking, vfs, processTrailingNulls); 38 | } 39 | else 40 | { 41 | throw new Exception($"File not found: {path}"); 42 | } 43 | 44 | if (result == null) 45 | { 46 | throw new Exception($"Could not determine if this is a Clonezilla folder, or a partclone file: {path}"); 47 | } 48 | 49 | return result; 50 | } 51 | 52 | public static List FromPaths(List paths, string cacheFolder, List partitionsToLoad, bool willPerformRandomSeeking, IVFS vfs, bool processTrailingNulls) 53 | { 54 | var result = paths 55 | .Select(path => FromPath(path, cacheFolder, partitionsToLoad, willPerformRandomSeeking, vfs, processTrailingNulls)) 56 | .ToList(); 57 | 58 | return result; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /libClonezilla/Partitions/ImageFilePartition.cs: -------------------------------------------------------------------------------- 1 | using libClonezilla.Cache; 2 | using libClonezilla.Decompressors; 3 | using libClonezilla.PartitionContainers; 4 | using Serilog; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace libClonezilla.Partitions 13 | { 14 | public class ImageFilePartition : Partition 15 | { 16 | public ImageFilePartition( 17 | string originFilename, 18 | PartitionContainer container, 19 | string partitionName, 20 | Stream stream, 21 | long? uncompressedSize, 22 | Compression compressionInUse, 23 | IPartitionCache? 24 | partitionCache, 25 | bool willPerformRandomSeeking, 26 | bool processTrailingNulls) : base(container, partitionName, partitionCache, stream) 27 | { 28 | var streamName = $"[{container.ContainerName}] [{partitionName}]"; 29 | 30 | Log.Information($"{streamName} Finding optimal decompressor (seekable/sequential)"); 31 | 32 | var decompressorSelector = new DecompressorSelector(originFilename, streamName, stream, uncompressedSize, compressionInUse, partitionCache, processTrailingNulls); 33 | 34 | Stream decompressedStream; 35 | if (willPerformRandomSeeking) 36 | { 37 | decompressedStream = decompressorSelector.GetSeekableStream(); 38 | } 39 | else 40 | { 41 | decompressedStream = decompressorSelector.GetSequentialStream(); 42 | } 43 | 44 | Log.Information($"[{container.ContainerName}] [{partitionName}] Loading partition information"); 45 | FullPartitionImage = decompressedStream; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /libClonezilla/Partitions/MountedContainer.cs: -------------------------------------------------------------------------------- 1 | using libClonezilla.PartitionContainers; 2 | using libClonezilla.VFS; 3 | using libDokan.VFS; 4 | using libDokan.VFS.Files; 5 | using libDokan.VFS.Folders; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using static libClonezilla.Partitions.MountedPartitionImage; 12 | 13 | namespace libClonezilla.Partitions 14 | { 15 | public class MountedContainer 16 | { 17 | public MountedContainer(PartitionContainer container, Folder containerFolder, IVFS vfs, DesiredContent desiredContent) 18 | { 19 | Container = container; 20 | 21 | MountedPartitions = container 22 | .Partitions 23 | .Select(partition => 24 | { 25 | Folder partitionFolder; 26 | 27 | if (container.Partitions.Count == 1 || desiredContent == DesiredContent.ImageFiles) 28 | { 29 | partitionFolder = containerFolder; 30 | } 31 | else 32 | { 33 | partitionFolder = new Folder(partition.PartitionName, containerFolder); 34 | } 35 | 36 | var mountedPartition = new MountedPartitionImage(partition, partitionFolder, vfs, desiredContent); 37 | return mountedPartition; 38 | }) 39 | .ToList(); 40 | } 41 | 42 | public PartitionContainer Container { get; } 43 | 44 | public List MountedPartitions; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /libClonezilla/Partitions/PartclonePartition.cs: -------------------------------------------------------------------------------- 1 | using libClonezilla.Cache; 2 | using libClonezilla.Decompressors; 3 | using libClonezilla.PartitionContainers; 4 | using libCommon; 5 | using libCommon.Streams; 6 | using libCommon.Streams.Seekable; 7 | using libPartclone; 8 | using libPartclone.Cache; 9 | using Serilog; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.IO; 13 | using System.IO.Compression; 14 | using System.Linq; 15 | using System.Text; 16 | using System.Threading.Tasks; 17 | using ZstdNet; 18 | 19 | namespace libClonezilla.Partitions 20 | { 21 | public class PartclonePartition : Partition 22 | { 23 | public PartclonePartition( 24 | string originFilename, 25 | PartitionContainer container, 26 | string partitionName, 27 | Stream compressedPartcloneStream, 28 | long? uncompressedLength, 29 | Compression compressionInUse, 30 | IPartitionCache? partitionCache, 31 | IPartcloneCache? partcloneCache, 32 | bool willPerformRandomSeeking, 33 | bool processTrailingNulls) : base(container, partitionName, partitionCache, compressedPartcloneStream) 34 | { 35 | var streamName = $"[{container.ContainerName}] [{partitionName}]"; 36 | Log.Information($"{streamName} Finding optimal decompressor (seekable/sequential)"); 37 | 38 | var decompressorSelector = new DecompressorSelector(originFilename, streamName, compressedPartcloneStream, uncompressedLength, compressionInUse, partitionCache, processTrailingNulls); 39 | 40 | Stream decompressedStream; 41 | if (willPerformRandomSeeking) 42 | { 43 | decompressedStream = decompressorSelector.GetSeekableStream(); 44 | } 45 | else 46 | { 47 | decompressedStream = decompressorSelector.GetSequentialStream(); 48 | } 49 | 50 | Log.Information($"{streamName} Loading partition information"); 51 | FullPartitionImage = new PartcloneStream(container.ContainerName, partitionName, decompressedStream, partcloneCache); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /libClonezilla/Partitions/Partition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Compression; 4 | using System.Text; 5 | using System.Linq; 6 | using System.IO; 7 | using libCommon.Streams; 8 | using libCommon; 9 | using libClonezilla.Cache; 10 | using libClonezilla.Cache.FileSystem; 11 | using Serilog; 12 | using libCommon.Streams.Sparse; 13 | using ZstdNet; 14 | using libCommon.Streams.Seekable; 15 | using lib7Zip; 16 | using libDokan.VFS.Folders; 17 | using libDokan.VFS.Files; 18 | using libClonezilla.PartitionContainers; 19 | 20 | namespace libClonezilla.Partitions 21 | { 22 | public abstract class Partition(PartitionContainer container, string partitionName, IPartitionCache? partitionCache, Stream? compressedOrigin) 23 | { 24 | public Stream? FullPartitionImage { get; protected set; } 25 | public PartitionContainer Container { get; protected set; } = container; 26 | public string PartitionName { get; protected set; } = partitionName; 27 | public IPartitionCache? PartitionCache { get; protected set; } = partitionCache; 28 | 29 | protected Stream? CompressedOrigin = compressedOrigin; 30 | 31 | public void ExtractToFile(string outputFilename, bool makeSparse) 32 | { 33 | if (FullPartitionImage == null) throw new Exception($"[{Container.ContainerName}] [{PartitionName}] Cannot extract. {nameof(FullPartitionImage)} has not been intialised."); 34 | 35 | ExtractToFile(Container.ContainerName, PartitionName, FullPartitionImage, outputFilename, makeSparse, CompressedOrigin); 36 | } 37 | 38 | public static void ExtractToFile(string containerName, string partitionName, Stream stream, string outputFilename, bool makeSparse, Stream? compressedOrigin) 39 | { 40 | Log.Information($"[{containerName}] [{partitionName}] Extracting partition to: {outputFilename}"); 41 | 42 | using var fileStream = File.Create(outputFilename); 43 | StreamUtility.ExtractToFile($"[{containerName}] [{partitionName}]", compressedOrigin, stream, fileStream, makeSparse); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /libClonezilla/Utility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Linq; 7 | using libClonezilla.PartitionContainers; 8 | using libDokan.VFS.Folders; 9 | using libDokan; 10 | using Serilog; 11 | using System.Threading.Tasks; 12 | using System.Threading; 13 | using DokanNet; 14 | using libClonezilla.Partitions; 15 | using libClonezilla.VFS; 16 | using static libClonezilla.Partitions.MountedPartitionImage; 17 | 18 | namespace libClonezilla 19 | { 20 | public static class Utility 21 | { 22 | public static void LoadAllBinDirectoryAssemblies(string folder) 23 | { 24 | foreach (string dll in Directory.GetFiles(folder, "*.dll", SearchOption.AllDirectories)) 25 | { 26 | try 27 | { 28 | //Assembly loadedAssembly = Assembly.LoadFile(dll); 29 | AppDomain.CurrentDomain.Load(Assembly.LoadFrom(dll).GetName()); 30 | } 31 | catch (FileLoadException) 32 | { } // The Assembly has already been loaded. 33 | catch (BadImageFormatException) 34 | { } // If a BadImageFormatException exception is thrown, the file is not an assembly. 35 | 36 | } 37 | } 38 | 39 | public static List PopulateVFS(IVFS vfs, Folder containersRoot, List containers, DesiredContent desiredContent) 40 | { 41 | var totalPartitions = containers.Sum(container => container.Partitions.Count); 42 | 43 | if (totalPartitions == 0) 44 | { 45 | Log.Error("No partitions to mount. Exiting."); 46 | Environment.Exit(1); 47 | } 48 | 49 | var result = containers 50 | .Select(container => 51 | { 52 | Folder containerFolder; 53 | 54 | if (containers.Count == 1) 55 | { 56 | containerFolder = containersRoot; 57 | } 58 | else 59 | { 60 | containerFolder = new Folder(container.ContainerName, containersRoot); 61 | } 62 | 63 | var mountedContainer = new MountedContainer(container, containerFolder, vfs, desiredContent); 64 | return mountedContainer; 65 | }) 66 | .ToList(); 67 | 68 | return result; 69 | } 70 | 71 | public static void WaitForFolderToExist(string folderPath) 72 | { 73 | Log.Information($"Waiting for {folderPath} to be available."); 74 | while (true) 75 | { 76 | if (Directory.Exists(folderPath)) 77 | { 78 | break; 79 | } 80 | Thread.Sleep(100); 81 | } 82 | } 83 | 84 | public static bool ConsoleConfirm(string title) 85 | { 86 | ConsoleKey response; 87 | do 88 | { 89 | Console.Write($"{title} [y/n] "); 90 | response = Console.ReadKey(false).Key; 91 | if (response != ConsoleKey.Enter) 92 | { 93 | Console.WriteLine(); 94 | } 95 | } while (response != ConsoleKey.Y && response != ConsoleKey.N); 96 | 97 | return (response == ConsoleKey.Y); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /libClonezilla/VFS/IVFS.cs: -------------------------------------------------------------------------------- 1 | using libDokan.VFS.Folders; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libClonezilla.VFS 9 | { 10 | public interface IVFS 11 | { 12 | public Folder CreateTempFolder(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libClonezilla/VFS/SevenZipBackedFileEntry.cs: -------------------------------------------------------------------------------- 1 | using lib7Zip; 2 | using libClonezilla.Extractors; 3 | using libDokan; 4 | using libDokan.VFS; 5 | using libDokan.VFS.Files; 6 | using libDokan.VFS.Folders; 7 | using Serilog; 8 | using SevenZip; 9 | using System; 10 | using System.Collections.Generic; 11 | using System.IO; 12 | using System.Linq; 13 | using System.Runtime.Serialization; 14 | using System.Text; 15 | using System.Text.Json.Serialization; 16 | using System.Threading.Tasks; 17 | 18 | namespace clonezilla_util.VFS 19 | { 20 | public class SevenZipBackedFileEntry : FileEntry 21 | { 22 | //for Json deserializer 23 | public SevenZipBackedFileEntry() : base("", null) 24 | { 25 | PathInArchive = ""; 26 | } 27 | 28 | public SevenZipBackedFileEntry(ArchiveEntry archiveEntry, Folder? parent, IExtractor extractor) : base(Path.GetFileName(archiveEntry.Path), parent) 29 | { 30 | Extractor = extractor; 31 | 32 | Created = archiveEntry.Created; 33 | Accessed = archiveEntry.Accessed; 34 | Modified = archiveEntry.Modified; 35 | Length = archiveEntry.Size; 36 | 37 | PathInArchive = archiveEntry.Path; 38 | } 39 | 40 | 41 | [IgnoreDataMember] 42 | public IExtractor? Extractor { get; set; } 43 | public string PathInArchive { get; set; } 44 | 45 | public override Stream GetStream() 46 | { 47 | if (Extractor == null) throw new Exception($"{nameof(SevenZipBackedFileEntry)}: Extractor not initialized."); 48 | 49 | var stream = Extractor.Extract(PathInArchive); 50 | return stream; 51 | } 52 | 53 | public override string ToString() 54 | { 55 | var result = $"{Name}"; 56 | return result; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /libClonezilla/libClonezilla.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | 7 | 8 | 9 | full 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /libCommon/Buffers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace libCommon 7 | { 8 | public static class Buffers 9 | { 10 | 11 | public const int ARBITARY_SMALL_SIZE_BUFFER = 1 * 1024 * 1024; 12 | public const int ARBITARY_MEDIUM_SIZE_BUFFER = 5 * ARBITARY_SMALL_SIZE_BUFFER; 13 | public static int ARBITARY_LARGE_SIZE_BUFFER 14 | { 15 | get 16 | { 17 | if (Environment.Is64BitProcess) 18 | { 19 | return ARBITARY_MEDIUM_SIZE_BUFFER * 10; 20 | } 21 | else 22 | { 23 | return ARBITARY_MEDIUM_SIZE_BUFFER; 24 | } 25 | } 26 | } 27 | 28 | public static int ARBITARY_HUGE_SIZE_BUFFER 29 | { 30 | get 31 | { 32 | if (Environment.Is64BitProcess) 33 | { 34 | return ARBITARY_LARGE_SIZE_BUFFER * 10; 35 | } 36 | else 37 | { 38 | return ARBITARY_MEDIUM_SIZE_BUFFER; 39 | } 40 | } 41 | } 42 | 43 | //Initialised to something big, because otherwise it defaults to 1MB and smaller. 44 | //See: https://adamsitnik.com/Array-Pool/ 45 | //Always remember to return the array back into the pool. 46 | //Never trust buffer.Length 47 | public static readonly ArrayPool BufferPool = ArrayPool.Create(ARBITARY_LARGE_SIZE_BUFFER + 1, 50); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /libCommon/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Interoperability", "SYSLIB1054:Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time", Justification = "", Scope = "module")] 9 | [assembly: SuppressMessage("Globalization", "CA2101:Specify marshaling for P/Invoke string arguments", Justification = "", Scope = "module")] 10 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "module")] 11 | [assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "", Scope = "module")] 12 | -------------------------------------------------------------------------------- /libCommon/Lists/IRangeComparer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libCommon.Lists 9 | { 10 | public interface IRangeComparer 11 | { 12 | /// 13 | /// Returns 0 if value is in the specified range; 14 | /// less than 0 if value is above the range; 15 | /// greater than 0 if value is below the range. 16 | /// 17 | int Compare(TRange range, TValue value); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libCommon/Lists/LazyList.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using static System.Reflection.Metadata.BlobBuilder; 10 | 11 | namespace libCommon.Lists 12 | { 13 | public class LazyList : IList 14 | { 15 | private readonly IEnumerator _source; 16 | private readonly List _internalList = []; 17 | private bool _isSourceExhausted = false; 18 | 19 | public bool FinishedReading 20 | { 21 | get => _isSourceExhausted; 22 | } 23 | 24 | public LazyList(IEnumerable source) 25 | { 26 | _source = source.GetEnumerator(); 27 | } 28 | 29 | public T this[int index] 30 | { 31 | get 32 | { 33 | EnsureExists(index); 34 | return _internalList[index]; 35 | } 36 | set 37 | { 38 | EnsureExists(index); 39 | _internalList[index] = value; 40 | } 41 | } 42 | 43 | public int Count 44 | { 45 | get 46 | { 47 | EnsureAllLoaded(); 48 | return _internalList.Count; 49 | } 50 | } 51 | 52 | public int CountSoFar => _internalList.Count; 53 | 54 | private bool EnsureExists(int desiredIndex) 55 | { 56 | while (_internalList.Count <= desiredIndex && !_isSourceExhausted) 57 | { 58 | if (_source.MoveNext()) 59 | { 60 | _internalList.Add(_source.Current); 61 | } 62 | else 63 | { 64 | _isSourceExhausted = true; 65 | } 66 | 67 | Log.Debug($"Currently at index {_internalList.Count - 1:N0}, seeking toward desired index {desiredIndex:N0}"); 68 | } 69 | 70 | 71 | if (desiredIndex >= _internalList.Count) 72 | { 73 | return false; 74 | } 75 | 76 | return true; 77 | } 78 | 79 | private void EnsureAllLoaded() 80 | { 81 | while (!_isSourceExhausted) 82 | { 83 | if (_source.MoveNext()) 84 | { 85 | _internalList.Add(_source.Current); 86 | } 87 | else 88 | { 89 | _isSourceExhausted = true; 90 | } 91 | } 92 | } 93 | 94 | // Other IList methods would need to be implemented here. 95 | 96 | public void Add(T item) => throw new NotSupportedException(); 97 | public void Clear() => throw new NotSupportedException(); 98 | public bool Contains(T item) => _internalList.Contains(item); 99 | public void CopyTo(T[] array, int arrayIndex) => _internalList.CopyTo(array, arrayIndex); 100 | public bool Remove(T item) => throw new NotSupportedException(); 101 | public IEnumerator GetEnumerator() 102 | { 103 | int i = 0; 104 | while (EnsureExists(i)) 105 | { 106 | var block = this[i]; 107 | yield return block; 108 | i++; 109 | 110 | Log.Debug($"GetEnumerator(): FinishedReading = {FinishedReading}, i = {i:N0}"); 111 | } 112 | } 113 | 114 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 115 | public int IndexOf(T item) => _internalList.IndexOf(item); 116 | public void Insert(int index, T item) => throw new NotSupportedException(); 117 | public bool IsReadOnly => false; 118 | public void RemoveAt(int index) => throw new NotSupportedException(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /libCommon/Logging/SuppressConsecutiveDuplicateFilter.cs: -------------------------------------------------------------------------------- 1 | using Serilog.Core; 2 | using Serilog.Events; 3 | 4 | namespace libCommon.Logging 5 | { 6 | public class SuppressConsecutiveDuplicateFilter : ILogEventFilter 7 | { 8 | private string? _lastMessage; 9 | 10 | public bool IsEnabled(LogEvent logEvent) 11 | { 12 | var current = logEvent.RenderMessage(); 13 | if (current == _lastMessage) 14 | { 15 | return false; 16 | } 17 | 18 | _lastMessage = current; 19 | return true; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /libCommon/Streams/IReadSuggestor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace libCommon.Streams 6 | { 7 | public interface IReadSuggestor 8 | { 9 | (long Start, long End) GetRecommendation(long start); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /libCommon/Streams/IndependentStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libCommon.Streams 9 | { 10 | //Independantly uses the provided the stream, keeping track of its on Position and uses the underlying stream in a synchronised manner 11 | public class IndependentStream(Stream baseStream, object readLock) : Stream 12 | { 13 | public override bool CanRead => true; 14 | 15 | public override bool CanSeek => true; 16 | 17 | public override bool CanWrite => false; 18 | 19 | public override long Length => BaseStream.Length; 20 | 21 | long positionInThisVirtualStream = 0; 22 | public override long Position 23 | { 24 | get 25 | { 26 | return positionInThisVirtualStream; 27 | } 28 | 29 | set 30 | { 31 | lock (ReadLock) 32 | { 33 | Seek(value, SeekOrigin.Begin); 34 | } 35 | } 36 | } 37 | public Stream BaseStream { get; } = Synchronized(baseStream); 38 | public object ReadLock { get; } = readLock; 39 | 40 | public override void Flush() => BaseStream.Flush(); 41 | 42 | public override int Read(byte[] buffer, int offset, int count) 43 | { 44 | lock (ReadLock) 45 | { 46 | //make sure the base stream is where we expect it to be 47 | BaseStream.Seek(positionInThisVirtualStream, SeekOrigin.Begin); 48 | 49 | var toRead = count; 50 | 51 | var bytesRead = BaseStream.Read(buffer, offset, toRead); 52 | 53 | positionInThisVirtualStream = BaseStream.Position; 54 | 55 | return bytesRead; 56 | } 57 | } 58 | 59 | public override long Seek(long offset, SeekOrigin origin) 60 | { 61 | lock (ReadLock) 62 | { 63 | long newAbsolutePosition = 0; 64 | 65 | switch (origin) 66 | { 67 | case SeekOrigin.Begin: 68 | newAbsolutePosition = offset; 69 | break; 70 | 71 | case SeekOrigin.Current: 72 | newAbsolutePosition = positionInThisVirtualStream + offset; 73 | break; 74 | 75 | case SeekOrigin.End: 76 | newAbsolutePosition = Length + offset; 77 | break; 78 | } 79 | 80 | BaseStream.Seek(newAbsolutePosition, SeekOrigin.Begin); 81 | 82 | positionInThisVirtualStream = BaseStream.Position; 83 | return positionInThisVirtualStream; 84 | } 85 | } 86 | 87 | public override void SetLength(long value) 88 | { 89 | throw new NotImplementedException(); 90 | } 91 | 92 | public override void Write(byte[] buffer, int offset, int count) 93 | { 94 | throw new NotImplementedException(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /libCommon/Streams/Multistream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Linq; 6 | using System.Diagnostics; 7 | 8 | namespace libCommon.Streams 9 | { 10 | public class Multistream : Stream 11 | { 12 | long position = 0; 13 | 14 | public Multistream(IEnumerable substreams) 15 | { 16 | long sizeSum = 0; 17 | Substreams = substreams 18 | .Select((stream, index) => new Substream( 19 | sizeSum, 20 | sizeSum += stream.Length, 21 | stream, 22 | index)) 23 | .ToList(); 24 | } 25 | 26 | public override bool CanRead => true; 27 | 28 | public override bool CanSeek => true; 29 | 30 | public override bool CanWrite => false; 31 | 32 | public override long Length 33 | { 34 | get 35 | { 36 | var result = Substreams.Sum(substream => substream.Length); 37 | return result; 38 | } 39 | } 40 | 41 | public override long Position 42 | { 43 | get => position; 44 | set => Seek(value, SeekOrigin.Begin); 45 | } 46 | 47 | public List Substreams { get; } 48 | 49 | public override void Flush() 50 | { 51 | throw new NotImplementedException(); 52 | } 53 | 54 | 55 | public override int Read(byte[] buffer, int offset, int count) 56 | { 57 | 58 | var substream = Substreams.FirstOrDefault(s => Position >= s.Start && Position < s.End); 59 | 60 | if (substream == null) 61 | { 62 | return 0; 63 | } 64 | 65 | //determine where we should start reading in the substream 66 | var positionInSubstream = Position - substream.Start; 67 | substream.Stream.Seek(positionInSubstream, SeekOrigin.Begin); 68 | 69 | 70 | var bytesRead = substream.Stream.Read(buffer, offset, count); 71 | 72 | position += bytesRead; 73 | 74 | return bytesRead; 75 | } 76 | 77 | public override long Seek(long offset, SeekOrigin origin) 78 | { 79 | switch (origin) 80 | { 81 | case SeekOrigin.Begin: 82 | position = offset; 83 | break; 84 | 85 | case SeekOrigin.Current: 86 | position += offset; 87 | break; 88 | 89 | case SeekOrigin.End: 90 | position = Length - offset; 91 | break; 92 | } 93 | 94 | return position; 95 | } 96 | 97 | public override void SetLength(long value) 98 | { 99 | throw new NotImplementedException(); 100 | } 101 | 102 | public override void Write(byte[] buffer, int offset, int count) 103 | { 104 | throw new NotImplementedException(); 105 | } 106 | } 107 | 108 | public class Substream(long start, long end, Stream stream, int streamIndex = 0) 109 | { 110 | public long Start = start; 111 | public long End = end; 112 | public long Length => End - Start; 113 | public Stream Stream = stream; 114 | public int StreamIndex = streamIndex; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /libCommon/Streams/PositionTrackerStream.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace libCommon.Streams 10 | { 11 | public class PositionTrackerStream : Stream 12 | { 13 | public PositionTrackerStream() 14 | { 15 | 16 | } 17 | 18 | public override bool CanRead => false; 19 | 20 | public override bool CanSeek => true; 21 | 22 | public override bool CanWrite => true; 23 | 24 | long length = 0; 25 | public override long Length => length; 26 | 27 | long position = 0; 28 | readonly object positionLock = new(); 29 | public override long Position { get => position; set => throw new NotImplementedException(); } 30 | 31 | public override void Flush() => throw new NotImplementedException(); 32 | 33 | public override int Read(byte[] buffer, int offset, int count) => throw new NotImplementedException(); 34 | 35 | public override long Seek(long offset, SeekOrigin origin) 36 | { 37 | switch (origin) 38 | { 39 | case SeekOrigin.Begin: 40 | lock (positionLock) 41 | { 42 | position = offset; 43 | } 44 | break; 45 | 46 | case SeekOrigin.Current: 47 | lock (positionLock) 48 | { 49 | position += offset; 50 | } 51 | break; 52 | 53 | case SeekOrigin.End: 54 | lock (positionLock) 55 | { 56 | position = Length - offset; 57 | } 58 | break; 59 | } 60 | 61 | return position; 62 | } 63 | 64 | 65 | public override void SetLength(long value) => length = value; 66 | 67 | public override void Write(byte[] buffer, int offset, int count) 68 | { 69 | ArgumentNullException.ThrowIfNull(buffer); 70 | if (offset < 0 || offset > buffer.Length) throw new ArgumentOutOfRangeException(nameof(offset), "Offset is out of range."); 71 | if (count < 0 || (offset + count) > buffer.Length) throw new ArgumentOutOfRangeException(nameof(count), "Count is out of range."); 72 | 73 | lock (positionLock) 74 | { 75 | position += count; 76 | if (position > Length) 77 | { 78 | length = position; 79 | } 80 | } 81 | } 82 | 83 | public override string ToString() 84 | { 85 | var result = $"Position: {Position:N0}"; 86 | return result; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /libCommon/Streams/Sparse/ISparseAwareReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libCommon.Streams 9 | { 10 | public interface ISparseAwareReader 11 | { 12 | public Stream Stream { get; } 13 | public bool LatestReadWasAllNull { get; set; } 14 | public bool StopReadingWhenRemainderOfFileIsNull { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libCommon/Streams/Sparse/ISparseAwareWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libCommon.Streams.Sparse 9 | { 10 | public interface ISparseAwareWriter 11 | { 12 | public Stream Stream { get; } 13 | public bool ExplicitlyWriteNullBytes { get; set; } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libCommon/Streams/Sparse/SparesAwareReadStream.cs: -------------------------------------------------------------------------------- 1 | using libCommon.Streams; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace libCommon.Streams.Sparse 10 | { 11 | public class SparseAwareReader(Stream stream) : Stream, ISparseAwareReader 12 | { 13 | public Stream Stream { get; } = stream; 14 | public bool LatestReadWasAllNull { get; set; } = false; 15 | public bool StopReadingWhenRemainderOfFileIsNull { get; set; } = false; 16 | 17 | public override bool CanRead => Stream.CanRead; 18 | 19 | public override bool CanSeek => Stream.CanSeek; 20 | 21 | public override bool CanWrite => Stream.CanWrite; 22 | 23 | public override long Length => Stream.Length; 24 | 25 | public override long Position { get => Stream.Position; set => Stream.Position = value; } 26 | 27 | public static bool IsAllZerosLINQParallel(byte[] data, int offset, int count) 28 | { 29 | IEnumerable dat = data; 30 | if (offset > 0) 31 | { 32 | dat = dat.Skip(offset); 33 | } 34 | 35 | if (count != data.Length) 36 | { 37 | dat = dat.Take(count); 38 | } 39 | 40 | var result = dat 41 | .AsParallel() 42 | .WithDegreeOfParallelism(Environment.ProcessorCount) 43 | .All(b => b == 0x0); 44 | 45 | return result; 46 | } 47 | 48 | //This is up to 3x faster than IsAllZerosLINQ. 49 | /* 50 | public static unsafe bool IsAllZerosUnsafe(byte[] data, int offset, int count) 51 | { 52 | fixed (byte* p = data) 53 | { 54 | byte* start = p + offset; 55 | byte* end = start + count; 56 | for (byte* current = start; current < end; current++) 57 | { 58 | if (*current != 0) return false; 59 | } 60 | } 61 | return true; 62 | } 63 | */ 64 | 65 | public override int Read(byte[] buffer, int offset, int count) 66 | { 67 | var bytesRead = Stream.Read(buffer, offset, count); 68 | 69 | LatestReadWasAllNull = IsAllZerosLINQParallel(buffer, offset, count); 70 | 71 | return bytesRead; 72 | } 73 | 74 | public override void Flush() 75 | { 76 | Stream.Flush(); 77 | } 78 | 79 | public override long Seek(long offset, SeekOrigin origin) 80 | { 81 | var result = Stream.Seek(offset, origin); 82 | return result; 83 | } 84 | 85 | public override void SetLength(long value) 86 | { 87 | Stream.SetLength(value); 88 | } 89 | 90 | public override void Write(byte[] buffer, int offset, int count) 91 | { 92 | Stream.Write(buffer, offset, count); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /libCommon/Streams/Sparse/SparseAwareWriteStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libCommon.Streams.Sparse 9 | { 10 | public class SparseAwareWriteStream(Stream stream, bool explicitlyWriteNullBytes) : ISparseAwareWriter 11 | { 12 | public Stream Stream { get; } = stream; 13 | public bool ExplicitlyWriteNullBytes { get; set; } = explicitlyWriteNullBytes; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /libCommon/Streams/SubStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace libCommon.Streams 7 | { 8 | public class SubStream : Stream 9 | { 10 | public SubStream(IndependentStream baseStream, long startByte, long endByte) 11 | { 12 | BaseStream = baseStream; 13 | StartByte = startByte; 14 | EndByte = endByte; 15 | 16 | Seek(0, SeekOrigin.Begin); 17 | } 18 | public override bool CanRead => true; 19 | 20 | public override bool CanSeek => true; 21 | 22 | public override bool CanWrite => false; 23 | 24 | public override long Length 25 | { 26 | get 27 | { 28 | var result = EndByte - StartByte; 29 | return result; 30 | } 31 | } 32 | 33 | public override long Position 34 | { 35 | get => BaseStream.Position - StartByte; 36 | set => Seek(value, SeekOrigin.Begin); 37 | } 38 | public Stream BaseStream { get; } 39 | public long StartByte { get; } 40 | public long EndByte { get; } 41 | 42 | public override void Flush() 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | 47 | public override int Read(byte[] buffer, int offset, int count) 48 | { 49 | //if (Position == Length) return 0; 50 | 51 | var bytesLeftInVirtualFile = Length - Position; 52 | //var bytesLeftInBaseStream = BaseStream.Length - BaseStream.Position; 53 | 54 | if (BaseStream.Length != 0 && Position >= BaseStream.Length) 55 | { 56 | //we are beyond the original stream. Just return blanks 57 | var toClear = (int)Math.Min(bytesLeftInVirtualFile, count); 58 | Array.Clear(buffer, offset, toClear); 59 | return toClear; 60 | } 61 | 62 | var toRead = count; 63 | toRead = (int)Math.Min(toRead, bytesLeftInVirtualFile); 64 | 65 | var result = BaseStream.Read(buffer, offset, toRead); 66 | return result; 67 | } 68 | 69 | public override long Seek(long offset, SeekOrigin origin) 70 | { 71 | switch (origin) 72 | { 73 | case SeekOrigin.Begin: 74 | BaseStream.Seek(StartByte + offset, origin); 75 | break; 76 | 77 | case SeekOrigin.Current: 78 | BaseStream.Seek(offset, origin); 79 | break; 80 | 81 | case SeekOrigin.End: 82 | BaseStream.Seek(EndByte + offset, origin); 83 | break; 84 | } 85 | 86 | return Position; 87 | } 88 | 89 | public override void SetLength(long value) 90 | { 91 | throw new NotImplementedException(); 92 | } 93 | 94 | public override void Write(byte[] buffer, int offset, int count) 95 | { 96 | throw new NotImplementedException(); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /libCommon/TempUtility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libCommon 9 | { 10 | public static class TempUtility 11 | { 12 | static string tempRoot = Path.GetTempPath(); 13 | public static string TempRoot 14 | { 15 | get 16 | { 17 | lock (tempRoot) 18 | { 19 | if (!Directory.Exists(tempRoot)) 20 | { 21 | Directory.CreateDirectory(tempRoot); 22 | } 23 | } 24 | return tempRoot; 25 | } 26 | 27 | set 28 | { 29 | tempRoot = value; 30 | } 31 | } 32 | 33 | static readonly List Folders = []; 34 | static readonly List Files = []; 35 | 36 | public static string GetTemporaryDirectory() 37 | { 38 | string tempDirectory = Path.Combine(TempRoot, Path.GetFileNameWithoutExtension(Path.GetRandomFileName())); 39 | Directory.CreateDirectory(tempDirectory); 40 | 41 | lock (Folders) 42 | { 43 | Folders.Add(tempDirectory); 44 | } 45 | 46 | return tempDirectory; 47 | } 48 | 49 | public static string GetTempFilename(bool createEmptyFile) 50 | { 51 | var tempFilename = Path.Combine(TempRoot, Path.GetRandomFileName()); 52 | 53 | if (createEmptyFile) 54 | { 55 | File.Create(tempFilename).Close(); 56 | File.SetAttributes(tempFilename, FileAttributes.Temporary); 57 | } 58 | 59 | lock (Files) 60 | { 61 | Files.Add(tempFilename); 62 | } 63 | 64 | return tempFilename; 65 | } 66 | 67 | public static void Cleanup() 68 | { 69 | lock (Folders) 70 | { 71 | Folders 72 | .ForEach(folder => 73 | { 74 | try 75 | { 76 | Directory.Delete(folder, true); 77 | } 78 | catch { } 79 | }); 80 | } 81 | 82 | lock (Files) 83 | { 84 | Files 85 | .ForEach(filename => 86 | { 87 | try 88 | { 89 | File.Delete(filename); 90 | } 91 | catch { } 92 | }); 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /libCommon/Utility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Management; 4 | using System.Text; 5 | using System.Linq; 6 | using System.IO; 7 | using System.Security.Cryptography; 8 | 9 | namespace libCommon 10 | { 11 | public static class Utility 12 | { 13 | public static long GetTotalRamSizeBytes() 14 | { 15 | var gcMemoryInfo = GC.GetGCMemoryInfo(); 16 | return gcMemoryInfo.TotalAvailableMemoryBytes; 17 | } 18 | 19 | public static bool IsOnNTFS(string filename) 20 | { 21 | //Get all the drives on the local machine. 22 | var allDrives = DriveInfo.GetDrives(); 23 | 24 | //Get the path root. 25 | var pathRoot = Path.GetPathRoot(filename); 26 | //Find the drive based on the path root. 27 | var driveBasedOnPath = allDrives.FirstOrDefault(d => d.RootDirectory.Name.Equals(pathRoot)); 28 | //Determine if NTFS 29 | var isNTFS = driveBasedOnPath != null && driveBasedOnPath.DriveFormat == "NTFS"; 30 | 31 | return isNTFS; 32 | } 33 | 34 | public static string Absolutify(string relativePath) 35 | { 36 | var result = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, relativePath); 37 | return result; 38 | } 39 | 40 | public static string CalculateMD5(string filename) 41 | { 42 | using var md5 = MD5.Create(); 43 | using var stream = File.OpenRead(filename); 44 | var hash = md5.ComputeHash(stream); 45 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); 46 | } 47 | 48 | public static string CalculateMD5(Stream stream) 49 | { 50 | stream.Seek(0, SeekOrigin.Begin); 51 | using var md5 = MD5.Create(); 52 | var hash = md5.ComputeHash(stream); 53 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); 54 | } 55 | 56 | public static string CalculateMD5(byte[] bytes) 57 | { 58 | var hash = MD5.HashData(bytes); 59 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /libCommon/libCommon.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | False 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /libDecompression/Lists/MappingComparer.cs: -------------------------------------------------------------------------------- 1 | using libCommon.Lists; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libDecompression.Lists 9 | { 10 | public class MappingComparer : IRangeComparer 11 | { 12 | public int Compare(Mapping range, long value) 13 | { 14 | if (value < range.UncompressedStartByte) return 1; 15 | if (value > range.UncompressedEndByte) return -1; 16 | return 0; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libDecompression/Utilities/BoyerMoore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace libDecompression.Utilities 8 | { 9 | public static class BoyerMoore 10 | { 11 | public static int IndexOf(Span haystack, byte[] needle) 12 | { 13 | if (needle.Length == 0) 14 | { 15 | return 0; 16 | } 17 | 18 | int[] charTable = MakeCharTable(needle); 19 | int[] offsetTable = MakeOffsetTable(needle); 20 | for (int i = needle.Length - 1; i < haystack.Length;) 21 | { 22 | int j; 23 | for (j = needle.Length - 1; needle[j] == haystack[i]; --i, --j) 24 | { 25 | if (j == 0) 26 | { 27 | return i; 28 | } 29 | } 30 | 31 | i += Math.Max(offsetTable[needle.Length - 1 - j], charTable[haystack[i]]); 32 | } 33 | 34 | return -1; 35 | } 36 | 37 | private static int[] MakeCharTable(byte[] needle) 38 | { 39 | const int ALPHABET_SIZE = 256; 40 | int[] table = new int[ALPHABET_SIZE]; 41 | for (int i = 0; i < table.Length; ++i) 42 | { 43 | table[i] = needle.Length; 44 | } 45 | 46 | for (int i = 0; i < needle.Length - 1; ++i) 47 | { 48 | table[needle[i]] = needle.Length - 1 - i; 49 | } 50 | 51 | return table; 52 | } 53 | 54 | private static int[] MakeOffsetTable(byte[] needle) 55 | { 56 | int[] table = new int[needle.Length]; 57 | int lastPrefixPosition = needle.Length; 58 | for (int i = needle.Length - 1; i >= 0; --i) 59 | { 60 | if (IsPrefix(needle, i + 1)) 61 | { 62 | lastPrefixPosition = i + 1; 63 | } 64 | 65 | table[needle.Length - 1 - i] = lastPrefixPosition - i + needle.Length - 1; 66 | } 67 | 68 | for (int i = 0; i < needle.Length - 1; ++i) 69 | { 70 | int slen = SuffixLength(needle, i); 71 | table[slen] = needle.Length - 1 - i + slen; 72 | } 73 | 74 | return table; 75 | } 76 | 77 | private static bool IsPrefix(byte[] needle, int p) 78 | { 79 | for (int i = p, j = 0; i < needle.Length; ++i, ++j) 80 | { 81 | if (needle[i] != needle[j]) 82 | { 83 | return false; 84 | } 85 | } 86 | 87 | return true; 88 | } 89 | 90 | private static int SuffixLength(byte[] needle, int p) 91 | { 92 | int len = 0; 93 | for (int i = p, j = needle.Length - 1; i >= 0 && needle[i] == needle[j]; --i, --j) 94 | { 95 | len += 1; 96 | } 97 | 98 | return len; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /libDecompression/libDecompression.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /libDokan/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace libDokan 8 | { 9 | public static class Extensions 10 | { 11 | public static IEnumerable Recurse(this IEnumerable source, Func> childSelector, bool depthFirst = false) 12 | { 13 | List queue = new(source); 14 | 15 | while (queue.Count > 0) 16 | { 17 | var item = queue[0]; 18 | queue.RemoveAt(0); 19 | 20 | var children = childSelector(item); 21 | 22 | if (depthFirst) 23 | { 24 | queue.InsertRange(0, children); 25 | } 26 | else 27 | { 28 | queue.AddRange(children); 29 | } 30 | 31 | yield return item; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /libDokan/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "module")] 9 | [assembly: SuppressMessage("Performance", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "", Scope = "module")] 10 | [assembly: SuppressMessage("Style", "IDE0130:Namespace does not match folder structure", Justification = "", Scope = "module")] 11 | -------------------------------------------------------------------------------- /libDokan/Processes/ProcInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libDokan.Processes 9 | { 10 | public class ProcInfo 11 | { 12 | public ProcInfo(int PID) 13 | { 14 | this.PID = PID; 15 | 16 | Proc = new Lazy(() => 17 | { 18 | var process = Process.GetProcessById(PID); 19 | return process; 20 | }); 21 | } 22 | 23 | Lazy Proc { get; } 24 | 25 | public int PID { get; } 26 | 27 | public string Name => Proc.Value.ProcessName; 28 | 29 | public string FullExePath 30 | { 31 | get 32 | { 33 | string result; 34 | 35 | try 36 | { 37 | result = Proc.Value.MainModule?.FileName ?? ""; 38 | } 39 | catch 40 | { 41 | result = "Not accessible"; 42 | } 43 | 44 | return result; 45 | } 46 | } 47 | 48 | public string ExeFilename => Path.GetFileName(FullExePath); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /libDokan/Utility.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace libDokan 8 | { 9 | public static class Utility 10 | { 11 | 12 | public static string GetAvailableDriveLetter(bool alphabetical = false) 13 | { 14 | var existingLetters = Directory.GetLogicalDrives(); 15 | 16 | var candidates = Enumerable.Range('A', 26); 17 | if (!alphabetical) candidates = candidates.Reverse(); 18 | 19 | var availableLetter = candidates 20 | .Select(i => $"{(char)i}") 21 | .FirstOrDefault(candidate => !existingLetters.Any(existing => candidate.Equals(existing, StringComparison.CurrentCultureIgnoreCase))) ?? throw new Exception($"Could not find an available drive letter."); 22 | 23 | availableLetter += @":\"; 24 | 25 | return availableLetter; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /libDokan/VFS/FileSystemEntry.cs: -------------------------------------------------------------------------------- 1 | using DokanNet; 2 | using libCommon; 3 | using libDokan.Processes; 4 | using libDokan.VFS.Folders; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Runtime.Serialization; 9 | using System.Text; 10 | using System.Text.Json.Serialization; 11 | using System.Threading.Tasks; 12 | 13 | namespace libDokan.VFS 14 | { 15 | public abstract class FileSystemEntry 16 | { 17 | public string Name { get; set; } 18 | 19 | Folder? parent; 20 | public Folder? Parent 21 | { 22 | get 23 | { 24 | return parent; 25 | } 26 | set 27 | { 28 | parent = value; 29 | parent?.AddChild(this); 30 | } 31 | } 32 | public bool Hidden { get; set; } = false; 33 | public bool System { get; set; } = false; 34 | 35 | public FileSystemEntry(string name, Folder? parent) 36 | { 37 | Name = name; 38 | Parent = parent; 39 | } 40 | 41 | public DateTime Modified; 42 | public DateTime Created; 43 | public DateTime Accessed; 44 | 45 | protected abstract FileInformation ToFileInfo(); 46 | 47 | public List Ancestors 48 | { 49 | get 50 | { 51 | var ancestors = this 52 | .Recurse(ancestor => ancestor.Parent) 53 | .Reverse() 54 | .ToList(); 55 | 56 | return ancestors; 57 | } 58 | } 59 | 60 | public bool IsAccessibleToProcess(int requestPID) 61 | { 62 | //check if any of the ancestors are restricted 63 | 64 | var restrictedAncestors = Ancestors 65 | .OfType() 66 | .ToList(); 67 | 68 | var procInfo = new ProcInfo(requestPID); 69 | 70 | bool isRestricted = restrictedAncestors 71 | .Any(ancestor => !ancestor.IsProcessPermitted(procInfo)); 72 | 73 | return isRestricted; 74 | } 75 | 76 | public string FullPath 77 | { 78 | get 79 | { 80 | var folderPath = Ancestors 81 | .Where(a => a is not RootFolder) 82 | .Select(a => a.Name) 83 | .ToString("\\"); 84 | 85 | var root = Ancestors 86 | .OfType() 87 | .FirstOrDefault()?.MountPoint ?? ""; 88 | 89 | var fullPath = Path.Combine(root, folderPath); 90 | 91 | return fullPath; 92 | } 93 | } 94 | 95 | public FileInformation ToFileInformation() 96 | { 97 | var result = ToFileInfo(); 98 | 99 | if (Hidden) result.Attributes |= FileAttributes.Hidden; 100 | if (System) result.Attributes |= FileAttributes.System; 101 | 102 | result.FileName = Name; 103 | result.CreationTime = Created; 104 | result.LastAccessTime = Accessed; 105 | result.LastWriteTime = Modified; 106 | 107 | return result; 108 | } 109 | 110 | public override string ToString() 111 | { 112 | var result = Name; 113 | return result; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /libDokan/VFS/Files/FileEntry.cs: -------------------------------------------------------------------------------- 1 | using DokanNet; 2 | using libDokan.VFS.Folders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace libDokan.VFS.Files 10 | { 11 | public abstract class FileEntry : FileSystemEntry 12 | { 13 | public long Length { get; set; } 14 | 15 | public abstract Stream GetStream(); 16 | 17 | public FileEntry(string name, Folder? parent) : base(name, parent) 18 | { 19 | } 20 | 21 | protected override FileInformation ToFileInfo() 22 | { 23 | var result = new FileInformation 24 | { 25 | Length = Length, 26 | }; 27 | 28 | return result; 29 | } 30 | 31 | public override string ToString() 32 | { 33 | var result = $"{Name}"; 34 | return result; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /libDokan/VFS/Files/StreamBackedFileEntry.cs: -------------------------------------------------------------------------------- 1 | using libDokan.VFS.Folders; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libDokan.VFS.Files 9 | { 10 | public class StreamBackedFileEntry : FileEntry 11 | { 12 | readonly Stream? Stream; 13 | readonly Func? StreamFactory; 14 | 15 | public StreamBackedFileEntry(string name, Folder? folder, Stream stream) : base(name, folder) 16 | { 17 | Stream = stream; 18 | Length = stream.Length; 19 | } 20 | 21 | public StreamBackedFileEntry(string name, Folder? folder, Func streamFactory, long fileSize) : base(name, folder) 22 | { 23 | StreamFactory = streamFactory; 24 | Length = fileSize; 25 | } 26 | 27 | public override Stream GetStream() 28 | { 29 | Stream? result = null; 30 | 31 | if (Stream != null) 32 | { 33 | result = Stream; 34 | } else if (StreamFactory != null) 35 | { 36 | result = StreamFactory(); 37 | } 38 | 39 | if (result == null) throw new Exception($"Could not retrieve a stream for {Name}"); 40 | 41 | return result; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libDokan/VFS/Folders/Folder.cs: -------------------------------------------------------------------------------- 1 | using DokanNet; 2 | using libDokan.VFS.Files; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO.Enumeration; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace libDokan.VFS.Folders 11 | { 12 | public class Folder : FileSystemEntry 13 | { 14 | readonly List children = []; 15 | public IEnumerable Children => children; 16 | 17 | public void AddChild(FileSystemEntry entry) 18 | { 19 | if (!children.Contains(entry)) 20 | { 21 | children.Add(entry); 22 | entry.Parent = this; 23 | } 24 | } 25 | 26 | public void AddChildren(IEnumerable entries) 27 | { 28 | entries 29 | .ToList() 30 | .ForEach(entry => AddChild(entry)); 31 | } 32 | 33 | public Folder(string name, Folder? parent) : base(name, parent) 34 | { 35 | } 36 | 37 | protected override FileInformation ToFileInfo() 38 | { 39 | var result = new FileInformation(); 40 | 41 | result.Attributes |= FileAttributes.Directory; 42 | 43 | return result; 44 | } 45 | 46 | public override string ToString() 47 | { 48 | var result = $"{Name}"; 49 | return result; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /libDokan/VFS/Folders/RestrictedFolderByPID.cs: -------------------------------------------------------------------------------- 1 | using libDokan.Processes; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libDokan.VFS.Folders 9 | { 10 | public class RestrictedFolderByPID : Folder 11 | { 12 | public RestrictedFolderByPID(string name, Folder? parent, Func isProcessPermitted) : base(name, parent) 13 | { 14 | IsProcessPermitted = isProcessPermitted; 15 | } 16 | 17 | public Func IsProcessPermitted { get; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libDokan/VFS/Folders/UnlistedFolder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace libDokan.VFS.Folders 8 | { 9 | public class UnlistedFolder : Folder 10 | { 11 | public UnlistedFolder(string name, Folder? parent) : base(name, parent) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /libDokan/libDokan.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /libGZip/ext/gztool/win-x86_64/gztool-Windows.x86_64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fiddyschmitt/clonezilla-util/bf65c0522e3035b4b201d065200c211ae734e563/libGZip/ext/gztool/win-x86_64/gztool-Windows.x86_64.exe -------------------------------------------------------------------------------- /libGZip/libGZip.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | 7 | 8 | 9 | full 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | PreserveNewest 32 | 33 | 34 | Always 35 | 36 | 37 | Never 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /libPartclone/Cache/IPartcloneCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace libPartclone.Cache 6 | { 7 | public interface IPartcloneCache 8 | { 9 | public List? GetPartcloneContentMapping(); 10 | public void SetPartcloneContentMapping(List contiguousRanges); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /libPartclone/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Runtime.InteropServices; 7 | using System.Security.Cryptography; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace libPartclone 12 | { 13 | public static class Extensions 14 | { 15 | public static byte[] ToByteArray(this BitArray bits) 16 | { 17 | byte[] ret = new byte[(bits.Length - 1) / 8 + 1]; 18 | bits.CopyTo(ret, 0); 19 | return ret; 20 | } 21 | 22 | public static string ToHexString(this IEnumerable ba) 23 | { 24 | return BitConverter.ToString(ba.ToArray()).Replace("-", ""); 25 | } 26 | 27 | public static string ToString(this IEnumerable list, string linePrefix, string separator) 28 | { 29 | string result = string.Join(separator, list.Select(line => $"{linePrefix}{line}")); 30 | return result; 31 | } 32 | 33 | public static string[] ToLines(this string str) 34 | { 35 | var result = new List(); 36 | 37 | using (var sr = new StringReader(str)) 38 | { 39 | string? line; 40 | while ((line = sr.ReadLine()) != null) 41 | { 42 | result.Add(line); 43 | } 44 | } 45 | 46 | return [.. result]; 47 | } 48 | 49 | public static List> ChunkBy(this List source, int chunkSize) 50 | { 51 | return source 52 | .Select((x, i) => new { Index = i, Value = x }) 53 | .GroupBy(x => x.Index / chunkSize) 54 | .Select(x => x.Select(v => v.Value).ToList()) 55 | .ToList(); 56 | } 57 | 58 | public static IEnumerable GetBits(this byte b) 59 | { 60 | for (int i = 0; i < 8; i++) 61 | { 62 | yield return (b & 0x80) != 0; 63 | b *= 2; 64 | } 65 | } 66 | 67 | public static IEnumerable> GroupAdjacentBy( 68 | this IEnumerable source, Func predicate) 69 | { 70 | using var e = source.GetEnumerator(); 71 | if (e.MoveNext()) 72 | { 73 | var list = new List { e.Current }; 74 | var pred = e.Current; 75 | while (e.MoveNext()) 76 | { 77 | if (predicate(pred, e.Current)) 78 | { 79 | list.Add(e.Current); 80 | } 81 | else 82 | { 83 | yield return list; 84 | list = [e.Current]; 85 | } 86 | pred = e.Current; 87 | } 88 | yield return list; 89 | } 90 | } 91 | 92 | public static IEnumerable Cached(this IEnumerable enumerable) 93 | { 94 | return CachedImpl(enumerable.GetEnumerator(), []); 95 | } 96 | 97 | static IEnumerable CachedImpl(IEnumerator source, List buffer) 98 | { 99 | int pos = 0; 100 | while (true) 101 | { 102 | if (pos == buffer.Count) 103 | if (source.MoveNext()) 104 | buffer.Add(source.Current); 105 | else 106 | yield break; 107 | yield return buffer[pos++]; 108 | } 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /libPartclone/Lists/ContiguousRangeComparer.cs: -------------------------------------------------------------------------------- 1 | using libCommon.Lists; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libPartclone.Lists 9 | { 10 | public class ContiguousRangeComparer : IRangeComparer 11 | { 12 | public int Compare(ContiguousRange range, long value) 13 | { 14 | if (value < range.OutputFileRange.StartByte) return 1; 15 | if (value > range.OutputFileRange.EndByte) return -1; 16 | return 0; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libPartclone/Metadata/FileSystemInfoV1.cs: -------------------------------------------------------------------------------- 1 | using libCommon; 2 | using System.IO; 3 | 4 | namespace libPartclone.Metadata 5 | { 6 | public class FileSystemInfoV1 7 | { 8 | public uint BlockSize; 9 | public ulong DeviceSizeBytes; 10 | public ulong TotalBlocks; 11 | public ulong UsedBlocks; 12 | 13 | public FileSystemInfoV1() 14 | { 15 | 16 | } 17 | 18 | public FileSystemInfoV1(BinaryReader binaryReader) 19 | { 20 | BlockSize = binaryReader.ReadUInt32(); 21 | DeviceSizeBytes = binaryReader.ReadUInt64(); 22 | TotalBlocks = binaryReader.ReadUInt64(); 23 | UsedBlocks = binaryReader.ReadUInt64(); 24 | } 25 | 26 | public override string ToString() 27 | { 28 | var result = $@" 29 | BlockSize: {BlockSize} 30 | DeviceSize: {DeviceSizeBytes:N0} ({DeviceSizeBytes.BytesToString()}) 31 | TotalBlocks: {TotalBlocks:N0} 32 | UsedBlocks: {UsedBlocks:N0} 33 | ".Trim(); 34 | 35 | return result; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /libPartclone/Metadata/FileSystemInfoV2.cs: -------------------------------------------------------------------------------- 1 | using libCommon; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace libPartclone.Metadata 10 | { 11 | public class FileSystemInfoV2 12 | { 13 | public string? FileSystemType; 14 | 15 | /// Size of the source device, in bytes 16 | public ulong DeviceSizeBytes; 17 | 18 | /// Number of blocks in the file system 19 | public ulong TotalBlocks; 20 | 21 | /// Number of blocks in use as reported by the file system 22 | public ulong UsedBlocks; 23 | 24 | /// Number of blocks in use within the bitmap 25 | public ulong UsedBitmapBlocks; 26 | 27 | /// Number of bytes in each block 28 | public uint BlockSize; 29 | 30 | public FileSystemInfoV2() 31 | { 32 | 33 | } 34 | 35 | public FileSystemInfoV2(BinaryReader binaryReader) 36 | { 37 | FileSystemType = Encoding.ASCII.GetString(binaryReader.ReadBytes(16)).TrimEnd('\0'); 38 | 39 | DeviceSizeBytes = binaryReader.ReadUInt64(); 40 | TotalBlocks = binaryReader.ReadUInt64(); 41 | UsedBlocks = binaryReader.ReadUInt64(); 42 | UsedBitmapBlocks = binaryReader.ReadUInt64(); 43 | 44 | BlockSize = binaryReader.ReadUInt32(); 45 | } 46 | 47 | public override string ToString() 48 | { 49 | var result = $@" 50 | FileSystemType: {FileSystemType} 51 | DeviceSize: {DeviceSizeBytes:N0} ({DeviceSizeBytes.BytesToString()}) 52 | TotalBlocks: {TotalBlocks:N0} 53 | UsedBlocks: {UsedBlocks:N0} 54 | UsedBitmap: {UsedBitmapBlocks:N0} 55 | BlockSize: {BlockSize:N0} ({BlockSize.BytesToString()}) 56 | ".Trim(); 57 | 58 | return result; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /libPartclone/Metadata/ImageDescV1.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace libPartclone.Metadata 7 | { 8 | public class ImageDescV1 9 | { 10 | public ImageHeadV1? ImageHeadV1; 11 | public FileSystemInfoV1? FileSystemInfoV1; 12 | public ImageOptionsV1? ImageOptionsV1; 13 | 14 | public ImageDescV1() 15 | { 16 | 17 | } 18 | 19 | public ImageDescV1(BinaryReader binaryReader) 20 | { 21 | ImageHeadV1 = new ImageHeadV1(binaryReader); 22 | FileSystemInfoV1 = new FileSystemInfoV1(binaryReader); 23 | ImageOptionsV1 = new ImageOptionsV1(binaryReader); 24 | } 25 | 26 | public override string ToString() 27 | { 28 | var imageHeadLines = ImageHeadV1?.ToString().ToLines(); 29 | var fileSystemInfoLines = FileSystemInfoV1?.ToString().ToLines(); 30 | var imageOptionsLines = ImageOptionsV1?.ToString().ToLines(); 31 | 32 | var result = $@" 33 | ImageDescV1 34 | 35 | ImageHeadV1 36 | {imageHeadLines?.ToString(" ", Environment.NewLine)} 37 | 38 | FileSystemInfoV1 39 | {fileSystemInfoLines?.ToString(" ", Environment.NewLine)} 40 | 41 | ImageOptionsV1 42 | {imageOptionsLines?.ToString(" ", Environment.NewLine)} 43 | 44 | ".Trim(); 45 | 46 | return result; 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /libPartclone/Metadata/ImageDescV2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libPartclone.Metadata 9 | { 10 | public class ImageDescV2 11 | { 12 | public ImageHeadV2? ImageHeadV2; 13 | public FileSystemInfoV2? FileSystemInfoV2; 14 | public ImageOptionsV2? ImageOptionsV2; 15 | public uint CRC; 16 | 17 | public ImageDescV2() 18 | { 19 | 20 | } 21 | 22 | public ImageDescV2(BinaryReader binaryReader) 23 | { 24 | ImageHeadV2 = new ImageHeadV2(binaryReader); //offset: 0x00 25 | FileSystemInfoV2 = new FileSystemInfoV2(binaryReader); //offset: 0x24 26 | ImageOptionsV2 = new ImageOptionsV2(binaryReader); //offset: 0x58 27 | CRC = binaryReader.ReadUInt32(); //offset: 0x6A 28 | } 29 | 30 | public override string ToString() 31 | { 32 | var imageHeadLines = ImageHeadV2?.ToString().ToLines(); 33 | var fileSystemInfoLines = FileSystemInfoV2?.ToString().ToLines(); 34 | var imageOptionsLines = ImageOptionsV2?.ToString().ToLines(); 35 | 36 | var result = $@" 37 | ImageDescV2 38 | 39 | ImageHeadV2 40 | {imageHeadLines?.ToString(" ", Environment.NewLine)} 41 | 42 | FileSystemInfoV2 43 | {fileSystemInfoLines?.ToString(" ", Environment.NewLine)} 44 | 45 | ImageOptionsV2 46 | {imageOptionsLines?.ToString(" ", Environment.NewLine)} 47 | 48 | CRC 49 | 0x{CRC:X} 50 | 51 | 52 | ".Trim(); 53 | 54 | return result; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /libPartclone/Metadata/ImageHeadV1.cs: -------------------------------------------------------------------------------- 1 | using libCommon; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text; 6 | 7 | namespace libPartclone.Metadata 8 | { 9 | public class ImageHeadV1 10 | { 11 | //char magic[IMAGE_MAGIC_SIZE]; 12 | public string? Magic; //15 bytes 13 | 14 | //char fs[FS_MAGIC_SIZE]; 15 | public string? FileSystem; //15 bytes 16 | 17 | //char version[IMAGE_VERSION_SIZE]; 18 | public string? ImageVersion; //4 bytes 19 | 20 | //char padding[2]; 21 | public string? Padding; //2 bytes 22 | 23 | public ImageHeadV1() 24 | { 25 | 26 | } 27 | 28 | public ImageHeadV1(BinaryReader binaryReader) 29 | { 30 | Magic = Encoding.ASCII.GetString(binaryReader.ReadBytes(15)).TrimEnd('\0'); 31 | FileSystem = Encoding.ASCII.GetString(binaryReader.ReadBytes(15)).TrimEnd('\0'); 32 | ImageVersion = Encoding.ASCII.GetString(binaryReader.ReadBytes(4)).TrimEnd('\0'); 33 | Padding = Encoding.ASCII.GetString(binaryReader.ReadBytes(2)).TrimEnd('\0'); 34 | } 35 | 36 | public override string ToString() 37 | { 38 | string[] lines = [ 39 | $"Magic: {Magic}", 40 | $"FileSystem: {FileSystem}", 41 | $"ImageVersion: {ImageVersion}", 42 | $"Padding: {Padding}" 43 | ]; 44 | 45 | string result = lines.ToString(Environment.NewLine); 46 | 47 | return result; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /libPartclone/Metadata/ImageHeadV2.cs: -------------------------------------------------------------------------------- 1 | using libCommon; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace libPartclone.Metadata 10 | { 11 | public class ImageHeadV2 12 | { 13 | public string? Magic; 14 | 15 | /// Partclone's version who created the image, ex: "2.61" 16 | public string? PartcloneVersion; 17 | 18 | /// Image's version 19 | public string? ImageVersion; 20 | 21 | /// 0xC0DE = little-endian, 0xDEC0 = big-endian 22 | public bool BigEndian; 23 | 24 | public ImageHeadV2() 25 | { 26 | 27 | } 28 | 29 | public ImageHeadV2(BinaryReader binaryReader) 30 | { 31 | Magic = Encoding.ASCII.GetString(binaryReader.ReadBytes(16)).TrimEnd('\0'); 32 | PartcloneVersion = Encoding.ASCII.GetString(binaryReader.ReadBytes(14)).TrimEnd('\0'); 33 | ImageVersion = Encoding.ASCII.GetString(binaryReader.ReadBytes(4)).TrimEnd('\0'); 34 | 35 | ushort endianess = binaryReader.ReadUInt16(); 36 | //ushort endianess = BitConverter.ToUInt16(binaryReader.ReadBytes(2).Reverse().ToArray(), 0); 37 | BigEndian = endianess == 0xC0DE; 38 | } 39 | 40 | public override string ToString() 41 | { 42 | string[] lines = [ 43 | $"Magic: {Magic}", 44 | $"PartcloneVersion: {PartcloneVersion}", 45 | $"ImageVersion: {ImageVersion}", 46 | $"Endianess: {(BigEndian ? "Big Endian" : "Little Endian")}" 47 | ]; 48 | 49 | string result = lines.ToString(Environment.NewLine); 50 | 51 | return result; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /libPartclone/Metadata/ImageOptionsV1.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace libPartclone.Metadata 4 | { 5 | public class ImageOptionsV1 6 | { 7 | public byte[] Buff = new byte[4096]; 8 | 9 | public ImageOptionsV1() 10 | { 11 | 12 | } 13 | 14 | public ImageOptionsV1(BinaryReader binaryReader) 15 | { 16 | Buff = binaryReader.ReadBytes(Buff.Length); 17 | } 18 | 19 | public override string ToString() 20 | { 21 | var result = $@" 22 | Buff Length: {Buff.Length} 23 | ".Trim(); 24 | 25 | return result; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /libPartclone/Metadata/ImageOptionsV2.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libPartclone.Metadata 9 | { 10 | public class ImageOptionsV2 11 | { 12 | /// Number of bytes used by this struct 13 | public uint FeatureSize; 14 | 15 | /// version of the image 16 | public ushort ImageVersion; 17 | 18 | /// partclone's compilation architecture: 32 bits or 64 bits 19 | public ushort CpuBits; 20 | 21 | /// checksum algorithm used (see checksum_mode_enum) 22 | public CrcModeEnum ChecksumMode; 23 | 24 | /// Size of one checksum, in bytes. 0 when NONE, 4 with CRC32, etc. 25 | public ushort ChecksumSize; 26 | 27 | /// How many consecutive blocks are checksumed together. 28 | public uint BlocksPerChecksum; 29 | 30 | /// Reseed the checksum after each write (1 = yes; 0 = no) 31 | public byte ReseedChecksum; 32 | 33 | /// Kind of bitmap stored in the image (see bitmap_mode_enum) 34 | public BitmapMode BitmapMode; 35 | 36 | public ImageOptionsV2() 37 | { 38 | 39 | } 40 | 41 | public ImageOptionsV2(BinaryReader binaryReader) 42 | { 43 | FeatureSize = binaryReader.ReadUInt32(); 44 | 45 | ImageVersion = binaryReader.ReadUInt16(); 46 | CpuBits = binaryReader.ReadUInt16(); 47 | ChecksumMode = (CrcModeEnum)binaryReader.ReadUInt16(); 48 | ChecksumSize = binaryReader.ReadUInt16(); 49 | 50 | BlocksPerChecksum = binaryReader.ReadUInt32(); 51 | 52 | ReseedChecksum = binaryReader.ReadByte(); 53 | BitmapMode = (BitmapMode)binaryReader.ReadByte(); 54 | } 55 | 56 | public override string ToString() 57 | { 58 | var result = $@" 59 | FeatureSize: {FeatureSize} 60 | ImageVersion: {ImageVersion} 61 | CpuBits: {CpuBits} 62 | ChecksumMode: {ChecksumMode} 63 | ChecksumSize: {ChecksumSize} 64 | BlocksPerChecksum: {BlocksPerChecksum} 65 | ReseedChecksum: {ReseedChecksum} 66 | BitmapMode: {BitmapMode} 67 | 68 | ".Trim(); 69 | 70 | return result; 71 | } 72 | } 73 | 74 | public enum BitmapMode 75 | { 76 | BM_NONE = 0x00, 77 | BM_BIT = 0x01, 78 | BM_BYTE = 0x08, 79 | } 80 | 81 | public enum CrcModeEnum 82 | { 83 | CSM_NONE = 0x00, 84 | CSM_CRC32 = 0x20, 85 | CSM_CRC32_0001 = 0xFF, 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /libPartclone/libPartclone.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | 08e71b31-a475-4367-8c34-a03aac4fa2f7 7 | 8 | 9 | 10 | full 11 | true 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /libTrainCompress/Compressors/Compressor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace libTrainCompress.Compressors 8 | { 9 | public abstract class Compressor 10 | { 11 | public string CompressionFormat; 12 | public abstract Stream GetCompressor(Stream streamToWriteTo); 13 | 14 | public abstract Stream GetDecompressor(Stream streamToReadFrom); 15 | 16 | public Compressor(string compressionFormat) 17 | { 18 | CompressionFormat = compressionFormat; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libTrainCompress/Compressors/gzCompressor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Compression; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libTrainCompress.Compressors 9 | { 10 | public class gzCompressor : Compressor 11 | { 12 | public gzCompressor() : base("gz") 13 | { 14 | } 15 | 16 | public override Stream GetCompressor(Stream streamToWriteTo) 17 | { 18 | var result = new GZipStream(streamToWriteTo, CompressionMode.Compress, true); 19 | return result; 20 | } 21 | 22 | 23 | public override Stream GetDecompressor(Stream streamToReadFrom) 24 | { 25 | var result = new GZipStream(streamToReadFrom, CompressionMode.Decompress, true); 26 | return result; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /libTrainCompress/Compressors/xzCompressor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace libTrainCompress.Compressors 8 | { 9 | public class xzCompressor : Compressor 10 | { 11 | public xzCompressor() : base("xz") 12 | { 13 | } 14 | 15 | public override Stream GetCompressor(Stream streamToWriteTo) 16 | { 17 | //Joveler.Compression.XZ 18 | /* 19 | 20 | var compressOptions = new XZCompressOptions() 21 | { 22 | LeaveOpen = true 23 | }; 24 | 25 | var threadedCompressOptions = new XZThreadedCompressOptions() 26 | { 27 | Threads = Environment.ProcessorCount 28 | }; 29 | 30 | 31 | XZInit.GlobalInit(); 32 | 33 | var verInst = XZInit.Version(); 34 | Console.WriteLine($"liblzma Version (Version) = {verInst}"); 35 | 36 | var verStr = XZInit.VersionString(); 37 | Console.WriteLine($"liblzma Version (String) = {verStr}"); 38 | 39 | 40 | var result = new Joveler.Compression.XZ.XZStream(streamToWriteTo, compressOptions, threadedCompressOptions); 41 | return result; 42 | */ 43 | 44 | var result = new XZ.NET.XZOutputStream(streamToWriteTo, Environment.ProcessorCount, 0, true); 45 | return result; 46 | } 47 | 48 | public override Stream GetDecompressor(Stream streamToReadFrom) 49 | { 50 | var result = new XZ.NET.XZInputStream(streamToReadFrom, true); 51 | 52 | return result; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /libTrainCompress/Compressors/zstdCompressor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace libTrainCompress.Compressors 8 | { 9 | public class zstdCompressor : Compressor 10 | { 11 | public zstdCompressor() : base("zstd") 12 | { 13 | } 14 | 15 | public override Stream GetCompressor(Stream streamToWriteTo) 16 | { 17 | var result = new ZstdNet.CompressionStream(streamToWriteTo); 18 | return result; 19 | } 20 | 21 | public override Stream GetDecompressor(Stream streamToReadFrom) 22 | { 23 | var result = new ZstdNet.DecompressionStream(streamToReadFrom); 24 | return result; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libTrainCompress/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0290:Use primary constructor", Justification = "", Scope = "module")] 9 | [assembly: SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "", Scope = "module")] 10 | [assembly: SuppressMessage("Maintainability", "CA1513:Use ObjectDisposedException throw helper", Justification = "", Scope = "module")] 11 | -------------------------------------------------------------------------------- /libTrainCompress/Lists/CarriageComparer.cs: -------------------------------------------------------------------------------- 1 | using libCommon.Lists; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace libTrainCompress.Lists 9 | { 10 | public class CarriageComparer : IRangeComparer 11 | { 12 | public int Compare(Carriage range, long value) 13 | { 14 | if (value < range.UncompressedStartByte) return 1; 15 | if (value >= range.UncompressedEndByte) return -1; 16 | return 0; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libTrainCompress/Streams/SubStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace libTrainCompress.Streams 8 | { 9 | public class SubStream : Stream 10 | { 11 | readonly Stream vector; 12 | private readonly long offset; 13 | private readonly long length; 14 | private long position = 0; 15 | 16 | public SubStream(Stream vector, long offset, long length) 17 | { 18 | if (length < 1) throw new ArgumentException("Length must be greater than zero."); 19 | 20 | this.vector = vector; 21 | this.offset = offset; 22 | this.length = length; 23 | 24 | vector.Seek(offset, SeekOrigin.Begin); 25 | } 26 | 27 | public override int Read(byte[] buffer, int offset, int count) 28 | { 29 | CheckDisposed(); 30 | long remaining = length - position; 31 | if (remaining <= 0) return 0; 32 | if (remaining < count) count = (int)remaining; 33 | int read = vector.Read(buffer, offset, count); 34 | position += read; 35 | return read; 36 | } 37 | 38 | private void CheckDisposed() 39 | { 40 | if (vector == null) throw new ObjectDisposedException(GetType().Name); 41 | } 42 | 43 | public override long Seek(long offset, SeekOrigin origin) 44 | { 45 | long pos = position; 46 | 47 | if (origin == SeekOrigin.Begin) 48 | pos = offset; 49 | else if (origin == SeekOrigin.End) 50 | pos = length + offset; 51 | else if (origin == SeekOrigin.Current) 52 | pos += offset; 53 | 54 | if (pos < 0) pos = 0; 55 | else if (pos >= length) pos = length - 1; 56 | 57 | position = vector.Seek(this.offset + pos, SeekOrigin.Begin) - this.offset; 58 | 59 | return pos; 60 | } 61 | 62 | public override bool CanRead => true; 63 | 64 | public override bool CanSeek => true; 65 | 66 | public override bool CanWrite => false; 67 | 68 | public override long Length => length; 69 | 70 | public override long Position { get => position; set { position = this.Seek(value, SeekOrigin.Begin); } } 71 | 72 | public override void Flush() 73 | { 74 | throw new NotImplementedException(); 75 | } 76 | 77 | public override void SetLength(long value) 78 | { 79 | throw new NotImplementedException(); 80 | } 81 | 82 | public override void Write(byte[] buffer, int offset, int count) 83 | { 84 | throw new NotImplementedException(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /libTrainCompress/libTrainCompress.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------