Skip to content

Releases: bechou0410/ZaDarkHelper

v26.5.005 — Health check 'Zalo launchable' không còn báo đỏ vĩnh viễn

Choose a tag to compare

@bechou0410 bechou0410 released this 02 May 08:15

Bug

Health check "Zalo codesign" luôn báo đỏ sau khi áp ZaDark, dù Zalo chạy bình thường. Lý do: codesign --verify whole bundle phát hiện:

file added: app.asar.bak    (file ZaDark backup tạo ra)
file modified: app.asar     (ZaDark patch byte → khác hash niêm phong)

Đây là expected behavior, không phải bug — VNG ký Zalo bằng cert của họ, niêm phong tất cả files trong Resources/. ZaDark phá vỡ niêm phong khi patch app.asar. Không thể restore mà không có cert VNG.

Fix

Đổi target verify: whole bundle → Electron Framework only.

dyld dùng Framework's signature để gate library load tại launch time. Nếu Framework valid → Zalo chạy được. App.asar/Resources seal không liên quan đến launch flow.

- args: ["--verify", ZaloVersionProbe.bundlePath]
+ args: ["--verify", bundle + "/Contents/Frameworks/Electron Framework.framework"]

Health check row đổi tên: "Zalo codesign" → "Zalo launchable" + subtitle giải thích là Framework codesign.

Behavior sau update

  • Sau "Cài lại ZaDark" → check "Zalo launchable" → ✓ (helper đã adhoc resign Frameworks via --deep với entitlements ở v26.5.002)
  • Nếu show ✗ → adhoc resign thực sự fail (Frameworks chưa adhoc-signed) → click "Cài lại ZaDark" lại

Test

30/30 pass. Không add test mới (verifyCodesign là wrapper shell, khó mock).

Tổng kết changes session này

Release Nội dung
v26.5.001 Always remove .bak before zadark install
v26.5.002 Inject entitlements khi adhoc resign
v26.5.003 Speed up rename ~8x
v26.5.004 Toggle thông báo rename, default OFF
v26.5.005 Health check 'launchable' check Frameworks thay vì whole bundle

v26.5.004 — Toggle thông báo rename + giải thích TCC

Choose a tag to compare

@bechou0410 bechou0410 released this 02 May 07:52

Thay đổi

1. Toggle bật/tắt thông báo đổi tên file (mặc định TẮT)

Tuỳ chọn → Tự động sửa tên file Zalo → Báo thông báo mỗi lần đổi tên

  • Tắt (mặc định): helper sửa tên file thầm lặng. User chỉ thấy tên đúng trong Finder.
  • Bật: banner notification + nút "Hoàn tác" + click vào để mở Finder tới file (như behavior v26.4.008).

Trong cả 2 mode, nhật ký phiên (Logs drawer) vẫn ghi đầy đủ — audit trail không mất.

2. Vấn đề App Management TCC bị mất quyền

Không fix được hẳn bằng code vì macOS TCC bind permission vào:

  • Designated Requirement (cert leaf SHA1)
  • cdhash (binary content SHA1)

Apple-signed apps (Developer ID notarized): TCC chỉ bind DR → updates an toàn.

Self-signed apps như helper: macOS conservatively bind cả DR + cdhash → mỗi build → cdhash khác → TCC re-prompt lại.

Đây là Apple's anti-malware design choice. Workaround duy nhất:

  • Pay $99/year cho Apple Developer Program + ký với Developer ID + notarize
  • Hoặc dùng existing OnboardingBanner + "Mở System Settings" deeplink (1-click recovery sẵn có)

Helper đã detect khi permission lost (qua probeAppManagementPermission lúc launch + sau mỗi action) → banner cam hiện ngay → user click 1 nút Mở System Settings → flip toggle → quay lại work.

Test

26/26 tests pass. Logic change tối thiểu (~25 LOC).

v26.5.003 — Sửa tên file Zalo nhanh hơn ~8x (1200ms → 150ms)

Choose a tag to compare

@bechou0410 bechou0410 released this 02 May 07:44

Cải tiến tốc độ FilenameFixer

Latency từ "Zalo save xong" → "helper rename file" giảm từ ~1200ms xuống ~150ms (~8x faster).

