Delta Patching Logic
This page describes the logic performed to generate and apply delta patches.
Applying Deltas
The delta update logic is as follows.
-
Check all
PatchSources
andFilesToCopy
against existing files.- Find item in File Hash Cache with same hash.
- Abort if not found.
- Check hash of actual file obtained from File Hash Cache.
- If file on disk has same write time, use cached hash.
- If file has different write time, recompute the hash. Don't update cache however!!
- Abort if hash mismatches.
- Get file path to file with hash.
- Find item in File Hash Cache with same hash.
-
Apply delta patches for all files.
- Writing them directly to temp directory.
-
Extract all files in
FilesToExtract
to temp directory.- Find file in archive by matching hash.
-
Copy all files in
FilesToCopy
to temp directory. - Rename temp directory to output directory.
In order to prevent data loss, the delta update logic will NOT run in place.
In the case of sudden termination of the process or power loss, the original folder must remain unchanged. Therefore the output will always be to a new directory, then we'll simply swap the directories once operation is complete.
Temp directory should be on the same mount as the output directory, ideally in parent folder.
Generating Deltas
These are the steps to generate a single delta patch.
This follows a 3 step process:
Intermediate .patch
files (patches) are stored in RAM.
Pack New Version
Pack the NewVersion
of the mod (non-delta).
This can be used as the reference .nx
file from which we source the data in the later steps.
This is useful, because the packing step also provides us with hashes of the final files (for free),
and pre-compressed data (for Repacking feature of .nx
) to accelerate the
delta generation process.
Get Previous Version(s)
Obtain the previous versions to create deltas from.
User should be able to select the previous versions they wish to create deltas from.
The original inputs can be sourced from:
- Disk (if File Hash Cache matches disk state)
- Download (otherwise)
Process Files
Process all files to we need to generate
Each file has a 'state' which can be one of the following:
- Unprocessed
- Processed
For each delta to generate do the following.
Iterate over all of the files in NewVersion
.
-
If the file is in the
Original
mod folder and is the same, add it to the copy list.- Mark the file as Processed.
-
Else If the file is not the same, generate a patch for it.
- GetOrCreate patch for
original
->new
file.- If a patch was already made for
hash(original)
then reuse it. - By appending to
PatchTargetPaths
list for that specific patch.
- If a patch was already made for
- Link the generated patch to the
original
file hash, for all output files. - Mark the file as Processed.
- GetOrCreate patch for
Then iterate again, to cover remaining files:
- If the file is new (not in
Original
folder), add it to the extract list.- Mark the file as Processed.
Now all files should be processed.
Now you can generate the delta header and pack the delta files.
Delta Patch Format
There are many formats with which you can generate deltas.
Which do we use?
I've tried several formats:
- VCDiff: Good for general purpose.
- HDiffPatch: Extremely competitive, low memory footprint.
- xdelta3: General purpose, sacrifices ratio for speed.
- ZStandard Patching Engine: Very fast decompression.
After trying several solutions, I decided to use the ZStandard Patching Engine. HDiffPatch was generally the best for executable data and used little memory. On the other hand, the ZStandard Patching Engine was slightly better for binary data (e.g. 3D Model Files).
The main reason for going with ZStandard comes down to decompression speed. Decompression speed on my machine was hitting around 1GiB/s, approximately double that of HDiffPatch.
This also blends well with the fact that is simply the fact that the dependency is already in use
within the the .nx
archive container; therefore it brings no additional dependency.