View Single Post
  #10  
Old Yesterday, 04:10
BLACKFIRE69's Avatar
BLACKFIRE69 BLACKFIRE69 is offline
Registered User
 
Join Date: Mar 2019
Location: In the Hell
Posts: 701
Thanks: 486
Thanked 2,610 Times in 574 Posts
BLACKFIRE69 is on a distinguished road
Lightbulb ISApexEx Update

ISApexEx Update
Unified Archive Extraction & Patching API for Inno Setup
by BLACKFIRE69

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━

▌ v0.1 Beta 3 (Build 6A406400)
Compared to: v0.1 Beta 2 (Build 6A3DC100)

✦ Added
  • Archive testing (no extraction)ISApexExTestDisk(n) and ISApexExTestAll test and CRC-check every member while writing nothing to disk. Real archives (7z / FreeArc / RAR) only; patch and zip-create jobs are skipped. Returns False on the first corrupt archive (fires APEX_EVENT_ERROR).
  • Existing-file overwrite policyISApexExSetOverwritePolicy(n, policy): APEX_OVERWRITE (default) / APEX_SKIP / APEX_RENAME. Skip and overwrite work on all engines; rename ('name (N).ext') is honoured by 7z and RAR. FreeArc has no per-file rename, so rename falls back to overwrite there.
  • Selective extractionISApexExSetExtractFilter(n, include, exclude): per-disk include/exclude masks using the scanner syntax — pipe-separated globs ('*.dll|data\*'), '@listfile', or regex wrapped as $"pattern" (e.g. '$"^setup.*\.exe$"' — the $ and quotes are required and protect any '|' inside). Honoured per-member by 7z and RAR; FreeArc maps globs to native filespecs / -x (the $"..." regex and @list forms are ignored for FreeArc).
  • Interactive password promptISApexExSetPasswordCallback(cb) for a custom prompt, plus a built-in fallback password box. An encrypted archive with no / empty / wrong stored password now prompts the user instead of failing. Covers all three real engines — 7z, RAR, and FreeArc. (Triggers only for genuinely encrypted archives.)
  • ZIP creation from a folderISApexExAddZip(srcDir, outZip, include, exclude, method, level, password, deleteSource) builds a .zip with a selectable compression method (APEX_ZIP_STORE / DEFLATE / DEFLATE64 / BZIP2 / LZMA / PPMD) and level (0=store .. 9=ultra, default 5), plus optional AES-256 (non-empty password). deleteSource=True removes the source folder (incl. subdirs) after a successful zip — never if the job fails or is cancelled. Registered like the patch helpers and runs in the unified extraction loop with continuous progress; the folder is walked at extraction time (deferred). New constants APEX_ARC_ZIPCREATE = 5 and APEX_ZIP_* methods.
  • New example scripts — one per feature, for individual testing: ISApexEx_Example_Test.iss, ISApexEx_Example_Overwrite.iss, ISApexEx_Example_Filter.iss, ISApexEx_Example_Password.iss, ISApexEx_Example_Zip.iss, ISApexEx_Example_Verify.iss.
  • Batch signature verificationISApexExVerifyFolder(folder, recursive, callback) walks a folder, finds every <file>.sig and verifies the file beside it in one call (instead of many ISApexExVerifyFile calls). Returns the number of files that failed (0 = all authentic); a missing signed file is caught as VFY_NO_FILE. The optional callback reports per-file status (path, index/total, VFY_* code, running fail count) and can cancel (VFY_CANCELLED). New VFY_CANCELLED = -10.
  • Job counterISApexExGetDiskCount returns how many jobs are registered (archives, patches and zip-create jobs). The DLL is now the single source of truth, so scripts no longer need to track their own disk counter — just loop 1..ISApexExGetDiskCount.
  • Encrypted password keystoreISApexExOpenKeyStore(file) / ISApexExCloseKeyStore. Keep archive (and .ekey) passwords out of the installer script entirely — store them in an encrypted (.keydb) keystore file and reference each by name. Once a keystore is open, a password argument of the form '@key:<name>' passed to ISApexExAddDisks / AddDisksEx / AddZip / SetVerifyKey is replaced with the matching stored password; the clear-text never appears in the .iss or the compiled Setup.exe. <name> is a FIXED reserved name, validated per use-site: 'disk' (archive password shared by all archives), 'disk2'/'disk3'/... (per-archive overrides, only when one differs), and 'ekey' (the ISApexExSetVerifyKey password). So 10 archives sharing one password need a single 'disk' entry. A name invalid for the site, or absent, resolves to an empty password (and is logged), so a typo or misuse cannot fall back to literal text. Companion console tool ApexKeyTool builds keystores (guided 'gen', plus add / remove / list); it is deliberately write-only for passwords — 'list' shows entry NAMES only and there is no command to print stored passwords (the keystore's key is embedded in the shipped tool, so a dump command would expose every keystore).
  • Full FreeArc error-message coverage — a failed FreeArc job now reports a specific, localizable reason (unknown method, output block too small, read/write error, not enough memory, unsupported feature, corrupted data, checksum mismatch, damaged headers, invalid password, ...) instead of a single generic "decompression error". New ERROR_ARC_* keys in English.ini / Russian.ini.
  • ZIP-create progress label — creating a zip now shows a clear status ("<name>.zip - Creating ZIP archive...", key STATUS_ZIPPING) and the archive-test (ISApexExTestDisk / TestAll) now reports per-file progress + count like a real extraction.

✦ Changed
  • The selective-extraction mask engine is now shared by both selective extraction and zip creation — one consistent include/exclude syntax.
  • The 7z engine gained archive-creation support, used by ISApexExAddZip (requires the full 7z library that ISApexEx ships).
  • FreeArc extraction now honours the per-disk overwrite policy when it would otherwise prompt to overwrite (overwrite-all / skip-all).
  • The 7z engine now opens real .zip archives (the format is detected from the file itself). Previously a .zip could fail to open and be reported as "not a valid 7z archive"; this also affected the Zip-Slip security test.
  • The log now records WHY a run ended early instead of a bare "finished with errors". On stop, ISApexEx writes the specific reason to ISApexEx.log: user cancellation from the progress callback, a declined password or disk-span volume prompt, the engine's own error text (e.g. a FreeArc "checksum mismatch"), or a host-initiated stop while a job was still running. (Needs a log file set with ISApexExSetLogFile.)
  • Version v0.1 Beta 2 → v0.1 Beta 3; build 6A3DC100 → 6A406400.

✦ Fixed
  • Interactive password on encrypted .7z archives whose file list is itself encrypted — a wrong or missing password now shows the prompt and retries (just like RAR and FreeArc) instead of failing with "not a valid 7z archive".
  • RAR wrong-password retry now also covers archives whose encrypted header is reported as generic data damage — but only when a password is actually involved, so genuinely corrupt archives are still reported as damaged.
  • A password-prompt callback that raises an error is now treated as a clean cancel instead of being misreported as a corrupt archive.
  • The crash-recovery (resume) checkpoint now saves reliably on all supported Windows versions (an internal integrity step could fail on some systems).
  • Optional component signature verification now loads the key and verifies files reliably on all supported Windows versions (same underlying fix). Files signed with the SignTool Suite verify unchanged.

✦ Security
  • In-memory password hygiene — archive passwords (per-disk, the session "last good password" cache, the interactive-prompt hand-off, and the FreeArc engine's working copies) are now overwritten with zeros as soon as they are no longer needed — not merely released — so a memory/crash dump cannot recover them from freed heap. Passwords are also wiped on ISApexExStop and on library unload.
  • Encrypted password keystore (AES-256, OFF by default) — see Added above. Lets the installer reference passwords by name instead of embedding clear text, and the stored passwords are wiped from memory on close / unload.
  • Optional component signature verification (RSA-2048 / SHA-256), OFF by default — ISApexExSetVerifyKey(ekey, password) turns on a check of every native engine component ISApexEx loads: each must carry a valid <name>.sig (made with the SignTool Suite) or the load is refused and the run fails (fail-closed). Plus ISApexExVerifyFile(path) to verify arbitrary shipped files (external compressors, helper tools, ISApexEx.dll's own image) and ISApexExGetVerifyStatus for diagnostics. Uses the OS crypto provider; no extra DLL shipped. One-time cost only; zero when unused. New result codes VFY_OK / VFY_TAMPERED / ...
  • Crash-recovery (resume) hardening — the resume file is now written atomically (temp file + flush-to-disk + atomic replace), so an instant crash mid-save can no longer truncate or lose the checkpoint.
  • Path-traversal protection on extraction (anti "Zip-Slip") — a malicious archive member that tries to write OUTSIDE the chosen destination — via '..' segments, an absolute path, a drive letter or a UNC path — is now blocked. ISApexEx fails closed: it stops the extraction, reports the blocked path, fires APEX_EVENT_ERROR and returns failure (no partial or dangerous install). Enforced for the 7z and RAR engines.
  • Hardened native-DLL loading — the engine DLLs and their dependencies now resolve only from System32 and ISApexEx's own folder, never the installer's working directory — closing a DLL-planting vector on end-user machines.
  • Password-handling robustness — the RAR password path now respects the engine's buffer size, removing a potential overflow with very long passwords.
  • New language string ERROR_UNSAFE_PATH (English.ini / Russian.ini).

✦ Notes / Known Limitations
  • Interactive password covers the three real archive engines (7z, RAR, FreeArc), including .7z archives whose file list is itself encrypted. On a wrong password all three now re-prompt: FreeArc retries until the password is correct or the user cancels; 7z and RAR retry up to 3 times, then fail that archive (re-run to try again). Cancelling the prompt aborts the archive cleanly (no corrupt output). Xdelta/HDiff patch jobs are unencrypted and never prompt.
  • Rename overwrite policy works on 7z and RAR. Only FreeArc lacks a rename-on-collision option and falls back to overwrite.
  • Selective extraction on FreeArc uses native globs only — quoted-regex and @listfile entries in the mask are ignored for the FreeArc engine (7z and RAR honour the full scanner syntax). Listing-time size totals are computed before filtering, so a heavily filtered job may show a slightly high byte total.
  • ZIP creation requires the full 7-Zip library that ISApexEx ships (it includes the zip writer). A stripped extract-only build cannot create archives and ISApexExAddZip fails. RAR creation is not possible; use ZIP for in-installer repacks.
  • ZIP methods STORE/DEFLATE/DEFLATE64 open in any unzip tool (incl. Windows Explorer); BZIP2/LZMA/PPMD compress better but need a modern tool (7-Zip, WinRAR). DEFLATE (the default) is the safe choice for end-user output.
  • Path-traversal protection is enforced for 7z and RAR. FreeArc (.arc/.bf) relies on its own engine to keep output inside the destination folder.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━

▌ v0.1 Beta 3 — Inno Setup Snippets
Runnable single-feature snippets — each also ships as its own example .iss (see the Added list above).

Example 1 — Archive Testing (No Extraction)
Code:
{ one shot - test every registered archive (writes nothing): }
if ISApexExTestAll then
  MsgBox('All archives are valid.', mbInformation, MB_OK)
else
  MsgBox('An archive is corrupt or the password is wrong.', mbError, MB_OK);

{ or per-disk, to pinpoint WHICH archive is bad: }
for i := 1 to ISApexExGetDiskCount do
  if not ISApexExTestDisk(i) then
    MsgBox(Format('Archive #%d failed the test.', [i]), mbError, MB_OK);
Example 2 — Existing-File Overwrite Policy
Code:
{ call AFTER AddDisks, once per disk. Policy is one of:
    APEX_OVERWRITE (0, default) | APEX_SKIP (1) | APEX_RENAME (2) }
ISApexExSetOverwritePolicy(ISApexExGetDiskCount, APEX_RENAME);
{ Overwrite + Skip work on all engines; Rename works on 7z and RAR
  (FreeArc has no per-file rename and falls back to overwrite). }
Example 3 — Selective Extraction (Include / Exclude Masks)
Code:
{ AFTER AddDisks, once per disk. '|' separates masks; exclude wins.
  Empty include = everything. }
ISApexExSetExtractFilter(ISApexExGetDiskCount, '*.dll|*.exe|data\*', '*.log|*.tmp');

{ regex form - wrap each pattern as $"..." (single-quoted Pascal string, so the
  inner double quotes are literal); a '|' inside is then protected: }
ISApexExSetExtractFilter(ISApexExGetDiskCount, '$".*\.(exe|dll)$"', '');
{ @listfile and the $"..." regex are honoured by 7z and RAR;
  FreeArc uses native globs only. }
Example 4 — Interactive Password Prompt (Custom Callback)
  • Way 1 — Native: do nothing extra; ISApexEx shows its own built-in password box automatically.
  • Way 2 — Custom callback (shown below): register your own prompt to replace the built-in box.
Code:
{ Custom callback prompt: register your own custom prompt to REPLACE
  the built-in box. Return the password, or '' to cancel (clean abort).
  Attempt is 1-based for retry messages. }
function OnPasswordRequest(const Filename: WideString; Attempt: Integer): WideString;
begin
  if Attempt > 1 then
    Result := InputBox('Wrong password - try again for ' + Filename, '', '')
  else
    Result := InputBox('Password for ' + Filename, '', '');
end;

{ register it BEFORE extraction (omit this call to use the native box): }
ISApexExSetPasswordCallback(@OnPasswordRequest);

{ Either way: triggers only for genuinely encrypted archives with no/empty/wrong
  password. The first entered password is cached and reused for the rest of the
  batch. Cancelling at the prompt writes the reason to ISApexEx.log (when a log
  file is set with ISApexExSetLogFile). }
Example 5 — Build a ZIP From a Folder
Code:
{ ISApexExAddZip(SrcDir, OutZip, Include, Exclude, Method, Level, Password, DeleteSource).
  Method : APEX_ZIP_STORE | DEFLATE | DEFLATE64 | BZIP2 | LZMA | PPMD
           (Store/Deflate/Deflate64 open anywhere; BZip2/LZMA/PPMd need a modern tool)
  Level  : 0=store .. 9=ultra (default 5; ignored for STORE)
  Password: '' = plain zip; non-empty = AES-256
  DeleteSource: True removes the source folder after a SUCCESSFUL zip. }
if not ISApexExAddZip(ExpandConstant('{app}\unpacked'),
                      ExpandConstant('{app}\bundle.zip'),
                      '*', '', APEX_ZIP_DEFLATE, 5, '', True) then
  MsgBox('Could not register the zip job.', mbError, MB_OK);
{ Runs in the same extraction loop as the archives, with unified progress. }
Example 6 — Job Counter (No Manual Disk Tracking)
Code:
{ No manual disk counter needed - loop 1..ISApexExGetDiskCount: }
for i := 1 to ISApexExGetDiskCount do
begin
  if not ISApexExExtract(i) then Break;
end;
Example 7 — Component Signature Verification
SetVerifyKey turns on fail-closed loading; VerifyFile checks one file, VerifyFolder batch-checks a whole tree.
Code:
{ Turn on verification: every engine DLL loaded after this must carry a valid
  '<name>.sig' beside it or its load is refused (fail-closed). }
if not ISApexExSetVerifyKey(ExpandConstant('{tmp}\public.ekey'), VerifyPassword) then
begin
  MsgBox('Verification could not be enabled: ' + ISApexExGetVerifyStatus,
         mbCriticalError, MB_OK);
  Exit;
end;

{ verify one file yourself (returns a VFY_* code; VFY_OK = authentic): }
if ISApexExVerifyFile(ExpandConstant('{tmp}\7z_32.dll')) <> VFY_OK then
  MsgBox('7z_32.dll is not trusted: ' + ISApexExGetVerifyStatus,
         mbCriticalError, MB_OK);

{ batch-verify every signed file under a folder. The callback (main thread)
  reports each file; return non-zero to cancel. The result is the number that
  FAILED (0 = all authentic; a negative value is an operational error). }
function VerifyProgress(Info: TISApexExVerifyProgress): LongWord;
begin
  Log(Format('Verify %d/%d: %s  code=%d',
      [Info.Index, Info.Total, Info.FilePath, Info.ResultCode]));
  Result := 0;
end;

Failed := ISApexExVerifyFolder(ExpandConstant('{tmp}'), True, @VerifyProgress);
if Failed <> 0 then
  MsgBox('Folder verification reported ' + IntToStr(Failed) + ' problem(s).',
         mbCriticalError, MB_OK);
Example 8 — Encrypted Password Keystore
Reserved names — 'disk' (shared password), 'disk2'/'disk3'... (per-archive overrides), 'ekey' (the SetVerifyKey password).
Code:
{ Keep the archive password out of the script: store it in an encrypted .keydb
  (built with the ApexKeyTool console utility) and reference it by NAME.
  Reserved names: 'disk' (shared archive password), 'disk2'/'disk3'.. (per-archive
  overrides), 'ekey' (the ISApexExSetVerifyKey password). }
if not ISApexExOpenKeyStore(ExpandConstant('{tmp}\ApexKeys.keydb')) then
begin
  MsgBox('Could not open the password keystore.', mbCriticalError, MB_OK);
  Exit;
end;

{ pass '@key:<name>' instead of a clear-text password - it is substituted from
  the keystore. A wrong/missing name resolves to an empty password (and is logged). }
if not ISApexExAddDisks(ExpandConstant('{src}\data1.7z'),
                        '@key:disk', ExpandConstant('{app}')) then
  MsgBox('Could not open the archive.', mbCriticalError, MB_OK);

{ close when done registering - the stored passwords are wiped from memory. }
ISApexExCloseKeyStore;
✦ Download
  • - Check the first post.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━

Feedback, bug reports, and edge cases are all welcome.
Attached Images
File Type: png 0.png (80.2 KB, 20 views)
File Type: png 1.png (107.9 KB, 20 views)
File Type: png 2.png (109.5 KB, 20 views)
File Type: png 3.png (78.6 KB, 20 views)
File Type: png 4.png (67.3 KB, 20 views)
File Type: png 5.png (70.9 KB, 20 views)
File Type: png 6.png (79.4 KB, 20 views)
File Type: png 7.png (74.6 KB, 20 views)
File Type: png 8.png (69.0 KB, 20 views)
File Type: png 9.png (129.1 KB, 20 views)
File Type: png 10.png (80.1 KB, 20 views)
File Type: png 11.png (114.3 KB, 20 views)
File Type: png 12.png (20.0 KB, 20 views)
Reply With Quote
The Following 3 Users Say Thank You to BLACKFIRE69 For This Useful Post:
audiofeel (Yesterday), Cesar82 (Yesterday), Ele (Yesterday)