Trước vs sau

Stage v26.5.002 v26.5.003
FSEvents coalescing 500ms 50ms
Debounce scheduleScan 500ms 100ms
isFileStillWriting probe 200ms (sleep) 0ms (xoá)
Tổng worst-case ~1200ms ~150ms

Thay đổi cụ thể

1. FSEvents latency: 500ms → 50ms

FSEventStreamCreate argument latency điều chỉnh xuống 50ms. Combined với kFSEventStreamCreateFlagNoDefer (event đầu tiên fire ngay), helper react gần như tức thì khi Zalo write xong file.

2. Debounce: 500ms → 100ms

Vẫn group được multi-chunk writes của Zalo thành 1 lần processFolder, nhưng không thêm delay đáng kể.

3. Bỏ isFileStillWriting (200ms sleep + 2 stat calls)

POSIX rename(2) chỉ swap directory entry — open file descriptor của Zalo tiếp tục write vào cùng inode → bytes đến đúng file (chỉ là tên mới). Safe to rename mid-write trên macOS. Defensive sleep 200ms không cần thiết.

Cảm nhận thực tế

  • Trước: save ảnh từ Zalo → gen-h-z776...jpg flicker ~1s trong Finder rồi đổi thành z776...jpg
  • Sau: flicker dưới 200ms — gần như không thấy tiền tố gen-…

Test

26/26 tests pass. Logic change chỉ trong DownloadFolderWatcher.swift — pure logic của FilenameFixer không đổi (vẫn safe roundtrip qua AsarPatcherTests).

Risk note

Với rename ngay khi FSEvents fire, có thể thỉnh thoảng rename mid-write. Đã verify POSIX guarantee: file output cuối cùng vẫn đúng + đủ data. Test với Zalo's actual save → user nên thấy file save thành công với tên đã sửa.

v26.5.002 — Inject entitlements khi adhoc resign Zalo

Choose a tag to compare

@bechou0410 bechou0410 released this 02 May 05:26

Fix gốc rễ cho crash "Library not loaded"

Bug

Sau khi helper "Cài lại ZaDark", Zalo crash ngay khi mở với dialog "Kiểm tra với nhà phát triển...". Crash report: Library not loaded: @rpath/Electron Framework... different Team IDs.

Root cause

Helper v26.4.007–v26.5.001 dùng:

codesign --force --deep --sign - --options=runtime

Lệnh này strip toàn bộ entitlements mà VNG đã ký gốc. Các entitlements quan trọng:

  • com.apple.security.cs.allow-jit — V8 JIT compilation
  • com.apple.security.cs.allow-unsigned-executable-memory — V8 codegen
  • com.apple.security.cs.disable-library-validation — bỏ qua Team ID enforcement

Khi entitlements bị strip + hardened-runtime vẫn enforce → library validation fire → load Electron Framework fail vì Team ID không match (do --deep adhoc resign không đồng nhất 100% qua nested frameworks).

Fix

Resources/zalo-entitlements.plist (bundled với helper) chứa exactly các entitlements VNG đã sign. ZaloRepairer.adhocResign giờ pass --entitlements <path>:

codesign --force --deep --sign - --options=runtime \
  --entitlements /tmp/zadark-helper-zalo-entitlements.plist \
  /Applications/Zalo.app

Với disable-library-validation enabled, mismatch Team IDs giữa main + nested frameworks không còn block dyld → Zalo launch bình thường.

Recovery cho user

Không cần reinstall Zalo (nếu Zalo hiện đang chạy được). Chỉ cần:

  1. Cập nhật helper lên v26.5.002
  2. Mở helper → "Cài lại ZaDark"
  3. Lần này helper sẽ inject entitlements khi resign → Zalo mở bình thường sau patch

Nếu Zalo hiện đang ở trạng thái crash (do v26.4.007–v26.5.001 đã thử resign sai trước):

