Table of Contents

Namespace Archiver.Core.Services

Classes

AntivirusScanService

T-F146. Scans an archive's expanded contents for threats via AMSI — deliberately not an extension of IArchiveService/ITarService (see docs/DECISIONS.md's T-F146 entry): this never writes anything to a real destination, has no conflict/MOTW dimension, and adding a method to those interfaces would ripple through every hand-rolled test fake in the repo for a capability that isn't a variant of extraction. Never throws — every failure becomes an Inconclusive ThreatFinding.

ArchiveCreationRouter

Routes archive creation to IArchiveService (ZIP) or ITarService (tar-family) based on ArchiveOptions.Format. Unlike IExtractionRouter, this needs no per-path format detection — creation format is a single explicit choice for the whole operation.

ArchiveDownloadMark

Whether an archive carries the "downloaded from the internet" mark (its Zone.Identifier stream, T-F45) — what the App asks before it offers to leave the mark off (T-F360).

ArchiveFormatDetector

Detects archive format from magic bytes only — no extension reliance, matching this codebase's existing ZIP signature-sniffing convention (see ZipArchiveService.IsZipFile). Used by ExtractionRouter to decide which service (IArchiveService vs ITarService) should handle a given archive path.

ArchiveFormatPolicy

The one classifier every operation uses to decide which engine may open an archive (T-F261): Group Policy first (AllowedFormats/BlockedFormats, then DisableTarExtraction), then what the system's tar.exe can read. Extraction, testing, listing and scanning all route through it, so no operation can drift from what another would allow or refuse.

ArchiveFormatPolicy.Classification

A selection split by engine. ZipPaths also holds unrecognized paths, so the ZIP engine's own "not a recognized archive" handling reports them.

ArchiveFormatRegistryNames

Maps ArchiveFormat/ArchiveContainerFormat to the registry-string vocabulary used by GroupPolicyOptions.AllowedFormats/BlockedFormats (T-F51). The two enums don't line up 1:1 — e.g. creating ArchiveContainerFormat.TarGz is later detected on extraction as ArchiveFormat.GZip — so both map onto the same 9-name vocabulary rather than each having its own registry string set.

ArchiveListingRouter

Routes a single archive's ListEntriesAsync call to IArchiveService (ZIP) or ITarService (tar-family), based on ArchiveFormatDetector — same dispatch IExtractionRouter uses for extraction. A separate interface from IExtractionRouter because listing and extracting return different result shapes; one archive path in, one ArchiveListResult out (not a batch, unlike ExtractAsync — the archive browser always lists exactly one archive at a time). T-F250: Group Policy applies exactly as for extraction (ArchiveFormatPolicy) — a blocked format, or a tar-family format under DisableTarExtraction, is a failed result and no engine is called.

ArchiveNaming

Derives a base name (for a destination folder, or an auto-named archive) from an archive's file name. Path.GetFileNameWithoutExtension only strips the last extension, which is wrong for the compound extensions tar.exe itself produces (T-F103: "archive.tar.gz" must strip to "archive", not "archive.tar"). Kept in sync with ShellExtUtils.cpp's native equivalent.

EncryptionPasswordRule

T-F193: which passwords Pakko accepts when creating an AES-256 ZIP. Public so every frontend can refuse bad input inside its own prompt with localized text, while ArchiveAsync(ArchiveOptions, IProgress<ProgressReport>?, CancellationToken) enforces the same rule as the last line of defence. Reading (T-F189) accepts any password.

ExtractionRouter

Routes ExtractAsync calls to IArchiveService (ZIP) or ITarService (tar-family) per archive, based on ArchiveFormatDetector, and merges the results. Never throws — all errors are captured in ArchiveResult.Errors.

FileHashService

T-F128: computes CRC-32/SHA-256 for the Explorer context menu's "Хеш-суми" submenu. A single folder gets NanaZip-compatible combined DataSum (all file contents) and NamesSum (all file names+paths+contents) values, via Archiver.Core.IO.HashDigestAccumulator — the algorithm of 7-Zip's HashCalc.cpp, checked live against the vendored 7za h by FolderHashParityTests (T-F225). NamesSum includes one item per directory, the selected folder itself included, hashed with an all-zero digest (7-Zip resets it before every item), so the result does not depend on enumeration order. Remaining difference: symbolic links and junctions are skipped here (T-F251), where 7-Zip follows them.

Files are hashed in parallel (ForEachAsync<TSource>(IEnumerable<TSource>, ParallelOptions, Func<TSource, CancellationToken, ValueTask>), up to ProcessorCount at once) — safe specifically because Add(ReadOnlySpan<byte>) is commutative, so combining DataSum/NamesSum in whatever order files finish hashing produces the exact same result as combining them sequentially (already relied on for the recursion-safety argument above).

T-F128 follow-up: a single large CRC-32 file is also hashed in parallel — the across-files parallelism above gives no benefit to a folder containing one huge file (or to a lone large file passed to ComputeAsync(IReadOnlyList<string>, HashAlgorithmKind, IProgress<ProgressReport>?, CancellationToken) directly). Files at or above Archiver.Core.Services.FileHashService.ParallelCrc32MinFileBytes are split into independently-hashed chunks and folded back together with Combine(uint, uint, long) — safe for the same reason cross-file combining is: CRC-32 combining is associative/order-preserving as long as chunks are folded back in their original byte order (unlike DataSum/NamesSum, chunk order here does matter, so chunks are combined sequentially by index after all finish, not as they complete).

FolderHashSummary

