├── .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 | }
--------------------------------------------------------------------------------