# Manual recovery 1 lệnh — fix codesign mà không cần reinstall Zalo:
cat > /tmp/zentitlements.plist <<'PLIST'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>com.apple.security.cs.allow-jit</key><true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key><true/>
<key>com.apple.security.cs.disable-library-validation</key><true/>
<key>com.apple.security.device.audio-input</key><true/>
<key>com.apple.security.device.camera</key><true/>
</dict></plist>
PLIST
codesign --force --deep --sign - --options=runtime --entitlements /tmp/zentitlements.plist /Applications/Zalo.app

Test

26/26 tests pass. Resign + entitlements verified manually trên real Zalo 26.4.10:

  • codesign --verify --deep --strict PASS
  • spctl -a ACCEPTED
  • Zalo launch successfully sau patch

Hậu cố các bug session này

Release Issue Resolution
v26.4.004 (F4 asar JS hook) Wrong API target Reverted v26.4.005
v26.4.006 (F5 AX watcher) No save dialog to hook Deprecated v26.4.007
v26.4.007 (F6 stale .bak) Logic flaw Fixed v26.5.001
v26.5.001 (always remove .bak) Resign strip entitlements Fixed v26.5.002
v26.5.002 ✅ Final fix

v26.5.001 — Hotfix: prevent Zalo crash from stale .bak loop

Choose a tag to compare

@bechou0410 bechou0410 released this 02 May 05:06

⚠️ Hotfix quan trọng

Fix cho lỗi Zalo crash với dialog "Kiểm tra với nhà phát triển để đảm bảo Zalo hoạt động..." sau khi helper auto re-patch.

Bug ở v26.4.007–v26.4.008

removeStaleBackupIfNeeded so sánh lastPatchedZaloBuild với current bundle version để quyết định xoá .bak. Logic này sai về căn bản:

1. Helper installs ZaDark → .bak captures Zalo X content
2. Squirrel updates Zalo → X+1 (replaces .asar, .bak vẫn X)
3. Helper detects drift → re-patch
4. Logic check: lastPatchedBuild = X+1 (helper update sau bước 1), 
   currentBuild = X+1 → MATCH → skip remove .bak
5. ZaDark CLI thấy .bak (X stale) → rollback → .asar = X (stale)
6. Patches X → repacks → .asar = X + ZaDark (OLD content)
7. Bundle = OLD app.asar + NEW Frameworks → Zalo crash on launch
   ("Library not loaded: @rpath/Electron Framework", Team ID mismatch)

lastPatchedBuild được update sau MỖI install, kể cả khi install chỉ rollback từ stale .bak. So comparison luôn trivially match → loop infinite.

Fix v26.5.001

Bỏ điều kiện build comparison. Luôn xoá .bak trước mỗi zadark install (sau lần install đầu tiên — first install vẫn keep .bak để ZaDark uninstall còn restore point).

guard lastPatchedBuild != nil else { return false }  // first install — keep
try? fileManager.removeItem(atPath: bakPath)         // always remove after
return true

Trade-off documented: zadark uninstall không restore được Zalo nguyên thuỷ. Acceptable vì user's recovery cho trường hợp stale-.bak corruption là "reinstall Zalo từ VNG" anyway.

⚠️ Recovery cho user đang bị crash

Helper v26.5.001 chỉ ngăn lỗi mới — KHÔNG tự fix bundle đã corrupt. User bị crash phải làm:

  1. Tải Zalo từ https://zalo.me/pc → drag vào /Applications, replace
  2. Terminal: rm -f /Applications/Zalo.app/Contents/Resources/app.asar.bak
  3. Cập nhật helper lên v26.5.001
  4. Mở helper → "Cài lại ZaDark" — helper xoá .bak nếu còn, patch fresh new Zalo, adhoc resign
  5. Mở Zalo bình thường

Bước 1 (reinstall Zalo) BẮT BUỘC vì current bundle có OLD app.asar content + NEW Frameworks → crash khi launch.

Versioning bump

Phiên bản nhảy từ v26.4.x → v26.5.001 vì sang tháng 5 (per YY.M.NNN scheme). Không phải breaking change.

Tests

26/26 tests pass. Logic change tối thiểu (~10 LOC).

v26.4.008 — Click rename toast → mở Finder tới file

Choose a tag to compare

@bechou0410 bechou0410 released this 29 Apr 02:50

Cải tiến UX

Click thông báo "Đã sửa tên tệp Zalo" → mở Finder tới file

Trước v26.4.008, click vào body của notification rename không làm gì. Chỉ nút "Hoàn tác" hoạt động.

Giờ:

  • Click body notification → Finder mở thư mục chứa file + highlight file
  • Nút "Hoàn tác" vẫn như cũ — đổi tên ngược lại

Behavior chi tiết:

  • File còn tồn tại → NSWorkspace.activateFileViewerSelecting mở Finder window với file selected
  • File đã di chuyển → fallback mở thư mục cha

Implementation

// AppDelegate routes notification taps:
switch action {
case UNNotificationDefaultActionIdentifier:  // body tap
    appState.revealRenamedFile(currentPath: ...)
case NotificationService.undoActionID:       // "Hoàn tác" button
    appState.undoRename(...)
}

userInfo[currentPath] đã có sẵn từ F1 (v26.4.002), không cần thay đổi NotificationService.

Test

26/26 tests pass. Tap body interaction cần manual test trên macOS notification banner.

Cài đặt

Helper auto-update banner sẽ hiện hoặc tải DMG manual.

v26.4.007 — Auto-recover sau Zalo Squirrel update

Choose a tag to compare

@bechou0410 bechou0410 released this 29 Apr 02:44

Hotfix tự động cho lỗi "Zalo bị hỏng"

Vấn đề ở v26.4.005/v26.4.006

Khi Zalo's Squirrel auto-updater download bản mới giữa 2 lần helper chạy → app.asar.bak (do ZaDark tạo lúc cài lần đầu) thành stale (Zalo cũ). Lần sau helper auto re-patch → ZaDark CLI rollback .asar từ .bak cũ → kết quả: Zalo content cũ + Zalo bundle code signature mới → macOS Gatekeeper báo "Zalo bị hỏng và không thể mở được. Bạn nên di chuyển ứng dụng vào Thùng rác."

Fix ở v26.4.007

Core/ZaloRepairer.swift (~80 LOC) auto-recover quanh mỗi zadark install:

  1. Pre-install — removeStaleBackupIfNeeded:

    • So sánh lastPatchedZaloBuild (helper ghi nhớ) vs current Zalo build
    • Mismatch → Zalo đã update qua Squirrel → xoá .bak để ZaDark patch trực tiếp Zalo mới (không rollback)
  2. Post-install — adhocResign:

    • Chạy codesign --force --deep --sign - --options=runtime /Applications/Zalo.app
    • --options=runtime là điểm critical: thiếu flag này, launchd refuse spawn dù codesign verify pass
  3. Health check thêm row "Zalo codesign" để user verify bundle launchable.

Cleanup F5 (BETA save dialog watcher)

Phase 1 verification ở v26.4.006 đã reveal: Zalo's popup-viewer save flow bypass NSSavePanel hoàn toàn (auto-save thẳng vào Downloads với cache filename) → AX-based interception không bao giờ fire → approach fundamentally flawed.

→ Toggle ẩn UI. User upgrade từ v26.4.006 → toggle force OFF on launch.

FilenameFixer là solution chính thức

Sau 4 attempts (F4 asar inject, F5 AX dialog, F6 repair) — rename-after-save (F1, đã ship v26.4.003) vẫn là cách duy nhất hoạt động cho gen-{tag}-. Helper rename file < 2s sau khi save → user thấy flicker ngắn rồi tên đúng.

Behavior khi user upgrade

State Action
Đang dùng v26.4.006 với BETA toggle ON Toggle force OFF, log "Đã tắt save-dialog watcher (deprecated)"
Zalo bị "hỏng" do stale .bak Click "Cài lại ZaDark" → tự động xoá .bak + adhoc resign → mở Zalo bình thường
Zalo còn nguyên No-op, helper hoạt động như cũ + thêm an toàn cho lần Zalo update tiếp

Manual recovery nếu Zalo đang "bị hỏng" trước khi cập nhật v26.4.007

Mở Terminal:

rm /Applications/Zalo.app/Contents/Resources/app.asar.bak
codesign --force --deep --sign - --options=runtime /Applications/Zalo.app

Hoặc đơn giản: cập nhật v26.4.007 → "Cài lại ZaDark" → helper tự handle.

Restart Mac sau recovery để clear Finder Gatekeeper cache.

Tests

26/26 unit tests pass. ZaloRepairer được test qua ReinstallOrchestrator integration path (full re-patch flow trên Zalo thật).

v26.4.006 — Save dialog watcher (Phase 1, BETA log-only)

Choose a tag to compare

@bechou0410 bechou0410 released this 27 Apr 08:09

Phase 1 verification spike

External patch approach mới — thay vì modify Zalo binary (F4 đã fail), helper dùng macOS Accessibility API quan sát save dialog NSSavePanel khi Zalo gọi dialog.showSaveDialog().

Phase 1 (release này) chỉ LOG — không sửa gì. Mục tiêu: verify walker tìm đúng filename text field trước khi enable rewrite ở Phase 2.

Cách test giúp tôi (5 phút)

  1. Cài v26.4.006 (drag DMG vào Applications, replace bản cũ)
  2. Mở helper → Tuỳ chọn → bật toggle "Theo dõi save dialog (BETA, log-only)"
  3. macOS sẽ prompt "Cho phép ZaDarkHelper điều khiển máy này" → click "Mở System Settings" → bật ZaDarkHelper trong Privacy & Security → Accessibility
  4. Trở lại helper, toggle ON nếu chưa tự bật
  5. Mở Zalo, lưu 1 ảnh từ popup viewer (như user case bug gen-{tag}-)
  6. Mở popover helper → Nhật ký drawer → tìm dòng:
    [F5] Save dialog #1 detected ✓ MATCH: "gen-h-z776...jpg"
    
    hoặc
    [F5] Save dialog #1 detected (no prefix): "regular.jpg"
    
  7. Báo lại tôi: log có hiển thị filename đúng không?

3 trường hợp sẽ xảy ra

Trường hợp Ý nghĩa Hành động
Log có entry với filename đúng ✅ Phase 1 PASS Tôi ship Phase 2 (auto-rewrite) ở v26.4.007
Log không có entry nào AX walker miss field, hoặc Zalo dùng custom UI Tôi điều chỉnh heuristic + retry
Helper crash hoặc Zalo crash Bug AX implementation Tôi rollback ngay (toggle OFF cleanup)

Behavior tổng quát

  • Toggle OFF (mặc định) → behavior gốc, FilenameFixer rename sau save (như v26.4.005)
  • Toggle ON + AX granted → bắt đầu observe Zalo, log mỗi lần save dialog xuất hiện
  • AX permission denied → orange banner trong PreferencesView + link System Settings, không crash

Bài học từ v26.4.004

Lần trước tôi ship rewrite ngay → broke Zalo. Lần này:

  • Phase 1 chỉ log (zero-risk — đọc UI, không sửa gì)
  • Wait user verify trước khi ship rewrite
  • FilenameFixer vẫn là fallback chính

26/26 tests pass. AX permission là quyền duy nhất mới — user có thể từ chối nếu không thoải mái.

v26.4.005 — Hotfix: revert v26.4.004 asar patch (broken)

Choose a tag to compare

@bechou0410 bechou0410 released this 27 Apr 07:56

⚠️ Quan trọng — hotfix cho v26.4.004

Vấn đề ở v26.4.004

User báo: cài đặt lại ZaDark khi đang dùng v26.4.004 → gây lỗi mở Zalo lần đầu, lưu ảnh vẫn bị gen-{tag}- (FilenameFixer rename sau).

Phân tích root cause:

  1. Hook chạy quá sớm: IIFE chạy trong bootstrap.js trước app.whenReady(). session.fromPartition(...) throw exception synchronously. Try/catch nuốt error → listener KHÔNG register.

  2. Sai API: Zalo's popup-viewer save dùng native IPC (downloadWithMultiSrc → main process → fs.writeFile), không gọi webContents.downloadURL(). Electron will-download event không fire cho code path này → kể cả register thành công cũng vô dụng.

→ Approach fundamentally wrong cho Zalo's architecture.