Combined DataSum/NamesSum for a single recursively-hashed folder — see FileHashService's doc comment for what these mean and their NanaZip parity.

GroupPolicyService

Reads Pakko's Group Policy settings (T-F51) from HKLM\Software\Policies\Pakko. Both Archiver.App (via DI) and Archiver.Shell (no DI container) call Load() once at startup and thread the resulting GroupPolicyOptions into every consumer. Never throws — an absent key, an absent registry hive entirely, or a malformed value all fall back to today's shipped (unrestricted) behavior.

HashEntry

Per-file hash result. Error is set instead of Hash when the file couldn't be read, or when it was skipped (e.g. a folder in a multi-item selection).

HashResult

Result of a ComputeAsync(IReadOnlyList<string>, HashAlgorithmKind, IProgress<ProgressReport>?, CancellationToken) call.

LaunchArguments

The launch-argument string Archiver.Shell passes to Archiver.App through IApplicationActivationManager::ActivateApplication — the single owner of both sides of the format, so the producer and the consumer cannot drift apart. Replaced the pakko:// URI scheme (T-F232): a registered scheme could be launched by any web page or document link, while an activation argument can only come from a process already running on the machine. Shape: --browse|--extract|--archive <base64 of a UTF-8 JSON string array> — base64 keeps paths with spaces, quotes or a trailing backslash out of command-line quoting rules.

MessageTemplates

The English template of every MessageCode (T-F209) — the one place Core's user-visible English text is written. Frontends translate these templates; their placeholders ({0}, {1}) must stay the same.

PakkoServices

The one place a frontend without a DI container (Archiver.Shell, Archiver.CLI) gets its Core services from (T-F261), so every service is built with the same Group Policy — a hand-built composition root per command is how the CLI once shipped a policy-less listing. The tar.exe capability probe runs at most once per instance (T-F85), only when an operation meets a tar-family archive that policy allows (T-F350), and not at all when Group Policy disables tar.exe.

PreviewPolicy

T-F97: safe-preview-type allowlist for the Archive Browser's double-click-to-preview feature. Deliberately conservative — only formats with no known code/macro/script execution path via their typical OS default handler. See SECURITY.md.

ProgressSpeedSampler

EMA-smoothed transfer-speed sampler shared between Archiver.App's status line and Archiver.Shell's progress dialog (T-F142) — extracted from what was private, untested arithmetic inline in Archiver.App's MainViewModel.UpdateOperationStatus. Public (not internal), matching Archiver.Core.IO.Crc32's own precedent for a dependency-free algorithm needed by multiple frontends. Sampling only: byte/speed string formatting (localization-adjacent in the App, plain text in the Shell dialog) and ETA (derived from Percent, not bytes — works already, isn't shared) both stay separate per frontend, not folded in here. Callers gate on TotalBytes > 0 themselves before calling Sample(long, DateTime) — a report with no real byte total carries no speed signal at all, and deciding to skip it is the caller's job, not this sampler's, so it stays a pure function of (bytes, time). Construct a fresh instance per operation (Archive/Extract) rather than reusing one across operations — there is no Reset() by design, so a stale carried-over speed from reusing an instance across two operations is not possible.

RecoveryDataLookup

What a frontend asks about PAR2 recovery data before any test runs (T-F275 step 3c). The two lookups that read the disk are reached through IRecoveryService (step 4c), which holds the policy; only the name check is public here.

RecoveryService

PAR2 recovery data next to an archive: what is there, and repair from it (T-F275 step 4). The checks themselves are TestAsync(IReadOnlyList<string>, IProgress<ProgressReport>?, Func<PasswordPromptInfo, Task<PasswordDecision>>?, bool, CancellationToken).

StickyCallback<TInfo, TDecision>

Widens an "apply to all/remaining" answer beyond the single Core call that asked for it. Core's own ConflictResolver/PasswordResolver already remember such an answer, but only for the lifetime of ONE ExtractAsync/TestAsync call. A frontend that makes several calls for one user action — Archiver.Shell's per-archive loop (T-F155/T-F192), Archiver.CLI's zip/tar split through ExtractionRouter (T-F160) — wraps its prompt in one instance of this, created once per user action, so the answer spans every call. Replaced two hand-written copies of this pattern.

TarSandboxedService

Extracts tar-family archives (tar, tar.gz, tar.bz2, tar.xz, tar.zst, tar.lzma, 7z, rar) via the system's tar.exe, launched inside a Windows AppContainer (no network capability) with a Job Object (ActiveProcessLimit = 1, RAM/CPU limits) — see TASKS.md's T-F52 entry for the full design and DECISIONS.md for the empirical trail. Never throws to callers — all errors are captured in ArchiveResult.Errors. Replaces the deleted TarProcessService.

TarVersionParser

Parses tar.exe --version output into TarCapabilities. Extracted into a separate class so T-F48 can unit-test format detection without launching a process.

Win32RegistryReader

Minimal seam over the Windows registry for GroupPolicyService (T-F51) — kept deliberately small so tests use a hand-rolled fake instead of a mocking library (none is used in this repo). Both members return null for an absent key/value or a value of the wrong registry type; implementations never throw.

ZipArchiveService

ZIP archive service using System.IO.Compression. Never throws to callers — all errors are captured in ArchiveResult.Errors.

Enums

EncryptionPasswordProblem

Why Check(string) refused a password for a new encrypted ZIP.

LaunchOperation

What Archiver.App should do with the files handed to it by Archiver.Shell (T-F232).