Skip to content

Version 3 Rewriting - #11

Open
neon-nyan wants to merge 64 commits into
mainfrom
v3
Open

Version 3 Rewriting#11
neon-nyan wants to merge 64 commits into
mainfrom
v3

Conversation

@neon-nyan

@neon-nyan neon-nyan commented Aug 23, 2026

Copy link
Copy Markdown
Member

TODO: Description

Benchmark Result

Test: Single file, LZMA2 compressed

Method Mean Error StdDev Ratio RatioSD Gen0 Gen1 Gen2 Allocated Alloc Ratio
v2 19.46 ms 18.01 ms 0.987 ms 1.00 0.06 - - - 60.43 MB 1.00
v2+OldFastBuffer 57.55 ms 49.20 ms 2.697 ms 2.96 0.17 - - - 34.4 MB 0.57
v3 16.92 ms 18.52 ms 1.015 ms 0.87 0.06 468.7500 468.7500 468.7500 4.58 MB 0.08

Test: Directory, LZMA2 compressed

Method Mean Error StdDev Ratio Gen0 Gen1 Gen2 Allocated Alloc Ratio
v2 7.028 s 0.3049 s 0.0167 s 1.00 - - - 937.31 MB 1.00
v2+OldFastBuffer 7.176 s 0.2930 s 0.0161 s 1.02 - - - 692.19 MB 0.74
v3 4.639 s 0.3283 s 0.0180 s 0.66 3000.0000 2000.0000 2000.0000 136.46 MB 0.15

Test: Single file, LZMA2 compressed - Native (Result: 25 ms)

old : "G:\UnityPlayer-SEA.dll"
diff: "G:\UnityPlayer.lzma2.diff"
out : "G:\UnityPlayer-CN-native.dll"
  input oldDataSize: 30144448
       diffDataSize: 39523
       diffDataType: HDiff
  saved oldDataSize: 30144448
  saved newDataSize: 30144408
       compressType: "lzma2" (need decompress 4)

  patch progress: [==============================]  100.0%
  patch ok!

hpatchz time: 0.025 s

Test: Directory, LZMA compressed - Native (Result: 4.906 s)

old  dir: "G:\Hi3SEA\"
diffFile: "G:\Hi3SEAtoCNExecOnly.lzma2.diff"
out  dir: "G:\Hi3CN-native\"
         diffDataSize: 107925175
         diffDataType: DirHDiff
         checksumType: "fadler64"
         oldPathIsDir: true
         newPathIsDir: true
       new path count: 196 (fileCount:178)
  copy from old count: 122 (copySize: 86831653)
   ref old file count: 56 (oldRefSize: 799499793)
   ref new file count: 56 (newRefSize: 927363046)
           oldSumSize: 886331446
           newSumSize: 1014194699
       diffDataType: HDiff (in DirHDiff)
  saved oldDataSize: 799499793
  saved newDataSize: 927363046
       compressType: "lzma2" (need decompress 4)

hpatchz run with checksum plugin: "fadler64" (checksumSets: new copy)

  patch progress: [==============================]  100.0%
  patch ok!

hpatchz dir patch time: 4.906 s

SharpHDiffPatch -> SharpHPatchZ
Attempt to implement a thread-safe Merged Stream Wrapper for Read/Write operations.

Warning: Most of the Code for the reader are vibe-coded. So, need to check it further.
+ Struct type adjustment inside DirectoryPatchMetadata
  + Use UnmanagedArray<T> for some input-output ref lists instead of a direct array pointer
+ Fix CreateUnmanagedInt64List and CreateUnmanagedInt64As32List reading on backed numbers
+ Add extern functions:
    - shpz_get_last_errorA
    - shpz_get_last_errorW
    - shpz_patch_from_filepath
    - shpz_patch_from_FILE
+ Make some string arguments in extern functions auto-detect the encoding of the string
    - shpz_read_header_signature_string
    - shpz_init_from_filepath
    - shpz_patch_from_filepath
+ Make the ExceptionHelper throw functions returns the Exception instead of throwing within
+ Add implicit cast for Utf16UnmanagedString and NativeStringW
+ Remove unnecessary type size check on UnmanagedArray<T>.GetSpan()
+ Use numbered error code on ExceptionHelper instead of flags
+ Add callback allocation check on ProgressCallback to avoit ExecutionEngineException due to attempt on calling null callback.
Use .NET Framework 4.7.2 for testing the lowest version of .NET to be supported
Comment on lines +71 to +75
#if NET6_0_OR_GREATER
copyOverTcs.SetCanceled(token);
#else
copyOverTcs.SetCanceled();
#endif

This comment was marked as outdated.

Comment thread SharpHPatchZ/Extension/StreamExtension.cs
Comment thread SharpHPatchZ/Patch/HDiff13DerivedPatcher.CorePatcher.cs
+ Reenable PrefetchedReadStream for Zstd
Comment thread SharpHPatchZ/IO/Reader/RandomMergedStreamWrapper.cs
Comment thread SharpHPatchZ/IO/Compression/Lzma/LzmaInputStream.cs
Comment thread SharpHPatchZ/Patch/HDiff13DerivedPatcher.cs Outdated
@socket-security

