├── .gitattributes ├── .github └── workflows │ └── dotnet-core.yml ├── .gitignore ├── DeltaCompressionDotNet.sln ├── LICENSE ├── README.md ├── src └── DeltaCompressionDotNet │ ├── DeltaCompressionDotNet.csproj │ ├── IDeltaCompression.cs │ ├── MsDelta │ ├── ApplyFlags.cs │ ├── CreateFlags.cs │ ├── DeltaInput.cs │ ├── FileTypeSet.cs │ ├── HashAlgId.cs │ ├── MsDeltaCompression.cs │ └── NativeMethods.cs │ └── PatchApi │ ├── NativeMethods.cs │ └── PatchApiCompression.cs └── test └── DeltaCompressionDotNet.Tests ├── CompressionTest.cs ├── CompressionTests.cs ├── DeltaCompressionDotNet.Tests.csproj ├── MsDeltaCompressionTests.cs ├── PatchApiCompressionTests.cs └── RandomFile.cs /.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 | -------------------------------------------------------------------------------- /.github/workflows/dotnet-core.yml: -------------------------------------------------------------------------------- 1 | name: .NET Core 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: windows-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | fetch-depth: 0 14 | 15 | - name: Install GitVersion 16 | uses: gittools/actions/gitversion/setup@v0.9.7 17 | with: 18 | versionSpec: '5.x' 19 | 20 | - name: Use GitVersion 21 | id: gitVersion 22 | uses: gittools/actions/gitversion/execute@v0.9.7 23 | 24 | - name: Use .NET 5 25 | uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: '5.0.x' 28 | 29 | - name: dotnet test 30 | run: dotnet test --configuration Release 31 | 32 | - name: dotnet pack 33 | run: dotnet pack --configuration Release --no-build 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | .vs/ 10 | 11 | # Build results 12 | [Dd]ebug/ 13 | [Dd]ebugPublic/ 14 | [Rr]elease/ 15 | [Rr]eleases/ 16 | x64/ 17 | x86/ 18 | build/ 19 | bld/ 20 | [Bb]in/ 21 | [Oo]bj/ 22 | 23 | # Roslyn cache directories 24 | *.ide/ 25 | 26 | # MSTest test Results 27 | [Tt]est[Rr]esult*/ 28 | [Bb]uild[Ll]og.* 29 | 30 | #NUNIT 31 | *.VisualState.xml 32 | TestResult.xml 33 | 34 | # Build Results of an ATL Project 35 | [Dd]ebugPS/ 36 | [Rr]eleasePS/ 37 | dlldata.c 38 | 39 | *_i.c 40 | *_p.c 41 | *_i.h 42 | *.ilk 43 | *.meta 44 | *.obj 45 | *.pch 46 | *.pdb 47 | *.pgc 48 | *.pgd 49 | *.rsp 50 | *.sbr 51 | *.tlb 52 | *.tli 53 | *.tlh 54 | *.tmp 55 | *.tmp_proj 56 | *.log 57 | *.vspscc 58 | *.vssscc 59 | .builds 60 | *.pidb 61 | *.svclog 62 | *.scc 63 | 64 | # Chutzpah Test files 65 | _Chutzpah* 66 | 67 | # Visual C++ cache files 68 | ipch/ 69 | *.aps 70 | *.ncb 71 | *.opensdf 72 | *.sdf 73 | *.cachefile 74 | 75 | # Visual Studio profiler 76 | *.psess 77 | *.vsp 78 | *.vspx 79 | 80 | # TFS 2012 Local Workspace 81 | $tf/ 82 | 83 | # Guidance Automation Toolkit 84 | *.gpState 85 | 86 | # ReSharper is a .NET coding add-in 87 | _ReSharper*/ 88 | *.[Rr]e[Ss]harper 89 | *.DotSettings.user 90 | 91 | # JustCode is a .NET coding addin-in 92 | .JustCode 93 | 94 | # TeamCity is a build add-in 95 | _TeamCity* 96 | 97 | # DotCover is a Code Coverage Tool 98 | *.dotCover 99 | 100 | # NCrunch 101 | _NCrunch_* 102 | .*crunch*.local.xml 103 | 104 | # MightyMoose 105 | *.mm.* 106 | AutoTest.Net/ 107 | 108 | # Web workbench (sass) 109 | .sass-cache/ 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.[Pp]ublish.xml 129 | *.azurePubxml 130 | # TODO: Comment the next line if you want to checkin your web deploy settings 131 | # but database connection strings (with potential passwords) will be unencrypted 132 | *.pubxml 133 | *.publishproj 134 | 135 | # NuGet Packages 136 | *.nupkg 137 | # The packages folder can be ignored because of Package Restore 138 | **/packages/* 139 | # except build/, which is used as an MSBuild target. 140 | !**/packages/build/ 141 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 142 | #!**/packages/repositories.config 143 | 144 | # Windows Azure Build Output 145 | csx/ 146 | *.build.csdef 147 | 148 | # Windows Store app package directory 149 | AppPackages/ 150 | 151 | # Others 152 | sql/ 153 | *.Cache 154 | ClientBin/ 155 | [Ss]tyle[Cc]op.* 156 | ~$* 157 | *~ 158 | *.dbmdl 159 | *.dbproj.schemaview 160 | *.pfx 161 | *.publishsettings 162 | node_modules/ 163 | bower_components/ 164 | 165 | # RIA/Silverlight projects 166 | Generated_Code/ 167 | 168 | # Backup & report files from converting an old project file 169 | # to a newer Visual Studio version. Backup files are not needed, 170 | # because we have git ;-) 171 | _UpgradeReport_Files/ 172 | Backup*/ 173 | UpgradeLog*.XML 174 | UpgradeLog*.htm 175 | 176 | # SQL Server files 177 | *.mdf 178 | *.ldf 179 | 180 | # Business Intelligence projects 181 | *.rdl.data 182 | *.bim.layout 183 | *.bim_*.settings 184 | 185 | # Microsoft Fakes 186 | FakesAssemblies/ 187 | 188 | .idea/ 189 | -------------------------------------------------------------------------------- /DeltaCompressionDotNet.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30717.126 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{46FB246E-CF40-4388-835A-C9A896938E58}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitattributes = .gitattributes 9 | .gitignore = .gitignore 10 | README.md = README.md 11 | EndProjectSection 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8A47F536-39D7-472E-82DE-F7888AA0C51E}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{5F11AFE4-A174-469D-B882-1DC993917DFA}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeltaCompressionDotNet", "src\DeltaCompressionDotNet\DeltaCompressionDotNet.csproj", "{FA04F22B-5282-4062-B039-3585C0A6F8DB}" 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeltaCompressionDotNet.Tests", "test\DeltaCompressionDotNet.Tests\DeltaCompressionDotNet.Tests.csproj", "{C938FAEB-B1B4-4A03-AC03-BFC58AF2FAFF}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{055CC9E8-2188-4E4C-93C8-22A77DDD8FF4}" 22 | EndProject 23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{1DF3A5AC-DDAD-4B98-9E8B-D42EDD577FD7}" 24 | ProjectSection(SolutionItems) = preProject 25 | .github\workflows\dotnet-core.yml = .github\workflows\dotnet-core.yml 26 | EndProjectSection 27 | EndProject 28 | Global 29 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 30 | Debug|Any CPU = Debug|Any CPU 31 | Release|Any CPU = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 34 | {FA04F22B-5282-4062-B039-3585C0A6F8DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {FA04F22B-5282-4062-B039-3585C0A6F8DB}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {FA04F22B-5282-4062-B039-3585C0A6F8DB}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {FA04F22B-5282-4062-B039-3585C0A6F8DB}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {C938FAEB-B1B4-4A03-AC03-BFC58AF2FAFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {C938FAEB-B1B4-4A03-AC03-BFC58AF2FAFF}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {C938FAEB-B1B4-4A03-AC03-BFC58AF2FAFF}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {C938FAEB-B1B4-4A03-AC03-BFC58AF2FAFF}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {964E8AF5-A41C-473D-8DDB-CB81B33FBC68} 48 | EndGlobalSection 49 | GlobalSection(NestedProjects) = preSolution 50 | {FA04F22B-5282-4062-B039-3585C0A6F8DB} = {8A47F536-39D7-472E-82DE-F7888AA0C51E} 51 | {C938FAEB-B1B4-4A03-AC03-BFC58AF2FAFF} = {5F11AFE4-A174-469D-B882-1DC993917DFA} 52 | {055CC9E8-2188-4E4C-93C8-22A77DDD8FF4} = {46FB246E-CF40-4388-835A-C9A896938E58} 53 | {1DF3A5AC-DDAD-4B98-9E8B-D42EDD577FD7} = {055CC9E8-2188-4E4C-93C8-22A77DDD8FF4} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Microsoft Public License (MS-PL) 2 | 3 | This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. 4 | 5 | 1. Definitions 6 | 7 | The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. 8 | 9 | A "contribution" is the original software, or any additions or changes to the software. 10 | A "contributor" is any person that distributes its contribution under this license. 11 | "Licensed patents" are a contributor's patent claims that read directly on its contribution. 12 | 13 | 2. Grant of Rights 14 | 15 | (A) Copyright Grant - Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. 16 | 17 | (B) Patent Grant - Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. 18 | 19 | 3. Conditions and Limitations 20 | 21 | (A) No Trademark License - This license does not grant you rights to use any contributors' name, logo, or trademarks. 22 | 23 | (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. 24 | 25 | (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. 26 | 27 | (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. 28 | 29 | (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeltaCompressionDotNet 2 | 3 | A managed wrapper around [Microsoft's delta compression application programming interfaces](http://msdn.microsoft.com/en-us/library/bb417345.aspx). 4 | 5 | > ⚠️ **This class library only works on Windows** 6 | > 7 | > If you need delta compression support that's cross platform, consider the managed implementation of bsdiff in [bsdiff.net](https://github.com/LogosBible/bsdiff.net). 8 | > It's included in the [deltaq](https://www.nuget.org/packages/deltaq/) NuGet package. 9 | 10 | ## License 11 | 12 | *DeltaCompressionDotNet* is licensed under the [*Microsoft Public License (MS-PL)*](https://opensource.org/licenses/MS-PL). 13 | 14 | It's worth noting that large parts of *DeltaCompressionDotNet*'s implementation are generic (we're talking `rangeCheck`, here) and while this wrapper represents my own, original work, it would be difficult to enforce the license against anything but a slavish copy. 15 | 16 | To this extent, the library is also licensed under the [*DBAD-PL*](http://www.dbad-license.org). 17 | 18 | ## Getting Started 19 | 20 | [Install the *DeltaCompressionDotNet* package from NuGet](http://nuget.org/packages/DeltaCompressionDotNet/). 21 | 22 | ### Creating Deltas 23 | 24 | var compression = new MsDeltaCompression(); /* or PatchApiCompression(); */ 25 | compression.CreateDelta(sourcePath, destinationPath, deltaPath); 26 | 27 | `CreateDelta` returns `void`; if the function is not successful in creating a delta a `Win32Exception` is thrown. The `NativeErrorCode` of the exception should contain the value of `GetLastError()`, which may describe the failure. 28 | 29 | *MSDelta* has a default limit of 32 MiB for source and destination files. The `MsDeltaCompression` implementation uses the `DELTA_FLAG_IGNORE_FILE_SIZE_LIMIT` flag to permit inputs of any size, but note that performance and memory usage suffers as inputs get larger. See [Run-Time Requirements](https://msdn.microsoft.com/en-us/library/bb417345.aspx#runtime_reqs) estimated memory usage. 30 | 31 | ### Applying Deltas 32 | 33 | var compression = new MsDeltaCompression(); /* or PatchApiCompression(); */ 34 | compression.ApplyDelta(deltaPath, sourcePath, destinationPath); 35 | 36 | `ApplyDelta` returns `void`; if the function is not successful in applying a delta a `Win32Exception` is thrown. The `NativeErrorCode` of the exception should contain the value of `GetLastError()`, which may describe the failure. 37 | 38 | ## Miscellany 39 | 40 | Both `MsDeltaCompression` and `PatchApiCompression` implement `IDeltaCompression`. 41 | 42 | *PatchAPI* (concomitant class: `PatchApiCompression`) is available from Windows 2000; *MSDelta* (concomitant class: `MsDeltaCompression`) is available from Windows Vista. 43 | 44 | *DeltaCompressionDotNet* targets the .NET Framework 2.0, but if you're lucky enough to be using the .NET Framework 4.5 then you should prefer `MsDeltaCompression` over `PatchApiCompression` where possible. 45 | 46 | The library is not tied to any particular architecture, the code should execute on x86, x64 and ia64. 47 | 48 | It's unlikely that *DeltaCompressionDotNet* will work on operating systems other than Windows (à la Mono). 49 | 50 | ## Bugs/Feedback 51 | 52 | If you encounter a scenario where the wrapping around *PatchAPI* or *MSDelta* doesn't work as you expect, you're welcome to report a bug, initiate a pull request or send an email to `t AT speot DOT is`. The latter method is likely to elicit a response, but not guaranteed. 53 | 54 | The above applies equally for feedback. 55 | 56 | ## Contributors 57 | 58 | [Mark Junker](https://github.com/fubar-coder) made [this request](https://github.com/taspeotis/DeltaCompressionDotNet/issues/2) to get *DeltaCompressionDotNet* working with Windows XP. 59 | 60 | [Atif Aziz](https://github.com/atifaziz) kindly submitted [improvements](https://github.com/taspeotis/DeltaCompressionDotNet/pull/3) to the unit tests ([twice](https://github.com/taspeotis/DeltaCompressionDotNet/pull/4)) and provided advice on structuring the project. 61 | 62 | John Anderson from [Fungusware](http://www.fungusware.com/) identified [several](https://github.com/taspeotis/DeltaCompressionDotNet/issues/7) [issues](https://github.com/taspeotis/DeltaCompressionDotNet/issues/8) [with](https://github.com/taspeotis/DeltaCompressionDotNet/issues/9) *DeltaCompressionDotNet*. 63 | 64 | [Jann Roder](https://github.com/roederja2) moved the target framework [forward](https://github.com/taspeotis/DeltaCompressionDotNet/pull/15) to .NET Standard. 65 | -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/DeltaCompressionDotNet.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | default 5 | enable 6 | netstandard2.0;netstandard1.3;net20 7 | 8 | -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/IDeltaCompression.cs: -------------------------------------------------------------------------------- 1 | namespace DeltaCompressionDotNet 2 | { 3 | public interface IDeltaCompression 4 | { 5 | void CreateDelta(string oldFilePath, string newFilePath, string deltaFilePath); 6 | 7 | void ApplyDelta(string deltaFilePath, string oldFilePath, string newFilePath); 8 | } 9 | 10 | // TODO IDeltaCompressionWithHandles (exclusively PatchAPI) 11 | // TODO IDeltaCompressionWithBuffers (PatchAPI and MSDelta) 12 | } -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/MsDelta/ApplyFlags.cs: -------------------------------------------------------------------------------- 1 | namespace DeltaCompressionDotNet.MsDelta 2 | { 3 | /// 4 | /// http://msdn.microsoft.com/en-us/library/bb417345.aspx#deltaflagtypeflags 5 | /// 6 | internal enum ApplyFlags : long 7 | { 8 | /// Indicates no special handling. 9 | None = 0, 10 | 11 | /// Allow MSDelta to apply deltas created using PatchAPI. 12 | AllowLegacy = 1 13 | } 14 | } -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/MsDelta/CreateFlags.cs: -------------------------------------------------------------------------------- 1 | namespace DeltaCompressionDotNet.MsDelta 2 | { 3 | /// 4 | /// http://msdn.microsoft.com/en-us/library/bb417345.aspx#deltaflagtypeflags 5 | /// 6 | internal enum CreateFlags : long 7 | { 8 | /// Indicates no special handling. 9 | None = 0, 10 | 11 | /// Allow the source, target and delta files to exceed the default size limit. 12 | IgnoreFileSizeLimit = 1 << 17 13 | }; 14 | } -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/MsDelta/DeltaInput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace DeltaCompressionDotNet.MsDelta 5 | { 6 | /// 7 | /// http://msdn.microsoft.com/en-us/library/bb417345.aspx#deltainputstructure 8 | /// 9 | [StructLayout(LayoutKind.Sequential)] 10 | internal struct DeltaInput 11 | { 12 | /// Memory address non-editable input buffer. 13 | public IntPtr Start; 14 | 15 | /// Size of the memory buffer in bytes. 16 | public IntPtr Size; 17 | 18 | /// 19 | /// Defines whether MSDelta is allowed to edit the input buffer. If you make the input editable, the buffer will 20 | /// be zeroed at function return. However this will cause most MSDelta functions to use less memory. 21 | /// 22 | [MarshalAs(UnmanagedType.Bool)] public bool Editable; 23 | } 24 | } -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/MsDelta/FileTypeSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DeltaCompressionDotNet.MsDelta 4 | { 5 | /// 6 | /// http://msdn.microsoft.com/en-us/library/bb417345.aspx#filetypesets 7 | /// 8 | [Flags] 9 | internal enum FileTypeSet : long 10 | { 11 | /// 12 | /// File type set that includes I386, IA64 and AMD64 Portable Executable (PE) files. Others are treated as raw. 13 | /// 14 | Executables = 0x0FL 15 | } 16 | } -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/MsDelta/HashAlgId.cs: -------------------------------------------------------------------------------- 1 | namespace DeltaCompressionDotNet.MsDelta 2 | { 3 | internal enum HashAlgId 4 | { 5 | /// No signature. 6 | None = 0, 7 | 8 | /// 32-bit CRC defined in msdelta.dll. 9 | Crc32 = 32 10 | } 11 | } -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/MsDelta/MsDeltaCompression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace DeltaCompressionDotNet.MsDelta 5 | { 6 | public sealed class MsDeltaCompression : IDeltaCompression 7 | { 8 | public void CreateDelta(string oldFilePath, string newFilePath, string deltaFilePath) 9 | { 10 | const string? sourceOptionsName = null; 11 | const string? targetOptionsName = null; 12 | var globalOptions = new DeltaInput(); 13 | var targetFileTime = IntPtr.Zero; 14 | 15 | if (!NativeMethods.CreateDelta( 16 | FileTypeSet.Executables, CreateFlags.IgnoreFileSizeLimit, CreateFlags.None, oldFilePath, newFilePath, 17 | sourceOptionsName, targetOptionsName, globalOptions, targetFileTime, HashAlgId.Crc32, deltaFilePath)) 18 | { 19 | throw new Win32Exception(); 20 | } 21 | } 22 | 23 | public void ApplyDelta(string deltaFilePath, string oldFilePath, string newFilePath) 24 | { 25 | if (!NativeMethods.ApplyDelta(ApplyFlags.AllowLegacy, oldFilePath, deltaFilePath, newFilePath)) 26 | throw new Win32Exception(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/MsDelta/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace DeltaCompressionDotNet.MsDelta 5 | { 6 | internal static class NativeMethods 7 | { 8 | /// 9 | /// The ApplyDelta function use the specified delta and source files to create a new copy of the target file. 10 | /// 11 | /// Either DELTA_FLAG_NONE or DELTA_APPLY_FLAG_ALLOW_PA19. 12 | /// The name of the source file to which the delta is to be applied. 13 | /// The name of the delta to be applied to the source file. 14 | /// The name of the target file that is to be created. 15 | /// 16 | /// Returns TRUE on success or FALSE otherwise. 17 | /// 18 | /// 19 | /// http://msdn.microsoft.com/en-us/library/bb417345.aspx#applydeltaaw 20 | /// 21 | [DllImport("msdelta.dll", CharSet = CharSet.Unicode, SetLastError = true)] 22 | [return: MarshalAs(UnmanagedType.Bool)] 23 | public static extern bool ApplyDelta( 24 | [MarshalAs(UnmanagedType.I8)] ApplyFlags applyFlags, 25 | string sourceName, 26 | string deltaName, 27 | string targetName); 28 | 29 | /// 30 | /// The CreateDelta function creates a delta from the specified source and target files and write the output delta to the designated file name. 31 | /// 32 | /// The file type set used for Create. 33 | /// The file type set used for Create. 34 | /// The file type set used for Create. 35 | /// The file type set used for Create. 36 | /// The name of the target against which the source is compared. 37 | /// Reserved. Pass NULL. 38 | /// Reserved. Pass NULL. 39 | /// Reserved. Pass a DELTA_INPUT structure with lpStart set to NULL and uSize set to 0. 40 | /// The time stamp set on the target file after delta Apply. If NULL, the timestamp of the target file during delta Create will be used. 41 | /// ALG_ID of the algorithm to be used to generate the target signature. 42 | /// The name of the delta file to be created. 43 | /// 44 | /// Returns TRUE on success or FALSE otherwise. 45 | /// 46 | /// 47 | /// http://msdn.microsoft.com/en-us/library/bb417345.aspx#createdeltaaw 48 | /// 49 | [DllImport("msdelta.dll", CharSet = CharSet.Unicode, SetLastError = true)] 50 | [return: MarshalAs(UnmanagedType.Bool)] 51 | public static extern bool CreateDelta( 52 | [MarshalAs(UnmanagedType.I8)] FileTypeSet fileTypeSet, 53 | [MarshalAs(UnmanagedType.I8)] CreateFlags setFlags, 54 | [MarshalAs(UnmanagedType.I8)] CreateFlags resetFlags, 55 | string sourceName, 56 | string targetName, 57 | string? sourceOptionsName, 58 | string? targetOptionsName, 59 | DeltaInput globalOptions, 60 | IntPtr targetFileTime, 61 | [MarshalAs(UnmanagedType.U4)] HashAlgId hashAlgId, 62 | string deltaName); 63 | } 64 | } -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/PatchApi/NativeMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace DeltaCompressionDotNet.PatchApi 5 | { 6 | internal static class NativeMethods 7 | { 8 | /// 9 | /// The ApplyPatchToFile function applies the specified delta to the specified source file. The output file is saved 10 | /// under the designated new file name. 11 | /// 12 | /// The name of the delta to be applied to the source file. 13 | /// The name of the source file to which the delta is to be applied. 14 | /// The name of the target file that is to be created. 15 | /// ApplyPatch Flags. 16 | /// Returns TRUE on success or FALSE otherwise. 17 | /// http://msdn.microsoft.com/en-us/library/bb417345.aspx#applypatchtofileaw 18 | [DllImport("mspatcha.dll", CharSet = CharSet.Unicode, SetLastError = true)] 19 | [return: MarshalAs(UnmanagedType.Bool)] 20 | public static extern bool ApplyPatchToFile( 21 | string patchFileName, string oldFileName, string newFileName, uint applyOptionFlags); 22 | 23 | /// 24 | /// The CreatePatchFile function creates a delta from the specified source and target files and write the delta to the 25 | /// designated file name. 26 | /// 27 | /// The name of the source file. 28 | /// The name of the target file. 29 | /// The name of the output delta file. 30 | /// Creation Flags. 31 | /// Not used. Pass NULL. Pointer to a structure of type PATCH_OPTION_DATA. 32 | /// Returns TRUE on success or FALSE otherwise. 33 | /// http://msdn.microsoft.com/en-us/library/bb417345.aspx#createpatchfileaw 34 | [DllImport("mspatchc.dll", CharSet = CharSet.Unicode, SetLastError = true)] 35 | [return: MarshalAs(UnmanagedType.Bool)] 36 | public static extern bool CreatePatchFile( 37 | string oldFileName, string newFileName, string patchFileName, uint optionFlags, IntPtr optionData); 38 | } 39 | } -------------------------------------------------------------------------------- /src/DeltaCompressionDotNet/PatchApi/PatchApiCompression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace DeltaCompressionDotNet.PatchApi 5 | { 6 | public sealed class PatchApiCompression : IDeltaCompression 7 | { 8 | public void CreateDelta(string oldFilePath, string newFilePath, string deltaFilePath) 9 | { 10 | const int optionFlags = 0; 11 | var optionData = IntPtr.Zero; 12 | 13 | if (!NativeMethods.CreatePatchFile(oldFilePath, newFilePath, deltaFilePath, optionFlags, optionData)) 14 | throw new Win32Exception(); 15 | } 16 | 17 | public void ApplyDelta(string deltaFilePath, string oldFilePath, string newFilePath) 18 | { 19 | const int applyOptionFlags = 0; 20 | 21 | if (!NativeMethods.ApplyPatchToFile(deltaFilePath, oldFilePath, newFilePath, applyOptionFlags)) 22 | throw new Win32Exception(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /test/DeltaCompressionDotNet.Tests/CompressionTest.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace DeltaCompressionDotNet.Tests 7 | { 8 | internal sealed class CompressionTest 9 | { 10 | private readonly string _privateFile1Path; 11 | 12 | private readonly string _privateFile2Path; 13 | 14 | private readonly string _privateDeltaPath; 15 | 16 | private readonly string _privateFinalPath; 17 | 18 | private readonly byte[] _expectedHash; 19 | 20 | public CompressionTest(string baseFolderPath, string file1FileName, string file2FileName) 21 | { 22 | _privateFile1Path = Path.GetTempFileName(); 23 | _privateFile2Path = Path.GetTempFileName(); 24 | _privateDeltaPath = Path.GetTempFileName(); 25 | _privateFinalPath = Path.GetTempFileName(); 26 | 27 | var file1Path = Path.Combine(baseFolderPath, file1FileName); 28 | var file2Path = Path.Combine(baseFolderPath, file2FileName); 29 | 30 | CopyFile(file1Path, _privateFile1Path); 31 | CopyFile(file2Path, _privateFile2Path); 32 | 33 | _expectedHash = HashFile(file2Path); 34 | } 35 | 36 | public void CreateAndApplyDelta(IDeltaCompression deltaCompression) 37 | { 38 | deltaCompression.CreateDelta(_privateFile1Path, _privateFile2Path, _privateDeltaPath); 39 | deltaCompression.ApplyDelta(_privateDeltaPath, _privateFile1Path, _privateFinalPath); 40 | 41 | var actualHash = HashFile(_privateFinalPath); 42 | 43 | CollectionAssert.AreEqual(_expectedHash, actualHash); 44 | } 45 | 46 | public void TestCleanup() 47 | { 48 | TryDeleteFile(_privateFile1Path); 49 | TryDeleteFile(_privateFile2Path); 50 | TryDeleteFile(_privateDeltaPath); 51 | TryDeleteFile(_privateFinalPath); 52 | } 53 | 54 | private static void CopyFile(string sourceFileName, string destinationFileName) 55 | { 56 | File.Copy(sourceFileName, destinationFileName, true); 57 | } 58 | 59 | private static byte[] HashFile(string path) 60 | { 61 | using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) 62 | using (var hashAlgorithm = SHA1.Create()) 63 | { 64 | return hashAlgorithm.ComputeHash(fileStream); 65 | } 66 | } 67 | 68 | [ExcludeFromCodeCoverage] 69 | private static void TryDeleteFile(string path) 70 | { 71 | try 72 | { 73 | File.Delete(path); 74 | } 75 | catch (IOException) 76 | { 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /test/DeltaCompressionDotNet.Tests/CompressionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.IO; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace DeltaCompressionDotNet.Tests 8 | { 9 | public class CompressionTests 10 | where TDeltaCompression : IDeltaCompression, new() 11 | { 12 | private const string CalcFileName = "Calc.exe"; 13 | private const string NotepadFileName = "Notepad.exe"; 14 | private const string MediaFolderName = "Media"; 15 | private const string Alarm01FileName = "Alarm01.wav"; 16 | private const string Alarm02FileName = "Alarm02.wav"; 17 | 18 | private readonly List _compressionTests = new List(); 19 | 20 | [TestInitialize] 21 | public void TestInitialize() 22 | { 23 | _compressionTests.Clear(); 24 | 25 | var systemFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.System); 26 | 27 | _compressionTests.Add(new CompressionTest(systemFolderPath, CalcFileName, NotepadFileName)); 28 | 29 | var windowsFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows); 30 | var mediaFolderPath = Path.Combine(windowsFolderPath, MediaFolderName); 31 | 32 | _compressionTests.Add(new CompressionTest(mediaFolderPath, Alarm01FileName, Alarm02FileName)); 33 | } 34 | 35 | [TestCleanup] 36 | public void TestCleanup() 37 | { 38 | foreach (var compressionTest in _compressionTests) 39 | compressionTest.TestCleanup(); 40 | } 41 | 42 | [ExpectedException(typeof (Win32Exception)), TestMethod] 43 | public void ApplyDelta_Calls_GetLastError() 44 | { 45 | var deltaCompression = new TDeltaCompression(); 46 | 47 | try 48 | { 49 | deltaCompression.ApplyDelta(null!, null!, null!); 50 | } 51 | catch (Win32Exception exception) 52 | { 53 | Assert.AreNotEqual(0, exception.NativeErrorCode); 54 | 55 | throw; 56 | } 57 | } 58 | 59 | [ExpectedException(typeof (Win32Exception)), TestMethod] 60 | public void CreateDelta_Calls_GetLastError() 61 | { 62 | var deltaCompression = new TDeltaCompression(); 63 | 64 | try 65 | { 66 | deltaCompression.CreateDelta(null!, null!, null!); 67 | } 68 | catch (Win32Exception exception) 69 | { 70 | Assert.AreNotEqual(0, exception.NativeErrorCode); 71 | 72 | throw; 73 | } 74 | } 75 | 76 | [TestMethod] 77 | public void CreateDelta_And_ApplyDelta_Creates_And_Applies_Delta() 78 | { 79 | var deltaCompression = new TDeltaCompression(); 80 | 81 | foreach (var compressionTest in _compressionTests) 82 | compressionTest.CreateAndApplyDelta(deltaCompression); 83 | } 84 | 85 | [TestMethod] 86 | public void CreateDelta_And_ApplyDelta_Handle_Big_Files() 87 | { 88 | var baseFolderPath = Path.GetTempPath(); 89 | using var tempFile1 = new RandomFile(baseFolderPath); 90 | using var tempFile2 = new RandomFile(baseFolderPath); 91 | var compressionTest = new CompressionTest(baseFolderPath, tempFile1.FileName, tempFile2.FileName); 92 | 93 | try 94 | { 95 | var deltaCompression = new TDeltaCompression(); 96 | 97 | compressionTest.CreateAndApplyDelta(deltaCompression); 98 | } 99 | finally 100 | { 101 | compressionTest.TestCleanup(); 102 | } 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /test/DeltaCompressionDotNet.Tests/DeltaCompressionDotNet.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | enable 6 | net5.0;net472 7 | 9 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/DeltaCompressionDotNet.Tests/MsDeltaCompressionTests.cs: -------------------------------------------------------------------------------- 1 | using DeltaCompressionDotNet.MsDelta; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace DeltaCompressionDotNet.Tests 5 | { 6 | [TestClass] 7 | public class MsDeltaCompressionTests : CompressionTests 8 | { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/DeltaCompressionDotNet.Tests/PatchApiCompressionTests.cs: -------------------------------------------------------------------------------- 1 | using DeltaCompressionDotNet.PatchApi; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace DeltaCompressionDotNet.Tests 5 | { 6 | [TestClass] 7 | public class PatchApiCompressionTests : CompressionTests 8 | { 9 | } 10 | } -------------------------------------------------------------------------------- /test/DeltaCompressionDotNet.Tests/RandomFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.Cryptography; 4 | 5 | namespace DeltaCompressionDotNet.Tests 6 | { 7 | internal sealed class RandomFile : IDisposable 8 | { 9 | public string FileName { get; private set; } 10 | 11 | private string FilePath { get; set; } 12 | 13 | public RandomFile(string baseFolderPath) 14 | { 15 | FileName = Path.GetRandomFileName(); 16 | FilePath = Path.Combine(baseFolderPath, FileName); 17 | 18 | try 19 | { 20 | using (var fileStream = new FileStream(FilePath, FileMode.Create)) 21 | using (var randomNumberGenerator = new RNGCryptoServiceProvider()) 22 | { 23 | var randomData = new byte[4096]; 24 | 25 | // 48 MB => Runs in a manageable amount of time yet still tests tests that we 26 | // can create deltas for files whose size exceed DELTA_FILE_SIZE_LIMIT (32 MB) 27 | for (var x = 0; x < 48*1024*1024; x += randomData.Length) 28 | { 29 | randomNumberGenerator.GetBytes(randomData); 30 | fileStream.Write(randomData, 0, randomData.Length); 31 | } 32 | } 33 | } 34 | catch (Exception) 35 | { 36 | Dispose(); 37 | 38 | throw; 39 | } 40 | } 41 | 42 | public void Dispose() 43 | { 44 | try 45 | { 46 | File.Delete(FilePath); 47 | } 48 | catch (IOException) 49 | { 50 | } 51 | } 52 | } 53 | } --------------------------------------------------------------------------------