commit 889289e5a038fa62d5f94dab1976e41cbd33b091 Author: Simone Date: Wed Apr 29 23:19:37 2026 +0200 initial: nsAdvsplash plugin 1.0.0 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..55629c5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{yml,yaml,json}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[*.{cmd,bat,ps1}] +end_of_line = crlf + +[*.{nsi,nsh,sln,vcxproj,filters}] +end_of_line = crlf + +[Makefile] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..20b175b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,52 @@ +# Normalizzazione line endings +* text=auto eol=lf + +# File di codice +*.c text eol=lf +*.cpp text eol=lf +*.h text eol=lf +*.hpp text eol=lf +*.def text eol=lf +*.py text eol=lf +*.ps1 text eol=lf +*.cmd text eol=crlf +*.bat text eol=crlf + +# NSIS +*.nsi text eol=crlf +*.nsh text eol=crlf + +# Markdown / docs / config +*.md text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.json text eol=lf +*.txt text eol=lf +*.xml text eol=lf + +# Visual Studio (CRLF richiesto) +*.sln text eol=crlf +*.vcxproj text eol=crlf merge=union +*.filters text eol=crlf merge=union +*.rc binary +*.rc2 binary + +# Binari +*.dll binary +*.exe binary +*.lib binary +*.obj binary +*.pdb binary +*.zip binary +*.7z binary +*.png binary +*.ico binary +*.gif binary +*.jpg binary +*.jpeg binary +*.bmp binary +*.pdf binary + +# Linguistic detection (GitHub Linguist / Gitea analogo) +*.nsi linguist-language=NSIS +*.nsh linguist-language=NSIS diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..7599f2a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,76 @@ +name: Bug Report +description: Segnala un bug riproducibile +title: "[Bug]: " +labels: ["bug", "triage"] +body: + - type: markdown + attributes: + value: | + Grazie per la segnalazione. Compila i campi sotto per aiutarci a riprodurre il problema. + + - type: input + id: plugin-version + attributes: + label: Versione plugin + description: Contenuto del file VERSION o tag della release scaricata + placeholder: "1.6.0" + validations: + required: true + + - type: dropdown + id: architecture + attributes: + label: Architettura DLL usata + options: + - x86-ansi + - x86-unicode + - amd64-unicode + - non so / multiple + validations: + required: true + + - type: input + id: nsis-version + attributes: + label: Versione NSIS + placeholder: "3.10" + validations: + required: true + + - type: input + id: os + attributes: + label: Sistema operativo + placeholder: "Windows 11 23H2 x64" + validations: + required: true + + - type: textarea + id: repro + attributes: + label: Passi per riprodurre + description: Script .nsi minimale che riproduce il problema + render: nsis + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Comportamento atteso + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Comportamento osservato + description: Includi messaggi di errore esatti, output del log NSIS, screenshot + validations: + required: true + + - type: textarea + id: additional + attributes: + label: Note aggiuntive + description: Qualunque altra informazione utile (workaround tentati, ecc.) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..a505ba5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Domande generali + url: https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash/issues?type=question + about: Per domande di utilizzo che non sono bug, apri una issue con label "question" + - name: Security + url: https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash/security/advisories/new + about: Segnala vulnerabilità di security in privato (non aprire issue pubbliche) diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..fae6d8d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,32 @@ +name: Feature Request +description: Suggerisci una nuova funzionalità o miglioramento +title: "[Feature]: " +labels: ["enhancement"] +body: + - type: textarea + id: problem + attributes: + label: Problema / Caso d'uso + description: Quale problema vorresti risolvere? Quale workflow vuoi abilitare? + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposta + description: Come dovrebbe funzionare? Includi esempio di API/uso negli script NSIS se possibile. + render: nsis + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternative considerate + description: Altri approcci che hai valutato e perché sono stati scartati + + - type: textarea + id: additional + attributes: + label: Note aggiuntive diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..3758b50 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,31 @@ +## Descrizione + + + +## Tipo di Modifica + +- [ ] Bug fix (modifica non breaking che risolve un problema) +- [ ] Feature (modifica non breaking che aggiunge funzionalità) +- [ ] Breaking change (fix o feature che rompe la compatibilità) +- [ ] Documentazione +- [ ] Build / CI + +## Architetture Testate + +- [ ] `x86-ansi` +- [ ] `x86-unicode` +- [ ] `amd64-unicode` + +## Checklist + +- [ ] Build completa senza warning: `python build_plugin.py` +- [ ] CHANGELOG.md aggiornato (sezione `[Unreleased]`) +- [ ] README / docs aggiornati se l'API è cambiata +- [ ] Esempi aggiornati se l'API è cambiata +- [ ] Conventional Commits rispettato +- [ ] Nessun file in `dist/` o `__pycache__/` committato +- [ ] Self-review completata + +## Note Aggiuntive + + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..4102e1e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,66 @@ +# Build (GitHub) — pipeline CI primaria +# +# Strategia hosting: +# - Repo primario su Gitea (push iniziale) +# - Push mirror automatico Gitea -> GitHub +# - GitHub esegue TUTTA la CI (Gitea non esegue Actions) +# +# Trigger: +# - push su main (incluso quello propagato dal mirror Gitea) +# - PR aperte su GitHub +# - run manuale via UI + +name: Build + +on: + push: + branches: [main] + paths-ignore: + - '**.md' + - 'docs/**' + - 'examples/**' + pull_request: + branches: [main] + workflow_dispatch: + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + build: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + config: [x86-ansi, x86-unicode, x64-unicode] + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + - name: Read VERSION + id: version + shell: pwsh + run: | + $v = (Get-Content VERSION -Raw).Trim() + "version=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append + + - name: Build ${{ matrix.config }} + run: python build_plugin.py --config ${{ matrix.config }} --verbosity minimal + + - name: Upload DLL + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }}-${{ steps.version.outputs.version }}-${{ matrix.config }} + path: dist/** + if-no-files-found: error + retention-days: 30 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e4cf67d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,99 @@ +# Release (GitHub) — pipeline release primaria +# +# Triggerata quando un tag v* viene propagato dal mirror Gitea. +# Costruisce gli artifact per tutte le architetture e crea la GitHub Release. + +name: Release + +on: + push: + tags: ['v*'] + workflow_dispatch: + inputs: + tag: + description: 'Tag versione (es. v1.7.0)' + required: true + +permissions: + contents: write + +jobs: + build: + runs-on: windows-latest + strategy: + fail-fast: true + matrix: + config: [x86-ansi, x86-unicode, x64-unicode] + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - uses: microsoft/setup-msbuild@v2 + + - name: Build ${{ matrix.config }} + run: python build_plugin.py --config ${{ matrix.config }} + + - name: Determine version & arch + id: meta + shell: pwsh + run: | + $tag = "${{ github.ref_name }}" + if ([string]::IsNullOrEmpty($tag) -or $tag -eq "main") { $tag = "${{ inputs.tag }}" } + $v = $tag -replace '^v','' + $map = @{ 'x86-ansi' = 'x86-ansi'; 'x86-unicode' = 'x86-unicode'; 'x64-unicode' = 'amd64-unicode' } + $dir = $map['${{ matrix.config }}'] + echo "version=$v" >> $env:GITHUB_OUTPUT + echo "tag=$tag" >> $env:GITHUB_OUTPUT + echo "dir=$dir" >> $env:GITHUB_OUTPUT + + - name: Package + shell: pwsh + run: | + $name = "${{ github.event.repository.name }}-${{ steps.meta.outputs.version }}-${{ steps.meta.outputs.dir }}" + $stage = "stage/$name" + New-Item -ItemType Directory -Force -Path $stage | Out-Null + Copy-Item "dist/${{ steps.meta.outputs.dir }}/*" $stage -Recurse + Copy-Item README.md, LICENSE, VERSION, CHANGELOG.md $stage + if (Test-Path "include") { Copy-Item "include" $stage -Recurse } + if (Test-Path "examples") { Copy-Item "examples" $stage -Recurse } + Compress-Archive -Path "$stage/*" -DestinationPath "$name.zip" -Force + + - uses: actions/upload-artifact@v4 + with: + name: pkg-${{ matrix.config }} + path: '*.zip' + + publish: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - name: SHA256SUMS + run: | + cd artifacts + sha256sum *.zip > SHA256SUMS.txt + cat SHA256SUMS.txt + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + files: | + artifacts/*.zip + artifacts/SHA256SUMS.txt + generate_release_notes: true + body: | + **Repository primario**: vedi link Gitea nel README. + + Il mirror GitHub è generato automaticamente dal repo Gitea (sorgente di verità). + Per issue e PR aprire ticket sul repository primario Gitea. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c3a730 --- /dev/null +++ b/.gitignore @@ -0,0 +1,63 @@ +# Build output (src/ subdirs only — dist/ is tracked, contains pre-built DLLs) +bin/ +obj/ +Build/ +build/ + +# Visual Studio +*.user +*.suo +*.ncb +*.sdf +.vs/ +ipch/ +*.VC.db +*.VC.VC.opendb +*.opendb +*.aps +*.tlog +*.idb +*.pdb +*.ilk +*.exp +*.lib +*.obj + +# Python +__pycache__/ +*.pyc +*.pyo +.pytest_cache/ +*.egg-info/ +.venv/ +venv/ + +# Editor / IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +Thumbs.db +desktop.ini +.DS_Store + +# NSIS +*.nsh.bak + +# Logs +*.log +.logs/ + +# Sensitive +.env +.env.local +*.pem +*.key + +# Migration/automation scripts (temporary, contain tokens — never commit) +tools/t*.py +tools/check_*.py +tools/legacy/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a8e9ada --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +Tutte le modifiche rilevanti a questo progetto sono documentate in questo file. + +Il formato segue [Keep a Changelog](https://keepachangelog.com/it/1.0.0/), +e il progetto aderisce al [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [1.0.1] — 2026-04-29 + +### Changed +- Build script completamente riscritto: Colors, Spinner, BuildConfig, build parallelo con ThreadPoolExecutor, CPU info, output colorato OK/FAIL +- CI: rimosso flag `--verbose` (non esistente) in favore di `--verbosity minimal` + +## [1.0.0] — 2026-04-29 + +### Added +- Prima release come repository indipendente +- Build script unificato (`build_plugin.py`) con CLI canonica +- CI/CD via GitHub Actions (mirror automatico Gitea → GitHub) +- Documentazione completa (README, CONTRIBUTING, SECURITY) + +[Unreleased]: https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash/compare/v1.0.1...HEAD +[1.0.1]: https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash/compare/v1.0.0...v1.0.1 +[1.0.0]: https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash/releases/tag/v1.0.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a0d8792 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,137 @@ +# Contribuire a nsAdvsplash + +Grazie per il tuo interesse a contribuire! + +> Repository primario: [https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash](https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash) +> Mirror GitHub: [https://github.com/systempal/nsis-plugin-nsadvsplash](https://github.com/systempal/nsis-plugin-nsadvsplash) (read-only — apri PR su Gitea) + +## Prerequisiti + +| Strumento | Versione | Note | +|-----------|----------|------| +| Visual Studio | 2022 / 2026 | Workload: Desktop development with C++ (toolset v143/v145) | +| Python | 3.7+ | Per `build_plugin.py` | +| Git | 2.30+ | Con supporto submoduli se applicabile | + +## Quick Start + +```powershell +git clone https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash.git +cd nsis-plugin-nsadvsplash +python build_plugin.py +``` + +Output in `dist/{x86-ansi,x86-unicode,amd64-unicode}/`. + +## Workflow Sviluppo + +### 1. Branch + +```powershell +git checkout -b feat/nome-feature # nuova feature +git checkout -b fix/descrizione-bug # bugfix +git checkout -b docs/aggiornamento # solo doc +git checkout -b chore/manutenzione # build, dipendenze +``` + +### 2. Commit + +Segui [Conventional Commits](https://www.conventionalcommits.org/it/v1.0.0/): + +``` +feat: aggiunge supporto per formato XYZ +fix: corregge crash su Windows 11 ARM +docs: aggiorna esempio di utilizzo +chore: aggiorna toolset MSVC +refactor: estrae logica parsing in funzione separata +test: aggiunge test per edge case +build: aggiorna script di build +ci: aggiorna workflow GitHub Actions +``` + +Breaking changes: + +``` +feat!: rinomina API Foo() in Bar() + +BREAKING CHANGE: Foo() rinominata in Bar(); aggiornare tutti i chiamanti. +``` + +### 3. Pull Request + +PR aperte verso il branch `main` su Gitea. Verifica: + +- [ ] Build completa senza warning per tutte le architetture: `python build_plugin.py` +- [ ] Esempi aggiornati se cambia l'API +- [ ] `CHANGELOG.md` aggiornato (sezione `[Unreleased]`) +- [ ] Documentazione aggiornata (README, docs/API.md se esiste) +- [ ] Conventional Commits rispettato +- [ ] Nessun file in `dist/` o `__pycache__/` committato + +## Build Script + +``` +python build_plugin.py [opzioni] +``` + +| Opzione | Default | Descrizione | +|---------|---------|-------------| +| `--config` | `all` | Architettura: `x86-ansi`, `x86-unicode`, `x64-unicode`, `all` | +| `--toolset` | `auto` | Visual Studio: `2022`, `2026`, `auto` (rileva il più recente) | +| `--jobs` | numero CPU | Thread paralleli per la build | +| `--clean` | false | Pulisce `dist/` prima di compilare | +| `--install-dir` | none | Copia DLL anche in `/{arch}/` (es. NSIS plugin folder) | +| `--verbose` | false | Output dettagliato (mostra comandi MSBuild) | + +Exit codes: +- `0` — Successo +- `1` — Errore di build +- `2` — Prerequisiti mancanti +- `3` — Argomenti invalidi + +## Release Workflow + +Le release sono automatiche via tag Git: + +```powershell +# 1. Aggiorna VERSION e CHANGELOG.md +echo "1.7.0" > VERSION +# (modifica CHANGELOG.md spostando entry da [Unreleased] a [1.7.0]) + +# 2. Commit +git add VERSION CHANGELOG.md +git commit -m "chore(release): v1.7.0" + +# 3. Tag annotato +git tag -a v1.7.0 -m "Release v1.7.0" + +# 4. Push +git push origin main +git push origin v1.7.0 +``` + +Il workflow `.github/workflows/release.yml` compilerà tutte le architetture +e creerà la release su Gitea con gli artifact ZIP allegati. Il mirror GitHub +riceverà automaticamente il tag. + +## Code Style + +- C/C++: stile coerente con il file modificato +- Python: PEP 8 (4 spazi indent, line length flessibile) +- Header file: `#include` ordinati: stdlib → Win32 → progetto +- Encoding: UTF-8 senza BOM (eccetto `.rc` che richiede UTF-16 LE) +- Line endings: LF in repo (`.gitattributes` normalizza) + +## Reporting Bug + +Apri una [issue](https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash/issues/new) usando il template "Bug Report". +Includi: +- Versione plugin e architettura DLL usata +- Versione NSIS +- Sistema operativo +- Script `.nsi` minimale che riproduce il problema +- Output / errore esatto + +## Domande + +Per domande generali, apri una [issue](https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash/issues) con label `question`. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bc3fdc5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +zlib License + +Copyright (c) 2026 Simone + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aea4d35 --- /dev/null +++ b/README.md @@ -0,0 +1,137 @@ +# NewAdvSplash NSIS Plugin + +**Personal modified version** + +--- + +## Original Description + +NewAdvSplash.dll - plug-in that lets you throw up a splash screen in NSIS installers with fading effects (win2k/xp) and transparency. At the same time can play wav-mp3 sound file. + +## Usage + +### 1) Show splash screen + +```nsis +newadvsplash::show [/NOUNLOAD] Delay FadeIn FadeOut KeyColor [/BANNER] [/PASSIVE] [/NOCANCEL] FileName +``` + +**Parameters:** +- `Delay` - time (milliseconds) to show image +- `FadeIn` - time to show the fadein scene +- `FadeOut` - time to show the fadeout scene +- `KeyColor` - color used for transparency, could be any RGB value (for ex. R=255 G=100 B=16 -> KeyColor=0xFF6410). Use KeyColor=-1 if there is no transparent color at your image. If KeyColor=-2 and image type is gif, plug-in attempts to extract transparency color value from the file header. For gif images transparency on the static background works even if KeyColor=-1. +- `/BANNER` - returns control to installer immediately after plug-in activation. Not depends on installer page - parent window NULL. +- `/NOCANCEL` - disables 'exit on user click' default behaviour. +- `/PASSIVE` - not forces splash window to foreground. +- `FileName` - splash image filename (with extension!). Bmp, gif and jpg image types are supported. + +**Examples:** + +```nsis +newadvsplash::show 3000 0 0 -2 "$PLUGINSDIR\catch.gif" +``` + +```nsis +newadvsplash::show /NOUNLOAD 1000 600 400 0xFF6410 /BANNER "$TEMP\spltmp.bmp" +``` + +Use /NOUNLOAD with /BANNER key - this case plug-in not waits for the end of 'show' and returns control to installer. If used in .onInit function, /BANNER key requires 'ShowWindow $HWNDPARENT 2' in .onGUIInit + +### 2) Stop splash + +```nsis +newadvsplash::stop [/WAIT | /FADEOUT] +``` + +- Without options terminates banner. +- With `/WAIT` option waits for the end. +- With `/FADEOUT` option forces banner close with fade out effect ('Delay' -> 0). + +### 3) Play sound + +```nsis +newadvsplash::play /NOUNLOAD [/LOOP] FileName +``` + +- `FileName` - sound filename to play (with extension, wav, mp3 ...). +- Empty Filename string "" stops sound. + +**Example:** + +```nsis +newadvsplash::play /NOUNLOAD /LOOP "$PLUGINSDIR\snd.mp3" +newadvsplash::show 2000 1000 500 -1 "$PLUGINSDIR\iamfat.jpg" +``` + +### 4) Get window handle + +```nsis +newadvsplash::hwnd /NOUNLOAD +Pop $0 ; $0 is window handle now +``` + +## Compatibility + +- Basic - Win95 and later. +- Fadein/fadeout - win2k/winxp. + +## Credits + +- Original code - Justin +- Converted to a plugin DLL by Amir Szekely (kichik) +- Fading and transparency by Nik Medved (brainsucker) +- Gif and jpeg support, /BANNER and /CANCEL, mp3 support - Takhir Bedertdinov + +--- + +## ⚠️ Differences in the Personal Version + +### New supported architecture: x64 (amd64-unicode) + +The original supported only: +- x86-ansi (Plugins\newadvsplash.dll) +- x86-unicode (Unicode\plugins\newadvsplash.dll) + +The personal version adds support for: +- **amd64-unicode** (x64) + +### Added Files + +- `build_plugin.py` - Python script to compile the plugin for all architectures +- `src/exdll.h` - Header for the NSIS extern DLL API +- `src/NewAdvSplash.vcxproj` - Visual Studio project updated for VS2022 and x64 + +### Removed Files + +Pre-compiled DLL files have been removed from the distribution as they are generated by the build: + +- `Plugins/newadvsplash.dll` +- `Unicode/plugins/newadvsplash.dll` + +### Build + +```cmd +cd nsAdvsplash +python build_plugin.py +``` + +DLLs are copied to `dist/{platform}/newadvsplash.dll`. + +### Build Options + +```powershell +python build_plugin.py --config x86-unicode # Single architecture (x86-ansi|x86-unicode|x64-unicode|all) +python build_plugin.py --vs-version 2026 # Specific VS version (2022|2026|auto) +python build_plugin.py --clean # Clean dist/ before build +python build_plugin.py --install-dir "C:\NSIS\Plugins" # Copy to additional NSIS directory +python build_plugin.py --verbosity minimal # MSBuild verbosity (quiet|minimal|normal|detailed|diagnostic) +``` + +### Functional Changes + +No functional changes compared to the original. Modifications are limited to the build infrastructure and x64 support. + +--- + +*See [README_IT.md](README_IT.md) for the Italian version.* diff --git a/README_IT.md b/README_IT.md new file mode 100644 index 0000000..b35b96e --- /dev/null +++ b/README_IT.md @@ -0,0 +1,128 @@ +# NewAdvSplash NSIS Plugin + +**Versione personale modificata** + +--- + +## Descrizione Originale + +NewAdvSplash.dll - plug-in che permette di visualizzare uno splash screen negli installer NSIS con effetti di dissolvenza (win2k/xp) e trasparenza. Può contemporaneamente riprodurre file audio wav/mp3. + +## Utilizzo + +### 1) Mostra splash screen + +```nsis +newadvsplash::show [/NOUNLOAD] Delay FadeIn FadeOut KeyColor [/BANNER] [/PASSIVE] [/NOCANCEL] FileName +``` + +**Parametri:** +- `Delay` - tempo (millisecondi) di visualizzazione dell'immagine +- `FadeIn` - tempo per la scena di fade in +- `FadeOut` - tempo per la scena di fade out +- `KeyColor` - colore usato per la trasparenza, può essere qualsiasi valore RGB (es. R=255 G=100 B=16 -> KeyColor=0xFF6410). Usare KeyColor=-1 se non c'è colore trasparente. Se KeyColor=-2 e il tipo è gif, il plug-in tenta di estrarre il colore di trasparenza dall'header del file. +- `/BANNER` - restituisce il controllo all'installer subito dopo l'attivazione del plug-in. +- `/NOCANCEL` - disabilita il comportamento default 'exit on user click'. +- `/PASSIVE` - non forza la finestra splash in primo piano. +- `FileName` - nome file immagine splash (con estensione!). Tipi supportati: Bmp, gif e jpg. + +**Esempi:** + +```nsis +newadvsplash::show 3000 0 0 -2 "$PLUGINSDIR\catch.gif" +``` + +```nsis +newadvsplash::show /NOUNLOAD 1000 600 400 0xFF6410 /BANNER "$TEMP\spltmp.bmp" +``` + +### 2) Ferma splash + +```nsis +newadvsplash::stop [/WAIT | /FADEOUT] +``` + +- Senza opzioni termina il banner. +- Con `/WAIT` attende la fine. +- Con `/FADEOUT` forza la chiusura con effetto fade out. + +### 3) Riproduci audio + +```nsis +newadvsplash::play /NOUNLOAD [/LOOP] FileName +``` + +- `FileName` - file audio da riprodurre (con estensione, wav, mp3...). +- Stringa vuota "" ferma l'audio. + +### 4) Ottieni handle finestra + +```nsis +newadvsplash::hwnd /NOUNLOAD +Pop $0 ; $0 è ora l'handle della finestra +``` + +## Compatibilità + +- Base - Win95 e successivi. +- Fadein/fadeout - win2k/winxp. + +## Crediti + +- Codice originale - Justin +- Convertito in plugin DLL da Amir Szekely (kichik) +- Dissolvenza e trasparenza di Nik Medved (brainsucker) +- Supporto gif e jpeg, /BANNER e /CANCEL, supporto mp3 - Takhir Bedertdinov + +--- + +## ⚠️ Differenze nella versione personale + +### Nuova architettura supportata: x64 (amd64-unicode) + +L'originale supportava solo: +- x86-ansi (Plugins\newadvsplash.dll) +- x86-unicode (Unicode\plugins\newadvsplash.dll) + +La versione personale aggiunge il supporto per: +- **amd64-unicode** (x64) + +### File aggiunti + +- `build_plugin.py` - Script Python per compilare il plugin per tutte le architetture +- `src/exdll.h` - Header per l'API NSIS extern DLL +- `src/NewAdvSplash.vcxproj` - Progetto Visual Studio aggiornato per VS2022 e x64 + +### File rimossi + +I file DLL precompilati sono stati rimossi dalla distribuzione in quanto vengono generati dalla compilazione: + +- `Plugins/newadvsplash.dll` +- `Unicode/plugins/newadvsplash.dll` + +### Compilazione + +```cmd +cd nsAdvsplash +python build_plugin.py +``` + +I DLL vengono copiati in `dist/{platform}/newadvsplash.dll`. + +### Opzioni build + +```powershell +python build_plugin.py --config x86-unicode # Solo un'architettura (x86-ansi|x86-unicode|x64-unicode|all) +python build_plugin.py --vs-version 2026 # Versione VS specifica (2022|2026|auto) +python build_plugin.py --clean # Pulizia dist/ prima della build +python build_plugin.py --install-dir "C:\NSIS\Plugins" # Copia in directory NSIS aggiuntiva +python build_plugin.py --verbosity minimal # Verbosità MSBuild (quiet|minimal|normal|detailed|diagnostic) +``` + +### Modifiche funzionali + +Nessuna modifica funzionale rispetto all'originale. Le modifiche riguardano solo l'infrastruttura di build e il supporto x64. + +--- + +*See [README.md](README.md) for the English version.* diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..9a7c1f5 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,40 @@ +# Security Policy + +## Versioni Supportate + +Solo l'ultima versione minor riceve fix di security. + +| Versione | Supporto | +|----------|----------| +| Latest minor (es. 1.0.0) | ✅ | +| Versioni precedenti | ❌ | + +## Reporting di una Vulnerabilità + +**Non aprire issue pubbliche per vulnerabilità di security.** + +Per segnalare una vulnerabilità: + +1. **Preferito**: usa la funzione [Security Advisory privato](https://gitea.emulab.it/Simone/nsis-plugin-nsadvsplash/security/advisories/new) di Gitea +2. **Alternativa**: invia email a `security@example.com` (sostituire con email reale prima del primo release) + +Includi: +- Descrizione della vulnerabilità +- Passi per riprodurla +- Versione/i affette +- Possibile mitigazione (se nota) + +## Tempo di Risposta + +- Acknowledgment iniziale: entro 7 giorni +- Valutazione: entro 14 giorni +- Patch (se confermata): tempistiche dipendono dalla severità + +## Hardening + +Questo plugin gira con i permessi del processo NSIS chiamante (tipicamente +l'utente che esegue l'installer). Non richiede privilegi elevati per la +compilazione né per l'esecuzione, salvo dove esplicitamente documentato. + +DLL distribuite tramite Releases sono compilate da CI GitHub Actions con +toolchain pinnata. Verificare hash SHA256 pubblicati nelle release. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..afaf360 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/build_plugin.py b/build_plugin.py new file mode 100644 index 0000000..1342c4d --- /dev/null +++ b/build_plugin.py @@ -0,0 +1,679 @@ +#!/usr/bin/env python3 +""" +Build script for NewAdvSplash plugin - All configurations +Supports multiple build configurations with flexible parameters +""" + +import argparse +import errno +import multiprocessing +import os +import shutil +import stat +import subprocess +import sys +import threading +import time +import xml.etree.ElementTree as ET +from concurrent.futures import ThreadPoolExecutor, as_completed +from pathlib import Path +from typing import List, Optional, Tuple + +# Ensure stdout/stderr handle Unicode on Windows CI (cp1252 default breaks non-ASCII) +if sys.stdout.encoding and sys.stdout.encoding.lower() not in ('utf-8', 'utf_8'): + import io + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') + sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') + + +# --------------------------------------------------------------------------- +# Filesystem helpers +# --------------------------------------------------------------------------- + +def _on_rmtree_error(func, path, exc_info): + if func in (os.unlink, os.rmdir) and exc_info[1].errno == errno.EACCES: + try: + os.chmod(path, stat.S_IWRITE) + func(path) + return + except Exception: + pass + for i in range(5): + try: + time.sleep(0.1 * (i + 1)) + if os.path.isdir(path): + shutil.rmtree(path) + else: + os.unlink(path) + return + except Exception: + pass + raise exc_info[1] + + +def robust_rmtree(path): + if not os.path.exists(path): + return + try: + shutil.rmtree(path, onexc=_on_rmtree_error) + except TypeError: + try: + shutil.rmtree(path, onerror=_on_rmtree_error) + except Exception: + pass + except Exception: + try: + shutil.rmtree(path, ignore_errors=True) + except Exception: + pass + + +# --------------------------------------------------------------------------- +# Colors & Spinner +# --------------------------------------------------------------------------- + +class Colors: + CYAN = "\033[36m" + GREEN = "\033[32m" + YELLOW = "\033[33m" + RED = "\033[31m" + GRAY = "\033[90m" + BLUE = "\033[34m" + RESET = "\033[0m" + BOLD = "\033[1m" + BRIGHT_GREEN = "\033[92m" + BRIGHT_CYAN = "\033[96m" + BRIGHT_WHITE = "\033[97m" + BRIGHT_YELLOW = "\033[93m" + BRIGHT_RED = "\033[91m" + + +class Spinner: + def __init__(self, message: str = "Building...", delay: float = 0.1, total: int = 0): + self.spinner = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f'] + self.delay = delay + self.message = message + self.running = False + self.thread = None + self.start_time = time.time() + + def update(self, current=None): + pass + + def _spin(self): + idx = 0 + _block = '\u28ff' + while self.running: + elapsed = time.time() - self.start_time + n_blocks = int(elapsed // 2) + time_blocks = f"{Colors.YELLOW}{_block * n_blocks}{Colors.RESET}" + spin_char = f"{Colors.YELLOW}{self.spinner[idx % len(self.spinner)]}{Colors.RESET}" + msg = f"{Colors.BOLD}{Colors.CYAN}{self.message}{Colors.RESET}" + time_str = f"{Colors.GREEN}{int(elapsed)}s{Colors.RESET}" + sys.stdout.write(f"\r{msg} {time_str} {time_blocks}{spin_char} ") + sys.stdout.flush() + idx += 1 + time.sleep(self.delay) + sys.stdout.write("\r" + " " * (len(self.message) + 80) + "\r") + sys.stdout.flush() + + def __enter__(self): + if sys.stdout.isatty(): + self.running = True + self.thread = threading.Thread(target=self._spin, daemon=True) + self.thread.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.running: + self.running = False + if self.thread: + self.thread.join() + + +# --------------------------------------------------------------------------- +# Build configuration +# --------------------------------------------------------------------------- + +class BuildConfig: + def __init__(self, name: str, config: str, platform: str, dest_dir: str): + self.name = name + self.config = config + self.platform = platform + self.dest_dir = dest_dir + + +CONFIGS = { + 'x86-ansi': BuildConfig('x86-ansi', 'Release', 'Win32', 'x86-ansi'), + 'x86-unicode': BuildConfig('x86-unicode', 'Release Unicode', 'Win32', 'x86-unicode'), + 'x64-unicode': BuildConfig('x64-unicode', 'Release Unicode', 'x64', 'amd64-unicode'), +} + +DLL_NAME = 'newadvsplash.dll' +_VSWHERE = Path(r'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe') +_VS_RANGES = {'2026': '[18.0,19.0)', '2022': '[17.0,18.0)'} +_VS_TOOLSETS = {'2026': 'v145', '2022': 'v143'} + + +# --------------------------------------------------------------------------- +# MSBuild discovery +# --------------------------------------------------------------------------- + +def _find_msbuild_vswhere(vs_version: str) -> Optional[Tuple[Path, str, str]]: + if not _VSWHERE.exists(): + return None + to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version] + for ver in to_try: + if ver not in _VS_RANGES: + continue + try: + result = subprocess.run( + [str(_VSWHERE), '-version', _VS_RANGES[ver], '-latest', + '-requires', 'Microsoft.Component.MSBuild', + '-find', r'MSBuild\**\Bin\MSBuild.exe'], + capture_output=True, text=True, timeout=15, + ) + if result.returncode == 0: + lines = [ln.strip() for ln in result.stdout.splitlines() if ln.strip()] + if lines: + p = Path(lines[0]) + if p.exists(): + return p, _VS_TOOLSETS[ver], ver + except Exception: + pass + return None + + +def find_msbuild(vs_version: str = 'auto') -> Optional[Tuple[Path, str, str]]: + result = _find_msbuild_vswhere(vs_version) + if result: + return result + # Fallback: well-known paths + to_try = ['2026', '2022'] if vs_version == 'auto' else [vs_version] + for ver in to_try: + for edition in ('Community', 'Professional', 'Enterprise', 'BuildTools'): + p = Path(rf'C:\Program Files\Microsoft Visual Studio\{ver}\{edition}\MSBuild\Current\Bin\MSBuild.exe') + if p.exists(): + return p, _VS_TOOLSETS[ver], ver + return None + + +# --------------------------------------------------------------------------- +# Project paths & version +# --------------------------------------------------------------------------- + +def get_project_paths() -> Tuple[Path, Path, Path]: + script_dir = Path(__file__).parent.absolute() + project_dir = script_dir / 'src' + project_file = project_dir / 'NewAdvSplash.vcxproj' + dist_dir = script_dir / 'dist' + return project_dir, project_file, dist_dir + + +def get_plugins_dir() -> Optional[Path]: + """Return workspace-level plugins/ dir (4 levels above src/modules//).""" + p = Path(__file__).parent.parent.parent.parent / 'plugins' + return p if p.is_dir() else None + + +def read_version() -> str: + vf = Path(__file__).parent / 'VERSION' + try: + return vf.read_text(encoding='utf-8-sig').strip() + except Exception: + return '0.0.0' + + +# --------------------------------------------------------------------------- +# CPU info & build optimizations +# --------------------------------------------------------------------------- + +def get_cpu_info() -> dict: + try: + import psutil + logical = psutil.cpu_count(logical=True) + physical = psutil.cpu_count(logical=False) + return {'logical_cores': logical, 'physical_cores': physical, + 'has_hyperthreading': logical > physical, 'has_psutil': True} + except ImportError: + n = multiprocessing.cpu_count() + return {'logical_cores': n, 'physical_cores': n, + 'has_hyperthreading': False, 'has_psutil': False} + + +def get_optimal_threads() -> int: + info = get_cpu_info() + return info['physical_cores'] if info['has_hyperthreading'] and info['physical_cores'] > 1 else info['logical_cores'] + + +def get_build_optimizations() -> List[str]: + return [ + '/p:BuildInParallel=true', + '/p:MultiProcessorCompilation=true', + '/p:PreferredToolArchitecture=x64', + '/p:UseSharedCompilation=true', + '/nodeReuse:true', + '/p:GenerateResourceUsePreserializedResources=true', + ] + + +def get_memory_optimizations() -> List[str]: + try: + import psutil + gb = psutil.virtual_memory().total / (1024 ** 3) + if gb >= 16: + return ['/p:DisableFastUpToDateCheck=false', '/p:BuildProjectReferences=true', + '/p:UseCommonOutputDirectory=false'] + elif gb >= 8: + return ['/p:DisableFastUpToDateCheck=false'] + return [] + except ImportError: + return ['/p:DisableFastUpToDateCheck=false'] + + +def print_cpu_info(use_parallel: bool, use_optimizations: bool = True) -> None: + if not use_parallel: + print(f"{Colors.CYAN}Build mode: {Colors.RESET} {Colors.BOLD}Single-threaded{Colors.RESET}") + if use_optimizations: + print(f"{Colors.CYAN}Optimizations: {Colors.RESET} {Colors.GREEN}ENABLED{Colors.RESET} {Colors.GRAY}(memory, caching){Colors.RESET}") + return + + info = get_cpu_info() + optimal = get_optimal_threads() + print(f"{Colors.CYAN}Build mode: {Colors.RESET} {Colors.BRIGHT_WHITE}{'Parallel' if use_parallel else 'Sequential'}{Colors.RESET}") + print(f"{Colors.CYAN}Logical cores: {Colors.RESET} {Colors.BRIGHT_WHITE}{info['logical_cores']}{Colors.RESET}") + print(f"{Colors.CYAN}Physical cores: {Colors.RESET} {Colors.BRIGHT_WHITE}{info['physical_cores']}{Colors.RESET}") + if info['has_hyperthreading']: + print(f"{Colors.CYAN}Hyperthreading: {Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET}") + print(f"{Colors.CYAN}Optimal threads: {Colors.RESET} {Colors.BRIGHT_WHITE}{optimal}{Colors.RESET} {Colors.GRAY}(using physical cores){Colors.RESET}") + else: + print(f"{Colors.CYAN}Hyperthreading: {Colors.RESET} {Colors.GRAY}NOT AVAILABLE{Colors.RESET}") + print(f"{Colors.CYAN}Optimal threads: {Colors.RESET} {Colors.BRIGHT_WHITE}{optimal}{Colors.RESET}") + print(f"{Colors.CYAN}MSBuild threads: {Colors.RESET} {Colors.BRIGHT_WHITE}{optimal}{Colors.RESET}") + if use_optimizations: + print(f"{Colors.CYAN}Optimizations: {Colors.RESET} {Colors.BRIGHT_GREEN}ENABLED{Colors.RESET} {Colors.GRAY}(parallel, memory, caching){Colors.RESET}") + try: + import psutil + gb = psutil.virtual_memory().total / (1024 ** 3) + print(f"{Colors.CYAN}Available memory: {Colors.RESET} {Colors.BRIGHT_WHITE}{gb:.1f} GB{Colors.RESET}") + except ImportError: + print(f"{Colors.CYAN}Available memory: {Colors.RESET} {Colors.GRAY}Unknown (install psutil){Colors.RESET}") + else: + print(f"{Colors.CYAN}Optimizations: {Colors.RESET} {Colors.BRIGHT_RED}DISABLED{Colors.RESET}") + if not info['has_psutil']: + print(f"{Colors.GRAY}Note: Install 'psutil' for detailed CPU/memory info (pip install psutil){Colors.RESET}") + print() + + +# --------------------------------------------------------------------------- +# Build helpers +# --------------------------------------------------------------------------- + +def format_time(seconds: float) -> str: + if seconds < 60: + return f"{seconds:.2f}s" + elif seconds < 3600: + return f"{int(seconds // 60)}m {seconds % 60:.1f}s" + return f"{int(seconds // 3600)}h {int((seconds % 3600) // 60)}m {seconds % 60:.0f}s" + + +def build_configuration( + msbuild_path: Path, + project_file: Path, + config: BuildConfig, + platform_toolset: str, + *, + rebuild: bool = True, + verbosity: str = 'quiet', + parallel: bool = True, + optimizations: bool = True, + counter: str = "", + capture_output: bool = False, +) -> Tuple[bool, float, str]: + optimal = get_optimal_threads() + cmd = [ + str(msbuild_path), + str(project_file), + f'/t:{"Rebuild" if rebuild else "Build"}', + f'/p:Configuration={config.config}', + f'/p:Platform={config.platform}', + f'/p:OutDir=Build\\{config.name}\\', + f'/p:IntDir=Build\\{config.name}\\obj\\', + '/p:WindowsTargetPlatformVersion=10.0', + f'/p:PlatformToolset={platform_toolset}', + f'/v:{verbosity}', + ] + if parallel: + cmd += [f'/maxcpucount:{optimal}', '/p:UseMultiToolTask=true', f'/p:CL_MPCount={optimal}'] + cmd += ['/p:Optimization=MaxSpeed', '/p:ExceptionHandling=Sync'] + if optimizations: + cmd += get_build_optimizations() + get_memory_optimizations() + + if not capture_output: + color = Colors.BRIGHT_YELLOW + print(f"\n{color}{'='*60}{Colors.RESET}") + label = f"[{counter}] " if counter else "" + print(f"{color}Building {Colors.BRIGHT_WHITE}{config.name:15s}{Colors.RESET} {color}{label}({'rebuild' if rebuild else 'incremental'}){Colors.RESET}") + print(f"{color}{'='*60}{Colors.RESET}") + + start = time.time() + try: + if capture_output: + result = subprocess.run(cmd, check=False, capture_output=True, text=True) + return result.returncode == 0, time.time() - start, result.stdout + result.stderr + else: + result = subprocess.run(cmd, check=False) + return result.returncode == 0, time.time() - start, "" + except Exception as e: + elapsed = time.time() - start + msg = f"ERROR: Build failed with exception: {e}" + if capture_output: + return False, elapsed, msg + print(msg) + return False, elapsed, "" + + +def copy_output(project_dir: Path, dist_dir: Path, config: BuildConfig) -> Tuple[bool, int, Optional[Path]]: + output_file = project_dir / 'Build' / config.name / DLL_NAME + if not output_file.exists(): + print(f"ERROR: DLL not found: {output_file}") + return False, 0, None + dest = dist_dir / config.dest_dir + dest.mkdir(parents=True, exist_ok=True) + try: + dst = dest / DLL_NAME + shutil.copy2(output_file, dst) + return True, output_file.stat().st_size, dst + except Exception as e: + print(f"ERROR: Failed to copy {config.name}: {e}") + return False, 0, None + + +def copy_to_plugins(dist_dir: Path, config: BuildConfig, plugins_dir: Path) -> Optional[Path]: + """Copy DLL from dist// to plugins//.""" + src = dist_dir / config.dest_dir / DLL_NAME + if not src.exists(): + return None + dst_dir = plugins_dir / config.dest_dir + dst_dir.mkdir(parents=True, exist_ok=True) + dst = dst_dir / DLL_NAME + shutil.copy2(src, dst) + return dst + + +def clean_build_artifacts(project_dir: Path, configs: List[BuildConfig]) -> None: + print(f"\n{Colors.CYAN}Cleaning build artifacts...{Colors.RESET}") + build_base = project_dir / 'Build' + if not build_base.exists(): + return + for cfg in configs: + d = build_base / cfg.name + if d.exists(): + try: + robust_rmtree(d) + print(f" {Colors.GRAY}- Cleaned:{Colors.RESET} {cfg.name}") + except Exception as e: + print(f" {Colors.RED}- Failed to clean {cfg.name}: {e}{Colors.RESET}") + try: + if build_base.exists() and not any(build_base.iterdir()): + build_base.rmdir() + except Exception: + pass + print(f"{Colors.BRIGHT_GREEN}Build artifacts cleaned successfully.{Colors.RESET}") + + +def print_available_configurations(project_file: Path) -> None: + try: + tree = ET.parse(project_file) + root = tree.getroot() + ns = {'ms': 'http://schemas.microsoft.com/developer/msbuild/2003'} + configs = [] + for ig in root.findall('.//ms:ItemGroup', ns): + for pc in ig.findall('ms:ProjectConfiguration', ns): + inc = pc.get('Include', '') + parts = inc.split('|') + if len(parts) == 2: + configs.append(tuple(parts)) + print("Available configurations in project:") + print("-" * 60) + from collections import defaultdict + grouped: dict = defaultdict(list) + for cfg, plat in configs: + grouped[cfg].append(plat) + for cfg_name in sorted(grouped): + print(f" {cfg_name:25s} - {', '.join(sorted(grouped[cfg_name]))}") + print(f"\nTotal: {len(configs)} configuration(s)\n") + except Exception as e: + print(f"Could not parse project file: {e}") + + +_print_lock = threading.Lock() + + +def _build_configs_parallel( + msbuild_path: Path, + project_file: Path, + configs: List[BuildConfig], + platform_toolset: str, + *, + rebuild: bool, + verbosity: str, + parallel: bool, + optimizations: bool, + project_dir: Path, + dist_dir: Path, +) -> list: + n = len(configs) + print(f"\nParallel: launching {n} builds simultaneously...") + print("=" * 50) + total_start = time.time() + results_by_idx: dict = {} + idx_lock = threading.Lock() + + def _build_one(idx: int, config: BuildConfig): + import contextlib + import io as _io + success, build_time, captured = build_configuration( + msbuild_path, project_file, config, platform_toolset, + rebuild=rebuild, verbosity=verbosity, + parallel=parallel, optimizations=optimizations, + capture_output=True, + ) + copy_buf = _io.StringIO() + if success: + with contextlib.redirect_stdout(copy_buf): + copy_ok, file_size, dest_path = copy_output(project_dir, dist_dir, config) + else: + copy_ok, file_size, dest_path = False, 0, None + + with _print_lock: + sys.stdout.write("\r" + " " * 80 + "\r") + sys.stdout.flush() + tag = "OK" if (success and copy_ok) else "FAILED" + size_str = f"{file_size:,} bytes" if file_size > 0 else "N/A" + copy_out = copy_buf.getvalue() + if copy_out.strip(): + print(copy_out.rstrip()) + if captured.strip(): + lines = [ln for ln in captured.splitlines() if "MSBuild" not in ln and "Copyright" not in ln] + filtered = "\n".join(lines).strip() + if filtered: + print(filtered) + color = Colors.BRIGHT_GREEN if (success and copy_ok) else Colors.RED + print(f"{color}[{tag}]{Colors.RESET} {config.name} ({format_time(build_time)}) {size_str}") + if dest_path: + print(f" -> {dest_path}") + + with idx_lock: + results_by_idx[idx] = (config, success and copy_ok, build_time, file_size, dest_path) + + with Spinner("Building configurations...", total=n) as s: + with ThreadPoolExecutor(max_workers=n) as executor: + futures = {executor.submit(_build_one, i, cfg): i for i, cfg in enumerate(configs)} + for fut in as_completed(futures): + s.update() + exc = fut.exception() + if exc: + idx = futures[fut] + with _print_lock: + print(f"ERROR: worker for config index {idx} raised: {exc}") + with idx_lock: + results_by_idx[idx] = (configs[idx], False, 0.0, 0, None) + + wall = time.time() - total_start + print(f"\nAll {n} configs finished in {format_time(wall)} (wall clock)") + return [results_by_idx[i] for i in range(n)] + + +# --------------------------------------------------------------------------- +# main +# --------------------------------------------------------------------------- + +def main() -> int: + parser = argparse.ArgumentParser(description='Build NewAdvSplash plugin for NSIS') + parser.add_argument('--configs', nargs='+', + choices=list(CONFIGS.keys()) + ['all'], default=None) + parser.add_argument('--config', # singular alias used by CI + choices=list(CONFIGS.keys()) + ['all'], default=None) + parser.add_argument('--rebuild', action='store_true', default=True) + parser.add_argument('--no-rebuild', action='store_false', dest='rebuild') + parser.add_argument('--parallel', action='store_true', default=True) + parser.add_argument('--no-parallel', action='store_false', dest='parallel') + parser.add_argument('--verbosity', + choices=['quiet', 'minimal', 'normal', 'detailed', 'diagnostic'], + default='quiet') + parser.add_argument('--clean', action='store_true', default=False) + parser.add_argument('--list', action='store_true') + parser.add_argument('--list-project', action='store_true') + parser.add_argument('--no-optimizations', action='store_true') + parser.add_argument('--vs-version', choices=['auto', '2026', '2022'], default='auto') + parser.add_argument('--final', action='store_true', default=False, + help='Force rebuild + clean (pre-release build)') + parser.add_argument('--install-dir', type=Path, default=None, + help='Copy built DLLs to this directory (in / subdirs)') + args = parser.parse_args() + + # Merge --config (singular) into --configs list + if args.config and not args.configs: + args.configs = [args.config] + + if args.final: + args.rebuild = True + args.clean = True + + project_dir, project_file, dist_dir = get_project_paths() + + if args.list_project: + print_available_configurations(project_file) + return 0 + + if args.list: + for name, config in CONFIGS.items(): + print(f" {name:15s} -> {config.config} / {config.platform}") + return 0 + + if not project_file.exists(): + print(f"ERROR: project file not found: {project_file}", file=sys.stderr) + return 3 + + msbuild_result = find_msbuild(args.vs_version) + if not msbuild_result: + print("ERROR: MSBuild not found. Install Visual Studio 2022 or 2026.", file=sys.stderr) + return 2 + msbuild_path, platform_toolset, vs_version_name = msbuild_result + + version = read_version() + + if not args.configs: + configs_to_build = list(CONFIGS.values()) + elif 'all' in args.configs: + configs_to_build = list(CONFIGS.values()) + else: + configs_to_build = [CONFIGS[n] for n in args.configs] + + # Banner + print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'='*60}") + print(f"Building NewAdvSplash plugin v{version} - {Colors.BRIGHT_WHITE}{len(configs_to_build)}{Colors.RESET} {Colors.BOLD}{Colors.BRIGHT_CYAN}configuration(s)") + print(f"{'='*60}{Colors.RESET}") + print(f"{Colors.CYAN}VS: {Colors.RESET} {Colors.BRIGHT_WHITE}{vs_version_name}{Colors.RESET} {Colors.GRAY}({platform_toolset}){Colors.RESET}") + print(f"{Colors.CYAN}MSBuild: {Colors.RESET} {Colors.BRIGHT_WHITE}{msbuild_path}{Colors.RESET}") + print(f"{Colors.CYAN}Project: {Colors.RESET} {Colors.BRIGHT_WHITE}{project_file}{Colors.RESET}") + print(f"{Colors.CYAN}Dist: {Colors.RESET} {Colors.BRIGHT_WHITE}{dist_dir}{Colors.RESET}") + print(f"{Colors.CYAN}Rebuild: {Colors.RESET} {Colors.BRIGHT_WHITE}{args.rebuild}{Colors.RESET}") + print(f"{Colors.CYAN}Verbosity: {Colors.RESET} {Colors.BRIGHT_WHITE}{args.verbosity}{Colors.RESET}") + print() + + use_optimizations = not args.no_optimizations + print_cpu_info(args.parallel, use_optimizations) + + build_results = [] + total_start = time.time() + + if args.parallel and len(configs_to_build) > 1: + build_results = _build_configs_parallel( + msbuild_path, project_file, configs_to_build, platform_toolset, + rebuild=args.rebuild, verbosity=args.verbosity, + parallel=True, optimizations=use_optimizations, + project_dir=project_dir, dist_dir=dist_dir, + ) + else: + for i, config in enumerate(configs_to_build, 1): + success, b_time, _ = build_configuration( + msbuild_path, project_file, config, platform_toolset, + rebuild=args.rebuild, verbosity=args.verbosity, + parallel=args.parallel, optimizations=use_optimizations, + counter=f"{i}/{len(configs_to_build)}", + ) + if success: + ok, size, path = copy_output(project_dir, dist_dir, config) + build_results.append((config, ok, b_time, size, path)) + else: + build_results.append((config, False, b_time, 0, None)) + + total_time = time.time() - total_start + all_success = all(res[1] for res in build_results) + + print(f"\n{Colors.BOLD}{Colors.BRIGHT_GREEN if all_success else Colors.RED}{'='*50}") + print("ALL BUILDS SUCCESSFUL!" if all_success else "SOME BUILDS FAILED!") + print(f"{'='*50}{Colors.RESET}") + + # Copy to workspace plugins/ dir if present + plugins_dir = get_plugins_dir() + plugins_copied = [] + if plugins_dir and all_success: + for cfg, ok, _, _, _ in build_results: + if ok: + dst = copy_to_plugins(dist_dir, cfg, plugins_dir) + if dst: + plugins_copied.append(str(dst)) + if plugins_copied: + print(f"\n{Colors.CYAN}Installed to plugins/:{Colors.RESET}") + for p in plugins_copied: + print(f" {Colors.GRAY}->{Colors.RESET} {p}") + + if args.install_dir and all_success: + print(f"\n{Colors.CYAN}Installing to {args.install_dir}...{Colors.RESET}") + for cfg, ok, _, _, path in build_results: + if ok and path: + dest = args.install_dir / cfg.dest_dir + dest.mkdir(parents=True, exist_ok=True) + shutil.copy2(path, dest / DLL_NAME) + print(f" {Colors.GRAY}- Installed:{Colors.RESET} {dest / DLL_NAME}") + + print(f"\n{Colors.BOLD}{Colors.BRIGHT_CYAN}{'-'*50}") + print(f"Build Summary - VS {vs_version_name} ({platform_toolset}):") + for cfg, ok, b_time, size, _ in build_results: + color = Colors.GREEN if ok else Colors.RED + tag = "OK " if ok else "FAIL" + print(f" {color}{tag}{Colors.RESET} {cfg.name:15s} - {format_time(b_time):8s} - {size:11,d} bytes") + print(f"{Colors.BOLD}{Colors.BRIGHT_CYAN}{'-'*50}{Colors.RESET}") + print(f"Total time: {format_time(total_time)}\n") + + if args.clean: + clean_build_artifacts(project_dir, configs_to_build) + + return 0 if all_success else 1 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/dist/amd64-unicode/newadvsplash.dll b/dist/amd64-unicode/newadvsplash.dll new file mode 100644 index 0000000..d3b534a Binary files /dev/null and b/dist/amd64-unicode/newadvsplash.dll differ diff --git a/dist/x86-ansi/newadvsplash.dll b/dist/x86-ansi/newadvsplash.dll new file mode 100644 index 0000000..db74861 Binary files /dev/null and b/dist/x86-ansi/newadvsplash.dll differ diff --git a/dist/x86-unicode/newadvsplash.dll b/dist/x86-unicode/newadvsplash.dll new file mode 100644 index 0000000..f0ed47e Binary files /dev/null and b/dist/x86-unicode/newadvsplash.dll differ diff --git a/docs/legacy-vc6/advsplash.dsp b/docs/legacy-vc6/advsplash.dsp new file mode 100644 index 0000000..e61e7ea --- /dev/null +++ b/docs/legacy-vc6/advsplash.dsp @@ -0,0 +1,177 @@ +# Microsoft Developer Studio Project File - Name="advsplash" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=advsplash - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "advsplash.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "advsplash.mak" CFG="advsplash - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "advsplash - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "advsplash - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "advsplash - Win32 Release_Unicode" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "advsplash - Win32 Debug_Unicode" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "advsplash - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /Og /Os /Oy /I "..\ExDll" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /FD /c +# SUBTRACT CPP /Ox /Ow /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib vfw32.lib winmm.lib ole32.lib oleaut32.lib uuid.lib msvcrt.lib /nologo /entry:"DllMain" /subsystem:windows /dll /machine:I386 /nodefaultlib /out:"../../Plugins/newadvsplash.dll" /opt:nowin98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "advsplash - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\ExDll" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib vfw32.lib winmm.lib /nologo /subsystem:windows /dll /debug /machine:I386 /out:"../../Plugins/newadvsplash.dll" /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "advsplash - Win32 Release_Unicode" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release_Unicode" +# PROP BASE Intermediate_Dir "Release_Unicode" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release_Unicode" +# PROP Intermediate_Dir "Release_Unicode" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_UNICODE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /Og /Os /Oy /I "..\ExDll" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /D "_MBCS" /D "_USRDLL" /FD /c +# SUBTRACT CPP /Ox /Ow /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib vfw32.lib winmm.lib ole32.lib oleaut32.lib uuid.lib msvcrt.lib /nologo /entry:"DllMain" /subsystem:windows /dll /machine:I386 /nodefaultlib /out:"../../Unicode/plugins/newadvsplash.dll" /opt:nowin98 +# SUBTRACT LINK32 /pdb:none + +!ELSEIF "$(CFG)" == "advsplash - Win32 Debug_Unicode" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_UNICODE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\ExDll" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_UNICODE" /D "UNICODE" /D "_MBCS" /D "_USRDLL" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib vfw32.lib winmm.lib /nologo /subsystem:windows /dll /debug /machine:I386 /out:"../../Unicode/plugins/newadvsplash.dll" /pdbtype:sept +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "advsplash - Win32 Release" +# Name "advsplash - Win32 Debug" +# Name "advsplash - Win32 Release_Unicode" +# Name "advsplash - Win32 Debug_Unicode" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\advsplash.cpp +# End Source File +# Begin Source File + +SOURCE=.\gif.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\ExDLL\exdll.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/docs/legacy-vc6/advsplash.dsw b/docs/legacy-vc6/advsplash.dsw new file mode 100644 index 0000000..03ad954 --- /dev/null +++ b/docs/legacy-vc6/advsplash.dsw @@ -0,0 +1,28 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "advsplash"=.\advsplash.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### \ No newline at end of file diff --git a/docs/original-readme.txt b/docs/original-readme.txt new file mode 100644 index 0000000..248077d --- /dev/null +++ b/docs/original-readme.txt @@ -0,0 +1,69 @@ +NewAdvSplash.dll - plug-in that lets you throw +up a splash screen in NSIS installers with +fading effects (win2k/xp) and transparency. +At the same time can play wav-mp3 sound file. + +Usage: + +1) newadvsplash::show [/NOUNLOAD] Delay FadeIn FadeOut KeyColor [/BANNER] [/PASSIVE] [/NOCANCEL] FileName + +Delay - time (milliseconds) to show image +FadeIn - time to show the fadein scene +FadeOut - time to show the fadeout scene +KeyColor - color used for transparency, could be any RGB value + (for ex. R=255 G=100 B=16 -> KeyColor=0xFF6410), + use KeyColor=-1 if there is no transparent color at your image. + If KeyColor=-2 and image type is gif, plug-in attempts to extract + transparency color value from the file header. For gif images + transparency on the static background works even if KeyColor=-1. +/BANNER - returns control to installer immediatelly after plug-in + activation. Not depends on installer page - parent window NULL. +/NOCANCEL - disables 'exit on user click' default behaviour. +/PASSIVE - not forces splash window to foreground. +FileName - splash image filename (with extension!). Bmp, gif and jpg + image types are supported. + +For example: + + newadvsplash::show 3000 0 0 -2 "$PLUGINSDIR\catch.gif" +or + newadvsplash::show /NOUNLOAD 1000 600 400 0xFF6410 /BANNER "$TEMP\spltmp.bmp" + + Use /NOUNLOAD with /BANNER key - this case plug-in not waits for + the end of 'show' and returns control to installer. If used in .onInit + function, /BANNER key requires 'ShowWindow $HWNDPARENT 2' in .onGUIInit + + +2) newadvsplash::stop [/WAIT | /FADEOUT] + + Without options terminates banner. + With /WAIT option waits for the end. + With /FADEOUT option forces banner close with fade out effect ('Delay' -> 0). + + +3) newadvsplash::play /NOUNLOAD [/LOOP] FileName + +FileName - sound filename to play (with extension, wav, mp3 ...). +Empty Filename string "" stops sound. +For example: + + newadvsplash::play /NOUNLOAD /LOOP "$PLUGINSDIR\snd.mp3" + newadvsplash::show 2000 1000 500 -1 "$PLUGINSDIR\iamfat.jpg" + + +4) newadvsplash::hwnd /NOUNLOAD + +Gets splash window handle. +For example: + + newadvsplash::hwnd /NOUNLOAD + Pop $0 ; $0 is window handle now + +Compatibility: +Basic - Win95 and later. +Fadein/fadeout - win2k/winxp. + +Original code - Justin +Converted to a plugin DLL by Amir Szekely (kichik) +Fading and transparency by Nik Medved (brainsucker) +Gif and jpeg support, /BANNER and /CANCEL, mp3 support - Takhir Bedertdinov diff --git a/docs/wiki.txt b/docs/wiki.txt new file mode 100644 index 0000000..9b7d98f --- /dev/null +++ b/docs/wiki.txt @@ -0,0 +1,82 @@ +{{PageAuthor|Takhir}} + +== Links == +Download:
+NewAdvSplash.zip
+[http://forums.winamp.com/showthread.php?s=&threadid=197479 Forum thread] + +== Description == + +NewAdvSplash.dll - startup image banner plug-in with fading effects (win2k/xp) and transparency. At the same time can play sound file. On the base of AdvSplash NSIS plug-in with following add-ons: allows background installer initialization (async-banner mode); plays mp3 files; gif, jpeg and some other OleLoadPicture() formats; "passive" mode - not forces spalsh window to foreground; can return splash HWND to work with other plug-ins (Marquee, AnimGif) in the async mode. +Compatibility: basic - Win95 and later, fadein/fadeout - win2k/winxp. + +== Installing == + +Unzip and place the .dll file into the Plugins directory. + +== Entry points and parameters (NSIS script) == + + newadvsplash::show [/NOUNLOAD] Delay FadeIn FadeOut KeyColor [/BANNER] [/PASSIVE] [/NOCANCEL] FileName + +; Delay +: time (milliseconds) to show image + +; FadeIn +: time to show the fadein scene + +; FadeOut +: time to show the fadeout scene + +; KeyColor +: color used for transparency, could be any RGB value (for ex. R=255 G=100 B=16 -> KeyColor=0xFF6410), use KeyColor=-1 if there is no transparent color at your image. If KeyColor=-2 and image type is gif, plug-in attempts to extract transparency color value from the file header. For gif images transparency on the static background works even if KeyColor=-1. + +; /BANNER +: returns control to installer immediatelly after plug-in activation. + +; /NOCANCEL +: disables 'exit on user click' default behaviour. + +; /PASSIVE +: not forces spalsh window to foreground + +; FileName +: splash image filename (with extension!). Bmp, gif and jpg image types supported. + + + newadvsplash::stop [/WAIT | /FADEOUT] + +: For /BANNER mode only! Without options terminates banner. +: With /WAIT option waits for the end. +: With /FADEOUT option forces banner close with fade out effect ('Delay' -> 0). + + + newadvsplash::play /NOUNLOAD [/LOOP] FileName + +; FileName +: sound filename to play (with extension, wav, mp3 ...). Empty Filename string "" stops playing ('show' end does this as well). + +; /LOOP +: loops sound. + + + newadvsplash::hwnd + +: Gets splash window handle (use Pop to take it from stack) + +== Examples == +'Modeless' logo with sound + + newadvsplash::play /NOUNLOAD /LOOP "$PLUGINSDIR\snd.mp3" + newadvsplash::show /NOUNLOAD 2000 1000 500 -2 /BANNER "$PLUGINSDIR\logo.gif" + ; add your initialization code here + newadvsplash::stop /WAIT + + +Simple logo + + newadvsplash::show 2000 1000 500 -1 "$PLUGINSDIR\logo.jpg" + + +No return value. + +[[Category:Plugins]] diff --git a/examples/aist.gif b/examples/aist.gif new file mode 100644 index 0000000..06ded06 Binary files /dev/null and b/examples/aist.gif differ diff --git a/examples/animbanner.nsi b/examples/animbanner.nsi new file mode 100644 index 0000000..1701a70 --- /dev/null +++ b/examples/animbanner.nsi @@ -0,0 +1,51 @@ +;----------------------------- +; Requires AnimGif plug-in + +Name "NewAdvSplash AnimBanner test" +OutFile "animbanner.exe" + +!define IMG_NAME aist.gif + +!include "MUI.nsh" +!define MUI_PAGE_CUSTOMFUNCTION_LEAVE anb +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_INSTFILES +!define MUI_PAGE_CUSTOMFUNCTION_LEAVE bna +!insertmacro MUI_PAGE_FINISH +!insertmacro MUI_LANGUAGE "English" + + +Function anb + + SetOutPath "$PLUGINSDIR" + File ${IMG_NAME} + + newadvsplash::show /NOUNLOAD 2000 1000 500 -1 /BANNER "$PLUGINSDIR\${IMG_NAME}" + newadvsplash::hwnd /NOUNLOAD + Pop $0 + AnimGif::play /NOUNLOAD /hwnd=$0 /bgcol=0xffffff "$PLUGINSDIR\${IMG_NAME}" + +FunctionEnd + +Function bna + + newadvsplash::stop + AnimGif::stop + + Delete "$PLUGINSDIR\${IMG_NAME}" + SetOutPath "$EXEDIR" + +FunctionEnd + +Section + + newadvsplash::stop /wait + AnimGif::stop + Sleep 1000 + newadvsplash::show /NOUNLOAD 2000 1000 500 -1 /BANNER "$PLUGINSDIR\${IMG_NAME}" + newadvsplash::hwnd /NOUNLOAD + Pop $0 + AnimGif::play /NOUNLOAD /hwnd=$0 /bgcol=0xffffff "$PLUGINSDIR\${IMG_NAME}" + Sleep 1000 + +SectionEnd diff --git a/examples/animsplash.nsi b/examples/animsplash.nsi new file mode 100644 index 0000000..900e444 --- /dev/null +++ b/examples/animsplash.nsi @@ -0,0 +1,55 @@ +;----------------------------- +; Requires AnimGif plug-in + +Name "NewAdvSplash AnimSplash test" +OutFile "animsplash.exe" + +!define IMG_NAME aist.gif + +!include "MUI.nsh" +!define MUI_CUSTOMFUNCTION_GUIINIT begin +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_LANGUAGE "English" + + +Function .onInit + +; following 2 lines can minimize all other desktop windows +; FindWindow $0 "Shell_TrayWnd" +; SendMessage $0 ${WM_COMMAND} 415 0 + +; the plugins dir is automatically deleted when the installer exits + InitPluginsDir + SetOutPath "$PLUGINSDIR" + + +; Modeless banner sample: show + wait + File "aist.gif" + newadvsplash::show /NOUNLOAD 2000 1000 500 -1 /BANNER "$PLUGINSDIR\${IMG_NAME}" + newadvsplash::hwnd /NOUNLOAD + Pop $0 + AnimGif::play /NOUNLOAD /hwnd=$0 /bgcol=0xffffff "$PLUGINSDIR\${IMG_NAME}" + +; you can add some background initialization code here (3.5 sec - aist.gif play time) +; wait & stop code also may be placed here, I moved them to .onGUIInit +; as a /BANNER (modeless) sample only. + +FunctionEnd + + +Function begin + + newadvsplash::stop /wait ; waits or exits immediately if 'show' already finished + AnimGif::stop + + Delete "$PLUGINSDIR\${IMG_NAME}" + SetOutPath "$EXEDIR" + +; plug-in requires this to be called in .onGUIInit in the BANNER mode only + ShowWindow $HWNDPARENT ${SW_RESTORE} + +FunctionEnd + + +Section +SectionEnd diff --git a/examples/bannermui.nsi b/examples/bannermui.nsi new file mode 100644 index 0000000..801af49 --- /dev/null +++ b/examples/bannermui.nsi @@ -0,0 +1,43 @@ + +Name "NewAdvSplash BannerMUI test" +OutFile "bannermui.exe" + + +!include "MUI.nsh" +!define MUI_CUSTOMFUNCTION_GUIINIT MUIGUIInit +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_LANGUAGE "English" + +!define IMG_NAME aist.gif + +Function .onInit + +; the plugins dir is automatically deleted when the installer exits + InitPluginsDir + SetOutPath "$PLUGINSDIR" + +; Modeless banner sample: show + wait + File ${IMG_NAME} + newadvsplash::show /NOUNLOAD 2000 1000 500 -2 /BANNER "$PLUGINSDIR\${IMG_NAME}" + Sleep 2000 ; not changes 3.5 sec of 'show time'. add your code instead of sleep + +FunctionEnd + + +; MUI requires custom function definition +Function MUIGUIInit + + newadvsplash::stop /wait ; waits or exits immediately if finished, use just 'stop' to terminate + + Delete "$PLUGINSDIR\${IMG_NAME}" + SetOutPath "$EXEDIR" + +; plug-in requires this to be called in .onGUIInit +; if you use 'show' in the .onInit function with /BANNER key. + ShowWindow $HWNDPARENT ${SW_RESTORE} + +FunctionEnd + + +Section +SectionEnd \ No newline at end of file diff --git a/examples/catch.ver.gif b/examples/catch.ver.gif new file mode 100644 index 0000000..017a015 Binary files /dev/null and b/examples/catch.ver.gif differ diff --git a/examples/example.nsi b/examples/example.nsi new file mode 100644 index 0000000..c5b2425 --- /dev/null +++ b/examples/example.nsi @@ -0,0 +1,51 @@ + +Name "NewAdvSplash.dll test" +OutFile "newadvsplash.exe" + +!define IMG_NAME1 splash.bmp +!define IMG_NAME2 aist.gif + +!include WinMessages.nsh + + +Function .onInit + +; the plugins dir is automatically deleted when the installer exits + InitPluginsDir + SetOutPath "$PLUGINSDIR" + +; optional - mp3, use /LOOP key for 'non-stop' +# File "09.mp3" +# newadvsplash::play /NOUNLOAD "$PLUGINSDIR\09.mp3" + +; Modal banner sample: show + File "/oname=$PLUGINSDIR\${IMG_NAME1}" "${NSISDIR}\Contrib\Graphics\Wizard\llama.bmp" + newadvsplash::show 1000 100 500 0x04025C /NOCANCEL "$PLUGINSDIR\${IMG_NAME1}" + Delete "$PLUGINSDIR\${IMG_NAME1}" + Sleep 500 ; optional + +; Modeless banner sample: show + wait + File ${IMG_NAME2} + newadvsplash::show /NOUNLOAD 2000 1000 500 -2 /BANNER "$PLUGINSDIR\${IMG_NAME2}" + Sleep 2000 ; not changes 3.5 sec of 'show time'. add your code instead of sleep + +FunctionEnd + + +; MUI requires custom function definition +Function .onGUIInit + + newadvsplash::stop /wait ; waits or exits immediately if finished, remove /wait to terminate now + + Delete "$PLUGINSDIR\${IMG_NAME2}" + SetOutPath "$EXEDIR" + +; plug-in requires this to be called in .onGUIInit +; if you use 'show' in the .onInit function with /BANNER key. + ShowWindow $HWNDPARENT ${SW_RESTORE} + +FunctionEnd + + +Section +SectionEnd \ No newline at end of file diff --git a/examples/stopfade.nsi b/examples/stopfade.nsi new file mode 100644 index 0000000..da05f1e --- /dev/null +++ b/examples/stopfade.nsi @@ -0,0 +1,34 @@ + +Name "NewAdvSplash.dll StopFade test" +OutFile "stopfade.exe" + +!define IMG_NAME2 aist.gif + +!include WinMessages.nsh + + +; MUI requires custom function definition +Function .onGUIInit + + InitPluginsDir + SetOutPath "$PLUGINSDIR" + + +; Modeless banner with variable initialization time - up to 10 sec +; uses 'soft' stop() with fade out + + File ${IMG_NAME2} + newadvsplash::show /NOUNLOAD 10000 1000 500 -2 /BANNER "$PLUGINSDIR\${IMG_NAME2}" + Sleep 2000 ; add here your initialization code (up to 11 sec) instead of sleep + newadvsplash::stop /fadeout +# newadvsplash::stop /wait +# newadvsplash::stop + Delete "$PLUGINSDIR\${IMG_NAME2}" + SetOutPath "$EXEDIR" + +FunctionEnd + + + +Section +SectionEnd \ No newline at end of file diff --git a/examples/version.nsi b/examples/version.nsi new file mode 100644 index 0000000..5badae7 --- /dev/null +++ b/examples/version.nsi @@ -0,0 +1,38 @@ + +; Requires marquee plug-in +; Displays text on the splash window + +!define IMG_NAME catch.ver.gif + +Name "NewAdvSplash Version test" +OutFile "version.exe" + +!include WinMessages.nsh + +Function .onInit + + InitPluginsDir + SetOutPath "$PLUGINSDIR" + + File "${IMG_NAME}" + newadvsplash::show /NOUNLOAD 3000 1000 500 -2 /BANNER /NOCANCEL "$PLUGINSDIR\${IMG_NAME}" + newadvsplash::hwnd /NOUNLOAD + Pop $0 + Sleep 100 ; to make window visible - this case you can skip /GCOL=00ff00 parameter + marquee::start /NOUNLOAD /left=73 /right=2 /COLOR=ff0000 /hwnd=$0 /step=0 /interval=100 /top=15 /height=35 /width=17 /start=right "v. 3.17" + +FunctionEnd + +Function .onGUIInit + + newadvsplash::stop /wait + marquee::stop + Delete "$PLUGINSDIR\${IMG_NAME}" + SetOutPath "$EXEDIR" ; after this system can remove PLUGINSDIR + ShowWindow $HWNDPARENT ${SW_RESTORE} + +FunctionEnd + + +Section +SectionEnd \ No newline at end of file diff --git a/src/NewAdvSplash.vcxproj b/src/NewAdvSplash.vcxproj new file mode 100644 index 0000000..3e70f48 --- /dev/null +++ b/src/NewAdvSplash.vcxproj @@ -0,0 +1,186 @@ + + + + + Release + Win32 + + + Release Unicode + Win32 + + + Release + x64 + + + Release Unicode + x64 + + + + 17.0 + {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942} + Win32Proj + NewAdvSplash + 10.0 + + + + DynamicLibrary + false + v143 + true + MultiByte + + + DynamicLibrary + false + v143 + true + Unicode + + + DynamicLibrary + false + v143 + true + MultiByte + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + true + false + + WIN32;NDEBUG;_WINDOWS;_USRDLL;EXDLL_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + MultiThreaded + MaxSpeed + + + Windows + true + true + false + + kernel32.lib;user32.lib;gdi32.lib;ole32.lib;vfw32.lib;winmm.lib;%(AdditionalDependencies) + + + + + + + Level3 + true + true + false + + WIN32;NDEBUG;_WINDOWS;_USRDLL;EXDLL_EXPORTS;UNICODE;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + MultiThreaded + MaxSpeed + + + Windows + true + true + false + + kernel32.lib;user32.lib;gdi32.lib;ole32.lib;vfw32.lib;winmm.lib;%(AdditionalDependencies) + + + + + + + Level3 + true + true + false + + NDEBUG;_WINDOWS;_USRDLL;EXDLL_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + MultiThreaded + MaxSpeed + + + Windows + true + true + false + + kernel32.lib;user32.lib;gdi32.lib;ole32.lib;vfw32.lib;winmm.lib;%(AdditionalDependencies) + + + + + + + Level3 + true + true + false + + NDEBUG;_WINDOWS;_USRDLL;EXDLL_EXPORTS;UNICODE;_UNICODE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + MultiThreaded + MaxSpeed + + + Windows + true + true + false + + kernel32.lib;user32.lib;gdi32.lib;ole32.lib;vfw32.lib;winmm.lib;%(AdditionalDependencies) + + + + + + + + + + + + \ No newline at end of file diff --git a/src/advsplash.cpp b/src/advsplash.cpp new file mode 100644 index 0000000..06fa556 --- /dev/null +++ b/src/advsplash.cpp @@ -0,0 +1,539 @@ +/*************************************************** + * FILE NAME: advsplash.cpp + * + * Copyright 2003 - Present NSIS + * + * PURPOSE: + * Splash screen in NSIS installers with fading + * and transparency effects + * + * CHANGE HISTORY + * + * Author Date Modifications + * + * Justin Original + * Amir Szekely (kichik) Converted to a plugin DLL + * Nik Medved (brainsucker) Fading and transparency by + * Takhir Bedertdinov + * 10-26-2004 Gif and jpeg support + * 06-25-2005 modeless mode + * 08-30-2005 hwnd entry point for AnimGif + * 10-16-2005 "no transparency" bug for white bg images fixed + * 03-04-2006 /PASSIVE mode + * 04-04-2006 NULL parent HWND for /BANNER mode + * hbatista + * 04-05-2007 Unregister window on 'stop' + * Takhir + * 11-20-2007 "stop /fadeout" option - decreases sleep time + * "stop /wait" fully replaces ::wait call + **************************************************/ + +// For layered windows +#define _WIN32_WINNT 0x0500 + +#include +#include +#include +#include +#include +#include +#include "exdll.h" + +#ifndef WS_EX_LAYERED +#define WS_EX_LAYERED 0x00080000 +#define LWA_COLORKEY 0x00000001 +#define LWA_ALPHA 0x00000002 +#endif // ndef WS_EX_LAYERED + +#define RESOLUTION 32 // 32 ms ~ 30 fps ;) (32? I like SHR more than iDIV ;) +#define MAX_SHOW_TIME 60000 + +enum FADE_STATES +{ + FADE_IN, + FADE_SLEEP, + FADE_OUT +}; + +const TCHAR classname[4] = _T("_sp"); +HWND myWnd = NULL; +HINSTANCE g_hInstance; +HANDLE hThread = NULL; +BITMAP bm; +HBITMAP g_hbm; +int resolution = RESOLUTION; +int sleep_val, fadein_val, fadeout_val, state, timeleft, keycolor, alphaparam; +LPPICTURE pIPicture = NULL; +bool modal = true, nocancel = false, setfg = true; +UINT timerEvent; +static WNDCLASS wc; + +typedef BOOL(_stdcall *_tSetLayeredWindowAttributesProc)(HWND hwnd, // handle to the layered window + COLORREF crKey, // specifies the color key + BYTE bAlpha, // value for the blend function + DWORD dwFlags // action +); +_tSetLayeredWindowAttributesProc SetLayeredWindowAttributesProc; + +int getTransparencyColor(TCHAR *fn_src); + +void sf(HWND hw) +{ + DWORD ctid = GetCurrentThreadId(); + DWORD ftid = GetWindowThreadProcessId(GetForegroundWindow(), NULL); + AttachThreadInput(ftid, ctid, TRUE); + SetForegroundWindow(hw); + AttachThreadInput(ftid, ctid, FALSE); +} + +/***************************************************** + * FUNCTION NAME: WndProc() + * PURPOSE: + * window proc. + * SPECIAL CONSIDERATIONS: + * for paint purposes mainly + *****************************************************/ +static LRESULT CALLBACK WndProc(HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) +{ + PAINTSTRUCT ps; + RECT r; + long w, h; + + switch (uMsg) + { + case WM_CREATE: + SystemParametersInfo(SPI_GETWORKAREA, 0, &r, 0); + SetWindowLong(hwnd, GWL_STYLE, 0); + SetWindowPos(hwnd, NULL, + r.left + (r.right - r.left - bm.bmWidth) / 2, + r.top + (r.bottom - r.top - bm.bmHeight) / 2, + bm.bmWidth, bm.bmHeight, + SWP_NOZORDER | SWP_SHOWWINDOW); + return 0; + + case WM_PAINT: + BeginPaint(hwnd, &ps); + if (pIPicture != NULL) + { + GetClientRect(hwnd, &r); + pIPicture->get_Width(&w); + pIPicture->get_Height(&h); + pIPicture->Render(ps.hdc, r.left, + r.top, r.right - r.left, r.bottom - r.top, 0, h, w, -h, &r); + } + EndPaint(hwnd, &ps); + + case WM_CLOSE: + return 0; + + case WM_LBUTTONDOWN: + if (nocancel) + break; + case WM_TIMER: + timeKillEvent(timerEvent); + if (setfg) + sf(hwnd); + PlaySound(0, 0, 0); + DestroyWindow(hwnd); + break; + } + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +/***************************************************** + * FUNCTION NAME: SetTransparentRegion() + * PURPOSE: + * creates window region + * SPECIAL CONSIDERATIONS: + * kind of skin creation from bmp + *****************************************************/ +void SetTransparentRegion(HWND myWnd) +{ + HDC dc; + int x, y; + HRGN region, cutrgn; + BITMAPINFO bmi = {{sizeof(BITMAPINFOHEADER), bm.bmWidth, bm.bmHeight, 1, 32, BI_RGB, 0, 0, 0, 0, 0}}; + int size = bm.bmWidth * bm.bmHeight * 4; + int *pbmp = (int *)GlobalAlloc(GPTR, size), + *bmp; + + bmp = pbmp; + dc = CreateCompatibleDC(NULL); + SelectObject(dc, g_hbm); + + x = GetDIBits(dc, g_hbm, 0, bm.bmHeight, bmp, &bmi, DIB_RGB_COLORS); + region = CreateRectRgn(0, 0, bm.bmWidth, bm.bmHeight); + + // Search for transparent pixels + for (y = bm.bmHeight - 1; y >= 0; y--) + for (x = 0; x < bm.bmWidth;) + if ((*bmp & 0xFFFFFF) == keycolor) + { + int j = x; + while ((x < bm.bmWidth) && ((*bmp & 0xFFFFFF) == keycolor)) + bmp++, x++; + + // Cut transparent pixels from the original region + cutrgn = CreateRectRgn(j, y, x, y + 1); + CombineRgn(region, region, cutrgn, RGN_XOR); + DeleteObject(cutrgn); + } + else + bmp++, x++; + + // Set resulting region. + SetWindowRgn(myWnd, region, TRUE); + DeleteObject(region); + DeleteObject(dc); + GlobalFree(pbmp); +} + +/***************************************************** + * FUNCTION NAME: TimeProc() + * PURPOSE: + * fade in/out tracking + * SPECIAL CONSIDERATIONS: + * works without WM_TIMER + *****************************************************/ +void CALLBACK TimeProc(UINT uID, + UINT uMsg, + DWORD_PTR dwUser, + DWORD_PTR dw1, + DWORD_PTR dw2) +{ + int call = -1; + + switch (state) + { + case FADE_IN: + if (timeleft == 0) + { + timeleft = sleep_val; + state++; + if (SetLayeredWindowAttributesProc != NULL) + call = 255; + } + else + { + call = ((fadein_val - timeleft) * 255) / fadein_val; + break; + } + case FADE_SLEEP: + if (timeleft == 0) + { + timeleft = fadeout_val; + state++; + } + else + break; + case FADE_OUT: + if (timeleft == 0) + { + PostMessage((HWND)dwUser, WM_TIMER, 0, 0); + return; + } + else + { + call = ((timeleft) * 255) / fadeout_val; + break; + } + } + // Transparency value aquired, and could be set... + if ((call >= 0) && SetLayeredWindowAttributesProc != NULL) + SetLayeredWindowAttributesProc((HWND)dwUser, keycolor, + call, + alphaparam); + // Time is running out... + timeleft--; +} + +/***************************************************** + * FUNCTION NAME: play() + * PURPOSE: + * PlaySound dll entry point + * SPECIAL CONSIDERATIONS: + * makes PlaySound working on Win98 + * works with /NOUNLOAD parameter only + * wav files are huge :-( + * but not flushes on the screen like mci window does + * Use it BEFORE 'show' call + *****************************************************/ +extern "C" void __declspec(dllexport) play(HWND hwndParent, + int string_size, + TCHAR *variables, + stack_t **stacktop) +{ + TCHAR fn[MAX_PATH]; + DWORD snd = SND_FILENAME | SND_ASYNC | SND_NODEFAULT; + EXDLL_INIT(); + if (popstring(fn) == 0) + { + if (lstrcmpi(fn, _T("/loop")) == 0) + { + snd |= SND_LOOP; + *fn = 0; + popstring(fn); + } + if (*fn == 0) + { + PlaySound(NULL, NULL, 0); + } + else + { + PlaySound(fn, NULL, snd); + } + } +} + +/***************************************************** + * FUNCTION NAME: splashThread() + * PURPOSE: + * modal/modeless banner presentation + * SPECIAL CONSIDERATIONS: + * from CreateThread runs window "detached", with NULL + * parent, otherwise uses current page as parent + *****************************************************/ +DWORD WINAPI splashThread(LPVOID lpParameter) +{ + MSG msg; + HWND hParent = (HWND)lpParameter; + wc.lpfnWndProc = WndProc; + wc.hInstance = g_hInstance; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.lpszClassName = classname; + + if (RegisterClass(&wc)) + { + pIPicture->get_Handle((OLE_HANDLE *)&g_hbm); + GetObject((HGDIOBJ)g_hbm, sizeof(BITMAP), &bm); + + myWnd = CreateWindowEx(WS_EX_TOOLWINDOW | (setfg ? WS_EX_TOPMOST : 0) | + (SetLayeredWindowAttributesProc != NULL ? WS_EX_LAYERED : 0), + classname, classname, 0, 0, 0, 0, 0, + hParent, NULL, g_hInstance, NULL); + // Set transparency / key color + if (SetLayeredWindowAttributesProc != NULL) + SetLayeredWindowAttributesProc(myWnd, keycolor, + fadein_val > 0 ? 0 : 255, + alphaparam); + if (keycolor != -1) + SetTransparentRegion(myWnd); + + // Start up timer... + state = FADE_IN; + timeleft = fadein_val; + timerEvent = timeSetEvent(resolution, RESOLUTION / 4, TimeProc, + (DWORD_PTR)myWnd, TIME_PERIODIC); + + while (IsWindow(myWnd) && GetMessage(&msg, myWnd, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + pIPicture->Release(); + pIPicture = NULL; // do not re-use with /NOUNLOAD + } + + myWnd = NULL; + // Stop currently playing wave, we want to exit + PlaySound(0, 0, 0); + + return 0; +} + +/***************************************************** + * FUNCTION NAME: show() + * PURPOSE: + * image banner "show" dll entry point + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +extern "C" void __declspec(dllexport) show(HWND hwndParent, + int string_size, + TCHAR *variables, + stack_t **stacktop) +{ + DEVMODE dm; + TCHAR fn[MAX_PATH]; + WCHAR wcPort[MAX_PATH]; + DWORD dwThreadId; + DWORD dwMainThreadId = GetCurrentThreadId(); + + EXDLL_INIT(); + + // 3 fade parameters and keycolor from stack come first + popstring(fn); + sleep_val = _tcstol(fn, NULL, 0); + popstring(fn); + fadein_val = _tcstol(fn, NULL, 0); + popstring(fn); + fadeout_val = _tcstol(fn, NULL, 0); + popstring(fn); + keycolor = _tcstol(fn, NULL, 0); + + // other parameters (if any) + filename + while (!popstring(fn) && fn[0] == _T('/')) + { + if (lstrcmpi(fn, _T("/nocancel")) == 0) + nocancel = true; + else if (lstrcmpi(fn, _T("/passive")) == 0) + setfg = false; + else + modal = false; + } + + if (keycolor == -2) + keycolor = getTransparencyColor(fn); + + // Check for winXP/2k at 32 bpp transparency support + dm.dmSize = sizeof(DEVMODE); + EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm); + if (dm.dmBitsPerPel >= 32) + { + HANDLE user32 = GetModuleHandle(_T("user32")); + SetLayeredWindowAttributesProc = (_tSetLayeredWindowAttributesProc) + GetProcAddress((HINSTANCE)user32, "SetLayeredWindowAttributes"); + } + + if (SetLayeredWindowAttributesProc == NULL) + { + // Fading+transparency is unsupported at old windows versions... + resolution = (sleep_val + fadein_val + fadeout_val) / 2; + fadeout_val = fadein_val = 0; + sleep_val = 1; + } + else + { + // div them by resolution - now times are in 32 ms "frames" + sleep_val >>= 5; + fadein_val >>= 5; + fadeout_val >>= 5; + // MS RGB - BGR format + alphaparam = LWA_ALPHA | (keycolor == -1 ? 0 : LWA_COLORKEY); + if (keycolor != -1) + keycolor = ((keycolor & 0xFF) << 16) + (keycolor & 0xFF00) + + ((keycolor & 0xFF0000) >> 16); + } + +// who wrote this script :-)) +#ifdef _UNICODE + lstrcpy(wcPort, fn); +#else + MultiByteToWideChar(CP_ACP, 0, fn, -1, wcPort, sizeof(wcPort) / 2); +#endif + OleLoadPicturePath((LPOLESTR)wcPort, 0, 0, 0, IID_IPicture, (void **)&pIPicture); + if (pIPicture == NULL || + (sleep_val + fadein_val + fadeout_val) <= 0) + return; + + if (modal) + { + splashThread((void *)hwndParent); + // We should UnRegister class, since Windows NT series never does this by itself + UnregisterClass(wc.lpszClassName, g_hInstance); + } + else + { + // start thread and wait for window initialization + hThread = CreateThread(0, 0, splashThread, NULL, 0, &dwThreadId); + while (myWnd == NULL || !IsWindowVisible(myWnd)) + Sleep(10); + if (setfg) + sf(myWnd); + } +} + +/***************************************************** + * FUNCTION NAME: stop() + * PURPOSE: + * image banner "stop" dll entry point + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +extern "C" void __declspec(dllexport) stop(HWND hwndParent, + int string_size, + TCHAR *variables, + stack_t **stacktop) +{ + TCHAR cmd[64]; + if (!modal && hThread != NULL) + { + if (popstring(cmd) == 0) + { + if (lstrcmpi(cmd, _T("/fadeout")) == 0) + { + if (SetLayeredWindowAttributesProc == NULL) + stop(hwndParent, string_size, variables, stacktop); + else + { + if (state == FADE_IN) + { + sleep_val = 1; // minimize sleep step - 1 frame only + } + else if (state == FADE_SLEEP) + { + timeleft = 1; // force sleep to finish + } + WaitForSingleObject(hThread, (fadein_val + fadeout_val + 3) * resolution); + } + } + if (lstrcmpi(cmd, _T("/wait")) == 0) + { + WaitForSingleObject(hThread, + resolution * (SetLayeredWindowAttributesProc == NULL ? 2 : fadein_val + fadeout_val + sleep_val + 3)); + } + else + pushstring(cmd); // this is not our string + } + if (IsWindow(myWnd)) + { + SendMessage(myWnd, WM_TIMER, 0, 0); // stops sound and destroys window + } + WaitForSingleObject(hThread, 1000); + CloseHandle(hThread); + hThread = NULL; + + // hbatista + UnregisterClass(wc.lpszClassName, g_hInstance); + Sleep(10); + if (setfg) + sf(hwndParent); + } +} + +/***************************************************** + * FUNCTION NAME: hwnd() + * PURPOSE: + * retrieves target window handle + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +extern "C" void __declspec(dllexport) hwnd(HWND hwndParent, + int string_size, + TCHAR *variables, + stack_t **stacktop) +{ + TCHAR s[16] = _T(""); + if (IsWindow(myWnd)) + wsprintf(s, _T("%d"), myWnd); + pushstring(s); +} + +/***************************************************** + * FUNCTION NAME: DllMain() + * PURPOSE: + * Dll main (initialization) entry point + * SPECIAL CONSIDERATIONS: + * + *****************************************************/ +BOOL WINAPI DllMain(HANDLE hInst, + ULONG ul_reason_for_call, + LPVOID lpReserved) +{ + g_hInstance = (HINSTANCE)hInst; + return TRUE; +} diff --git a/src/exdll.h b/src/exdll.h new file mode 100644 index 0000000..1762547 --- /dev/null +++ b/src/exdll.h @@ -0,0 +1,99 @@ +#ifndef _EXDLL_H_ +#define _EXDLL_H_ + +#include + +// only include this file from one place in your DLL. +// (it is all static, if you use it in two places it will fail) + +#define EXDLL_INIT() \ + { \ + g_stringsize = string_size; \ + g_stacktop = stacktop; \ + g_variables = variables; \ + } + +// For page showing plug-ins +#define WM_NOTIFY_OUTER_NEXT (WM_USER + 0x8) +#define WM_NOTIFY_CUSTOM_READY (WM_USER + 0xd) +#define NOTIFY_BYE_BYE 'x' + +typedef struct _stack_t +{ + struct _stack_t *next; + TCHAR text[1]; // this should be the length of string_size +} stack_t; + +static unsigned int g_stringsize; +static stack_t **g_stacktop; +static TCHAR *g_variables; + +enum +{ + INST_0, // $0 + INST_1, // $1 + INST_2, // $2 + INST_3, // $3 + INST_4, // $4 + INST_5, // $5 + INST_6, // $6 + INST_7, // $7 + INST_8, // $8 + INST_9, // $9 + INST_R0, // $R0 + INST_R1, // $R1 + INST_R2, // $R2 + INST_R3, // $R3 + INST_R4, // $R4 + INST_R5, // $R5 + INST_R6, // $R6 + INST_R7, // $R7 + INST_R8, // $R8 + INST_R9, // $R9 + INST_CMDLINE, // $CMDLINE + INST_INSTDIR, // $INSTDIR + INST_OUTDIR, // $OUTDIR + INST_EXEDIR, // $EXEDIR + INST_LANG, // $LANGUAGE + __INST_LAST +}; + +// utility functions (not required but often useful) +static int __stdcall popstring(TCHAR *str) +{ + stack_t *th; + if (!g_stacktop || !*g_stacktop) + return 1; + th = (*g_stacktop); + lstrcpy(str, th->text); + *g_stacktop = th->next; + GlobalFree((HGLOBAL)th); + return 0; +} + +static void __stdcall pushstring(const TCHAR *str) +{ + stack_t *th; + if (!g_stacktop) + return; + th = (stack_t *)GlobalAlloc(GPTR, sizeof(stack_t) + g_stringsize * sizeof(TCHAR)); + lstrcpyn(th->text, str, g_stringsize); + th->next = *g_stacktop; + *g_stacktop = th; +} + +static TCHAR *__stdcall getuservariable(int varnum) +{ + if (varnum < 0 || varnum >= __INST_LAST) + return NULL; + return g_variables + varnum * g_stringsize; +} + +static void __stdcall setuservariable(int varnum, const TCHAR *var) +{ + if (varnum < 0 || varnum >= __INST_LAST) + return; + lstrcpy(g_variables + varnum * g_stringsize, var); +} + +#endif //_EXDLL_H_ diff --git a/src/gif.cpp b/src/gif.cpp new file mode 100644 index 0000000..55ec3c5 --- /dev/null +++ b/src/gif.cpp @@ -0,0 +1,257 @@ +#include +#include +#include +#include +#include +#include + + +// My gratitute to Andy Key. His GBM lib served as a base +// for this short gif decoder. I only added extraction +// of transparansy color and comments. +// Takhir + +#define byte unsigned char +#define word unsigned short + +#define make_word(a,b) (((word)a) + (((word)b) << 8)) + + +typedef int GBM_ERR; + +#define GBM_ERR_GIF_BPP ((GBM_ERR) 1100) +#define GBM_ERR_GIF_TERM ((GBM_ERR) 1101) +#define GBM_ERR_GIF_CODE_SIZE ((GBM_ERR) 1102) +#define GBM_ERR_GIF_CORRUPT ((GBM_ERR) 1103) +#define GBM_ERR_GIF_HEADER ((GBM_ERR) 1104) + +#define GBM_ERR_OK ((GBM_ERR) 0) +#define GBM_ERR_BAD_MAGIC ((GBM_ERR) 5) +#define GBM_ERR_READ ((GBM_ERR) 7) +#define GBM_ERR_WRITE ((GBM_ERR) 8) + + + +typedef unsigned int cword; + +typedef struct + { + BOOLEAN ilace, errok; + int bpp; + byte pal[0x100*3]; + } GIF_PRIV; + + +typedef struct + { + int fd; /* File descriptor to read */ + int inx, size; /* Index and size in bits */ + byte buf[255+3]; /* Buffer holding bits */ + int code_size; /* Number of bits to return at once */ + cword read_mask; /* 2^code_size-1 */ + } READ_CONTEXT; + +typedef struct + { + int x, y, w, h, bpp, pass; + BOOLEAN ilace; + int stride; + byte *data, *data_this_line; + } OUTPUT_CONTEXT; + +typedef unsigned int cword; + +typedef struct { byte r, g, b; } GBMRGB; + +#define PRIV_SIZE 2000 + +typedef struct + { + int w, h, bpp; /* Bitmap dimensions */ + byte priv[PRIV_SIZE]; /* Private internal buffer */ + int trc; + char *szUrl; + } GBM; + + + + +/*...sgif_rhdr:0:*/ +GBM_ERR gif_rhdr(int fd, GBM *gbm) + { + GIF_PRIV *gif_priv = (GIF_PRIV *) gbm->priv; + byte signiture[6], scn_desc[7], image_desc[10]; + int img = -1, img_want = 0; + int bits_gct; + + /* Read and validate signiture block */ + + if ( _read(fd, signiture, 6) != 6 ) + return GBM_ERR_READ; + if ( memcmp(signiture, "GIF87a", 6) && + memcmp(signiture, "GIF89a", 6) ) + return GBM_ERR_BAD_MAGIC; + + /* Read screen descriptor */ + + if ( _read(fd, scn_desc, 7) != 7 ) + return GBM_ERR_READ; + bits_gct = gif_priv->bpp = (scn_desc[4] & 7) + 1; +// bits_gct = ((scn_desc[4] & 0x70) >> 4) + 1; + + if ( scn_desc[4] & 0x80 ) + /* Global colour table follows screen descriptor */ + { + if ( _read(fd, gif_priv->pal, 3 << bits_gct) != (3 << bits_gct) ) + return GBM_ERR_READ; + } + else + /* Blank out palette, but make entry 1 white */ + { + memset(gif_priv->pal, 0, 3 << bits_gct); + gif_priv->pal[3] = + gif_priv->pal[4] = + gif_priv->pal[5] = 0xff; + } + + /* Expected image descriptors / extension blocks / terminator */ + + while ( img < img_want ) + { + if ( _read(fd, image_desc, 1) != 1 ) + return GBM_ERR_READ; + switch ( image_desc[0] ) + { +/*...s0x2c \45\ image descriptor:24:*/ +case 0x2c: + if ( _read(fd, image_desc + 1, 9) != 9 ) + return GBM_ERR_READ; + gbm->w = make_word(image_desc[5], image_desc[6]); + gbm->h = make_word(image_desc[7], image_desc[8]); + gif_priv->ilace = ( (image_desc[9] & 0x40) != 0 ); + + if ( image_desc[9] & 0x80 ) + /* Local colour table follows */ + { + gif_priv->bpp = (image_desc[9] & 7) + 1; + if ( _read(fd, gif_priv->pal, 3 << gif_priv->bpp) != (3 << gif_priv->bpp) ) + return GBM_ERR_READ; + } + + if ( ++img != img_want ) + /* Skip the image data */ + { + byte code_size, block_size; + + if ( _read(fd, &code_size, 1) != 1 ) + return GBM_ERR_READ; + do + { + if ( _read(fd, &block_size, 1) != 1 ) + return GBM_ERR_READ; + _lseek(fd, block_size, SEEK_CUR); + } + while ( block_size ); + } + + break; +/*...e*/ +/*...s0x21 \45\ extension block:24:*/ +/* Ignore all extension blocks */ + +case 0x21: + { +/* Graphics and Comment ControlExtensions added by Takhir */ + byte func_code, byte_count, rslt; + if ( _read(fd, &func_code, 1) != 1 ) + return GBM_ERR_READ; + _read(fd, &byte_count, 1); + switch (func_code) + { + case 0xF9: // GraphicsControlExtensions + byte_count++; + for(;byte_count>0;byte_count--) + { + if ( _read(fd, &rslt, 1) != 1 ) + return GBM_ERR_READ; + if((byte_count==5)&&(rslt&1)) gbm->trc=1; + if((byte_count==2)&&(gbm->trc==1)) + gbm->trc=rslt; + } + break; + case 0xFE: // CommentControlExtensions + if(gbm->szUrl==NULL) + _lseek(fd, byte_count+1, SEEK_CUR); + else + { + gbm->szUrl[0] = 0; + if (_read(fd, gbm->szUrl, byte_count+1) != ((int)byte_count+1) ) + return GBM_ERR_READ; + } + break; + default: // Other Extensions + do + { _lseek(fd, byte_count, SEEK_CUR); + if ( _read(fd, &byte_count, 1) != 1 ) + return GBM_ERR_READ; + } + while ( byte_count ); + } +/* end of changes */ + } + break; +/*...e*/ +/*...s0x3b \45\ terminator:24:*/ +/* Oi, we were hoping to get an image descriptor! */ + +case 0x3b: + return GBM_ERR_GIF_TERM; +/*...e*/ +/*...sdefault:24:*/ +default: + return GBM_ERR_GIF_HEADER; +/*...e*/ + } + } + + switch ( gif_priv->bpp ) + { + case 1: gbm->bpp = 1; break; + case 2: + case 3: + case 4: gbm->bpp = 4; break; + case 5: + case 6: + case 7: + case 8: gbm->bpp = 8; break; + default: return GBM_ERR_GIF_BPP; + } + + return GBM_ERR_OK; + } + +/*...e*/ +/*...sgif_rdata:0:*/ +/*...sread context:0:*/ + + +int getTransparencyColor(TCHAR* fn_src) +{ + int fd; + GBM gbm; + GBMRGB *gbmrgb = (GBMRGB*)(gbm.priv + 8); + + if(lstrcmpi(fn_src + lstrlen(fn_src) - 4, _T(".gif")) != 0) return -1; + gbm.trc = -1; + gbm.szUrl = NULL; + if ((fd = _topen(fn_src, O_RDONLY | O_BINARY))==-1) return 0; + gif_rhdr( fd, &gbm); + _close(fd); + if(gbm.trc == -1 || gbm.trc > 255) return -1; + fd = 0; + memcpy(&fd, &gbmrgb[gbm.trc], 3); + /*char b[64]; + wsprintf(b, "Index %d, color=0x%06x", gbm.trc, fd); + MessageBox(NULL, b, "Index & color", 0);*/ + return fd; +}