socket-security Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedmicrosoft.netframework.referenceassemblies@​1.0.310010090100100

View full report

@socket-security

socket-security Bot commented Aug 24, 2026

Copy link
Copy Markdown

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

Comment thread SharpHPatchZ/IO/Reader/DecompressionTransitionStream.cs
+ HPatch.TryGetHDiff13PatchMetadata (Extern: shpz_util_get_hdiff13_patch_metadata)
+ HPatch.TryGetHDiff19DirectoryPatchMetadata (Extern: shpz_util_get_hdiff19_patch_metadata)
Comment thread SharpHPatchZ/HPatch.UnmanagedExtern.cs Outdated
catch (OperationCanceledException) when (token.IsCancellationRequested)
{
#if NET6_0_OR_GREATER
copyOverTcs.SetCanceled(token);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The corePatcherThread cancellation handler signals the wrong TaskCompletionSource, copyOverTcs instead of corePatcherTcs, causing a hang or an unhandled InvalidOperationException during cancellation.
Severity: HIGH

Suggested Fix

In the catch (OperationCanceledException) block for corePatcherThread in HDiff13DerivedPatcher.cs, change the calls from copyOverTcs.SetCanceled() to corePatcherTcs.SetCanceled(). This ensures the correct task is marked as canceled.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: SharpHPatchZ/Patch/HDiff13DerivedPatcher.cs#L80

Potential issue: In the `corePatcherThread`, the `catch` block for
`OperationCanceledException` incorrectly signals `copyOverTcs.SetCanceled()` instead of
`corePatcherTcs.SetCanceled()`. This leaves the `corePatcherTcs` task incomplete. As a
result, the main thread, waiting on `Task.WhenAll`, will hang. If the `copyOverThread`
has already completed its task, attempting to cancel the already-completed `copyOverTcs`
will throw an `InvalidOperationException`, leading to an unhandled exception that
terminates the process.

Also affects:

  • SharpHPatchZ/Patch/HDiff13DerivedPatcher.cs:83~83


public void Dispose()
{
_lifetimeLock.EnterWriteLock();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The Dispose() method in RandomMergedStreamWrapper is not idempotent. A second call will throw an ObjectDisposedException because it tries to use a lock that was disposed on the first call.
Severity: MEDIUM

Suggested Fix

Move the idempotency check if (Interlocked.Exchange(ref _disposed, 1) == 1) return; to be the first statement in the Dispose() method, before the _lifetimeLock.EnterWriteLock() call. This will ensure subsequent calls exit immediately without causing an exception.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: SharpHPatchZ/IO/Reader/RandomMergedStreamWrapper.cs#L141

Potential issue: The `Dispose()` method in `RandomMergedStreamWrapper` is not
idempotent, violating the `IDisposable` contract. On the first call, it acquires a
`ReaderWriterLockSlim` and disposes of it in the `finally` block. If `Dispose()` is
called a second time on the same instance, it attempts to acquire the lock again via
`_lifetimeLock.EnterWriteLock()`. Since the lock has already been disposed, this call
throws an `ObjectDisposedException`. The idempotency check is placed after the lock
acquisition, so it is never reached on the second call.

- shpz_util_get_hdiff13_patch_metadata -> shpz_util_get_patch_metadata
- shpz_util_get_hdiff19_patch_metadata -> shpz_util_get_directory_patch_metadata
WARNING: Fully Vibe-coded. Further test is required

Parallel.For(0, count, new ParallelOptions
{
MaxDegreeOfParallelism = (int)options.ParallelThreads

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: CopySimilarFilesContext.RunCopy() passes options.ParallelThreads directly to Parallel.For, which crashes if the value is 0. This occurs when PatchOptions is default-initialized.
Severity: HIGH

Suggested Fix

In CopySimilarFilesContext.RunCopy(), validate options.ParallelThreads before passing it to Parallel.For. Reuse the logic from CorePatcher.GetWorkerCount() to safely convert a value of 0 to a valid thread count, like Environment.ProcessorCount.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: SharpHPatchZ/Patch/HDiff13DerivedPatcher.CopySimilarFilesContext.cs#L34

Potential issue: When a directory patch is initiated via the unmanaged API with `null`
options, a default `PatchOptions` object is created where `ParallelThreads` is 0. This
value is passed directly to `Parallel.For`'s `MaxDegreeOfParallelism` in
`CopySimilarFilesContext.RunCopy()`, causing an `ArgumentOutOfRangeException`. Unlike
`CorePatcher`, this code path lacks a guard to handle the zero value by converting it to
a valid number of threads, such as `Environment.ProcessorCount`. This leads to a crash
when patching directories that contain similar files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant