Repository navigation
Releases: bechou0410/ZaDarkHelper
Release list
v26.5.005 — Health check 'Zalo launchable' không còn báo đỏ vĩnh viễn
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
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)
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...jpgflicker ~1s trong Finder rồi đổi thànhz776...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
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=runtimeLệ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 compilationcom.apple.security.cs.allow-unsigned-executable-memory— V8 codegencom.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.appVớ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:
- Cập nhật helper lên v26.5.002
- Mở helper → "Cài lại ZaDark"
- 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.appTest
26/26 tests pass. Resign + entitlements verified manually trên real Zalo 26.4.10:
codesign --verify --deep --strictPASSspctl -aACCEPTED- 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
⚠️ 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 trueTrade-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:
- Tải Zalo từ https://zalo.me/pc → drag vào /Applications, replace
- Terminal:
rm -f /Applications/Zalo.app/Contents/Resources/app.asar.bak - Cập nhật helper lên v26.5.001
- Mở helper → "Cài lại ZaDark" — helper xoá .bak nếu còn, patch fresh new Zalo, adhoc resign
- 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
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.activateFileViewerSelectingmở 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
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:
-
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)
- So sánh
-
Post-install —
adhocResign:- Chạy
codesign --force --deep --sign - --options=runtime /Applications/Zalo.app --options=runtimelà điểm critical: thiếu flag này, launchd refuse spawn dù codesign verify pass
- Chạy
-
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.appHoặ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)
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)
- Cài v26.4.006 (drag DMG vào Applications, replace bản cũ)
- Mở helper → Tuỳ chọn → bật toggle "Theo dõi save dialog (BETA, log-only)"
- 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
- Trở lại helper, toggle ON nếu chưa tự bật
- Mở Zalo, lưu 1 ảnh từ popup viewer (như user case bug
gen-{tag}-) - Mở popover helper → Nhật ký drawer → tìm dòng:
hoặc
[F5] Save dialog #1 detected ✓ MATCH: "gen-h-z776...jpg"[F5] Save dialog #1 detected (no prefix): "regular.jpg" - 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)
⚠️ 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:
-
Hook chạy quá sớm: IIFE chạy trong
bootstrap.jstrướcapp.whenReady().session.fromPartition(...)throw exception synchronously. Try/catch nuốt error → listener KHÔNG register. -
Sai API: Zalo's popup-viewer save dùng native IPC (
downloadWithMultiSrc→ main process →fs.writeFile), không gọiwebContents.downloadURL(). Electronwill-downloadevent 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.asarPatchEnabledmặ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_v1trongbootstrap.jscủa Zalo → tự động gỡ (chỉ làm khi Zalo không chạy) AsarPatcher+ZaloPatchInjectorcode 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
- Tải
ZaDarkHelper-26.4.005.dmg - Kéo vào
/Applications, replace bản cũ - Mở app — cleanup tự động chạy lần launch đầu
v26.4.004 — Asar patch injection (root-cause fix)
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 extracttrê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
- Tải
ZaDarkHelper-26.4.004.dmg - Drag vào
/Applications, replace bản cũ - Mở app — toggle mặc định ON
- Patch sẽ áp ngay khi
zadark installchạ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.bakcủa ZaDark vẫn còn nguyên (helper không touch backup) - Có thể tắt anytime — toggle OFF + reinstall ZaDark