Hotfix v26.4.005

  • Preferences.asarPatchEnabled mặc định OFF
  • Toggle UI ẩn (deprecated)
  • Auto-cleanup: khi launch helper v26.4.005, nếu phát hiện marker ZADARK_HELPER_FILENAME_FIX_v1 trong bootstrap.js của Zalo → tự động gỡ (chỉ làm khi Zalo không chạy)
  • AsarPatcher + ZaloPatchInjector code retain để tham khảo nhưng không gọi nữa

Behavior sau khi update

  • User upgrade v26.4.004 → v26.4.005:
    • Lần launch đầu: helper detect patch leftover → silently remove (log: "Đã gỡ patch app.asar v26.4.004 (deprecated)")
    • Zalo trở về clean state (giống ZaDark stock)
  • Save ảnh từ Zalo → vẫn gen-{tag}- ban đầu, FilenameFixer rename trong < 2s (tên đúng cuối cùng — như v26.4.003)

FilenameFixer vẫn là solution duy nhất hoạt động

26/26 tests pass.

Xin lỗi

Tôi assert sai khi recommend approach asar patch — đã verify hook chạy đúng trong unit test với fake asar nhưng KHÔNG verify hook thực sự fire trong Zalo runtime. Manual test với real Zalo lẽ ra phải làm trước khi ship. Sẽ cẩn trọng hơn.

Cài đặt

  1. Tải ZaDarkHelper-26.4.005.dmg
  2. Kéo vào /Applications, replace bản cũ
  3. Mở app — cleanup tự động chạy lần launch đầu

v26.4.004 — Asar patch injection (root-cause fix)

Choose a tag to compare

@bechou0410 bechou0410 released this 27 Apr 07:40

Fix tận gốc cho gen-{tag}- filename

Phương pháp mới

Thay vì rename file SAU khi save (FilenameFixer ở v26.4.003), giờ helper inject Electron will-download hook trực tiếp vào app.asar/bootstrap.js:

session.fromPartition('persist:zalo').on('will-download', (event, item) => {
  const original = item.getFilename()
  const fixed = original.replace(/^gen-[a-z0-9]{1,4}-/i, '')
  if (fixed !== original) {
    item.setSaveDialogOptions({ defaultPath: fixed })
  }
})

→ Save dialog hiển thị tên đúng z776…jpg ngay từ đầu, không cần rename sau.

Cách hoạt động

  • Default ON (toggle "Patch Zalo trực tiếp (advanced)" trong Tuỳ chọn)
  • Áp tự động sau mỗi zadark install (bao gồm auto re-patch khi Zalo update)
  • Idempotent: detect marker → skip inject lại
  • Reversible: tắt toggle → next install patch sẽ gỡ hook
  • Status icon ở Tuỳ chọn (lá chắn ✓ khi hook active)

FilenameFixer vẫn giữ làm safety net

  • File đã save trước khi patch áp → vẫn rename được qua "Quét + sửa file cũ"
  • Race condition (save trong lúc Zalo đang được patch) → backup catch

Implementation

Viết AsarPatcher thuần Swift:

  • Parse asar v0 pickle header
  • In-place modify FIRST file (bootstrap.js, offset 0)
  • Recompute SHA256 integrity + shift subsequent file offsets
  • Avoids 50MB extract+repack (chỉ rewrite ~3.5MB header + 1.3KB modified bootstrap.js + tail unchanged)

Test

  • 3 unit tests mới (offset shift, idempotent roundtrip, apply/remove)
  • Roundtrip kiểm chứng: npx @electron/asar extract trên patched real-Zalo asar produce file giống hệt original (chỉ khác bootstrap.js)
  • 26/26 tests pass total

Cài đặt

  1. Tải ZaDarkHelper-26.4.004.dmg
  2. Drag vào /Applications, replace bản cũ
  3. Mở app — toggle mặc định ON
  4. Patch sẽ áp ngay khi zadark install chạy (manual hoặc auto)

Note an toàn

  • Patch chỉ thêm 1 IIFE wrapped trong try/catch — fail-safe nếu Electron API thay đổi
  • Backup app.asar.bak của ZaDark vẫn còn nguyên (helper không touch backup)
  • Có thể tắt anytime — toggle OFF + reinstall ZaDark