commit 0947690677bf9f696a768cf26a898dc0239a2cf8 Author: Simone Date: Wed Apr 29 23:19:40 2026 +0200 initial: nsJson plugin 1.1.3 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..94b31f1 --- /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-nsjson/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-nsjson/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..cba4853 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,87 @@ +**1.1.1.1** - 10th October 2024 +- When deleting an element from an array, NSIS no longer crashes (was only working for the last element of an array before) +- Github migration (repository maintained by Pieter Dewachter) + +**1.1.1.0** - 21st November 2017 +- Fixed JSON with syntax errors still being parsed without setting the error flag. +- Fixed Set function not replacing the root value if the value was an array. +- Fixed Delete function not deleting the root node and tree. + +**1.1.0.9** - 9th August 2017 +- Fixed access violation and stack overflow crashes in JSON_Delete, which could occur on plug-in unload, Delete function call or when overwriting existing nodes with the Set function. +- Fixed DoHttpWebRequest failing when POST data exceeded 2048 characters. +- Fixed DoCreateProcess failing to read process output when the output exceeded 1024 characters. +- Delete function will now delete a complete node tree if no path is given. + +**1.1.0.8** - 8th January 2017 +- Added ErrorCode and ErrorMessage for HTTP requests for getting WinINet or Win32 errors. +- Added Sort function. + +**1.1.0.7** - 7th November 2016 +- Fixed /index for adding array elements with the Set function. +- Fixed file name being left on the stack after Serialize with /file. +- Added access type, proxy, authentication and timeout options for HTTP requests. +- Added POST data encoding options for HTTP requests. +- HTTP request headers can be given as raw headers or as JSON key/value pairs. +- Fixed a memory leak in JSON_SerializeAlloc and EscapePostData. +- POST parameters given as a JSON array was not escaped using EscapePostData. + +**1.1.0.6** - 14th October 2016 +- Output and ExitCode/StatusCode are always cleared for /exec and /http calls on the Set function. + +**1.1.0.5** - 10th August 2016 +- Empty value keys ("") were not included in serialized JSON. +- Serialize function popped one extra value from the stack. + +**1.1.0.4** - 7th June 2016 +- Fixed incorrect flags used for HTTP requests. +- Added support for negative indices. +- Added console application execution for input and output. +- Added support for asynchronous HTTP requests and console application execution. + +**1.1.0.3** - 4th March 2016 +- Fixed crash for the Delete function when specifying a tree/path that does not exist. +- Fixed Unicode build. +- Added /keys switch to Get function. + +**1.1.0.2** - 9th December 2015 +- Get function /type switch returns an empty string if the node does not exist. +- Added /always switch to Quote function which surrounds the input string in quotes even if it is already quoted. + +**1.1.0.1** - 23rd November 2015 +- Added Quote function. +- Added amd64-unicode build. + +**1.1.0.0** - 19th April 2015 +- Support for multiple JSON trees. +- JSON via HTTP web requests. + +**1.0.1.3** - 18th October 2014 +- Added UTF-16LE BOM, UTF-16BE BOM and UTF-8 signature detection for input files (with UTF-16BE conversion to UTF-16LE). +- Fixed formatting errors for the Serialize function. +- Fixed closing bracket or curly brace not being included on Serialize to stack when not using /format. +- Moved plug-in DLLs to x86-ansi and x86-unicode respectively for NSIS 3.0. + +**1.0.1.2** - 12th July 2014 +- Fixed crash on serialization to file for node values larger than 64KB. +- Fixed crash on serialization to stack for JSON larger than NSIS_MAX_STRLEN. The JSON will now be truncated. + +**1.0.1.1** - 29th March 2014 +- Fixed incorrect handling of escape character (\). + +**1.0.1.0** - 28th August 2012 +- Added /unicode switch to the Serialize function. Output files for both plug-in builds are now encoded in ANSI by default. +- Removed the Parse function in favour of Set /file [/unicode]. +- Added /type, /key, /exists, /count, /isempty to the Get function. +- Added /index switch for referencing nodes by index. + +**1.0.0.2** - 15th August 2012 +- Fixed Unicode build parsing and serializing. + +**1.0.0.1** - 1st July 2012 +- Fixed parsing of single digit numbers. +- Fixed Serialize not writing the output file when the stack isn't + empty. + +**1.0.0.0** - 25th June 2012 +- First version. \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ac26cdd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,137 @@ +# Contribuire a nsJson + +Grazie per il tuo interesse a contribuire! + +> Repository primario: [https://gitea.emulab.it/Simone/nsis-plugin-nsjson](https://gitea.emulab.it/Simone/nsis-plugin-nsjson) +> Mirror GitHub: [https://github.com/systempal/nsis-plugin-nsjson](https://github.com/systempal/nsis-plugin-nsjson) (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-nsjson.git +cd nsis-plugin-nsjson +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-nsjson/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-nsjson/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/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..0226c9a --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,15 @@ +This plug-in is provided 'as-is', without any express or implied +warranty. In no event will the author be held liable for any damages +arising from the use of this plug-in. + +Permission is granted to anyone to use this plug-in for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this plug-in must not be misrepresented; you must not + claim that you wrote the original plug-in. + If you use this plug-in in a product, an acknowledgment in the + product documentation would be appreciated but is not required. +2. Altered versions must be plainly marked as such, and must not be + misrepresented as being the original plug-in. +3. This notice may not be removed or altered from any distribution. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f3831a0 --- /dev/null +++ b/README.md @@ -0,0 +1,435 @@ +# nsJSON NSIS plug-in + +Authors: Stuart 'Afrow UK' Welch, Pieter Dewachter + +Date: 10th October 2024 + +Version: 1.1.2 + +A JSON (JavaScript Object Notation) parser, manipulator and generator plug-in for NSIS. + +See `examples/*.nsi`. + +## About JSON + +See http://www.json.org/ for information about JSON along with the syntax and string escape sequences. + +## Important information + +All plug-in functions set the NSIS error flag on error. For functions that return a value on the stack, no item will be returned if the error flag has been set. You can use `IfErrors` or `${If} ${Errors}` to check that a call has succeeded before Pop-ping a value, if any. + +The plug-in supports all escape sequences in string values: +`\r`, `\n`, `\t`, `\b`, `\f`, `\"`, `\\` and `\uXXXX` + +`[NodePath]`, used throughout this readme, is any space-delimited list of mixed node keys and indices (prefixed with /index). For example: + +``` +{ + "node1": { + "node2": false, + "node3": [ "Stuart", 1, { "node4": "Welch", "node5": "" }, 32.5, [ 0, "test", false ] ] + } +} +``` + +This is a JSON snippet. "node2" is a child node of "node1". "node2" has a Boolean value of false. "node3" is an array (denoted by square brackets). The 3rd element within the array is another node. The 5th element within the array is another array. +These are examples of node paths within the JSON snippet: + +"node1" "node2" +- The path for "node2" (value: false) +- `nsJSON::Get "node1" "node2" /end` + +"node1" "node3" /index 0 +- The path for the first array element in "node3" (value: "Stuart") +- `nsJSON::Get "node1" "node3" /index 0 /end` + +"node1" "node3" /index 2 "node4" +- The path for "node4" (value: "Welch") +- `nsJSON::Get "node1" "node3" /index 3 "node4" /end` + +/index 0 "node3" /index 2 /index 0 +- Also the path for "node4" (value: "Welch") +- `nsJSON::Get /index 0 /index 1 /index 3 /index 0 /end` + +"node1" "node3" /index 4 /index -2 +- The path for value "test" in the array; note the negative index +- `nsJSON::Get "node1" "node3" /index 4 /index -2 /end` + +Node names containing double quotes are automatically escaped. If no node path is given, the root node is used. + +Multiple JSON trees can be manipulated at the same time. Add `/tree Tree` (where Tree is the tree name) before all other plug-in arguments to specify which JSON tree you are manipulating. + +## Reading JSON files + +`nsJSON::Set [/tree Tree] [NodePath] /file [/unicode] "path\to\input.json"` + +Loads the JSON from the given file into the given node. Specify `/unicode` if the input file is Unicode. + +--- + +`nsJSON::Set [/tree Tree] /file [/unicode] "path\to\input.json"` + +Loads the JSON from the given file into memory, overwriting any existing JSON tree. Specify `/unicode` if the input file is Unicode. + +## JSON from HTTP web requests + +`nsJSON::Set [/tree Tree] [NodePath] /http ConfigTree` + +Executes an HTTP web request and loads the JSON response into the given node. The ConfigTree JSON tree specifies the web request configuration to execute, described using JSON. Its possible values are as follows... + +"**Url**": "http://..." +- The web request URL. This is required. + +"**Verb**": "GET" +- The request verb, such as GET or POST. Default is GET. + +"**Params**": "..." +- Optional parameters to append to the URL (after ?). + +"**ParamsType**": "..." +- The parameters type. Possible values are... + + Empty string/omitted (default) + - Parameters are given as JSON and will be sent as a standard key/value pair string. Special characters will be URL-encoded automatically. Arrays will be serialized as multiple keys of the same name with a [] suffix. + + For example `{"a": "Value1", "b": true, "c": "Value3"}` becomes `a=Value1&b&c=Value3`, and `{"Array": [1, 2]}` becomes `Array[]=1&Array[]=2`. + + "Raw" + - Raw parameters are given as a string. + +"**Data**": "..."` +- Optional request data to send (typically used with POST). + +"**DataType**": "..." +- The request data type. Possible values are... + + Empty string/omitted (default) + - Data is given as JSON and will be sent as a standard POST key/value pair string. Special characters will be URL-encoded automatically. Arrays will be serialized as multiple keys of the + same name with a [] suffix. + + For example `{"a": "Value1", "b": true, "c": "Value3"}` becomes `a=Value1&b&c=Value3`, and `{"Array": [1, 2]}` becomes + `Array[]=1&Array[]=2`. + + "JSON" + - POST data is JSON and will be sent as-is (serialized JSON). + + "Raw" + - Raw POST data is given as a string. + +"**DataEncoding**": "..." +- Specifies the encoding to use for the POST data. Possible values + are... + + Empty string/omitted (default) + - Data is encoded as 1 byte per character. + + "Unicode" + - Data is encoded as 2 bytes per character. + + "Headers": "..." + - Additional headers to send, which will override these defaults... + * "Content-Type: application/x-www-form-urlencoded" is sent if "Verb" is "POST" and "DataType" is "Raw". + * "Content-Type: application/json" is sent if "Verb" is "POST" and "DataType" is "JSON". + * "Accept-Encoding: gzip,deflate" is sent if "Decoding" is set to true. + + Specify headers as JSON, for example: `{"Content-Type": "text/plain", "a": "b"}` + + + "Agent": "nsJSON NSIS plug-in/1.0.x.x" + - The request agent string. Default is shown. + + "Decoding": false + - Enables automatic GZIP decompression by sending the "Accept-Encoding: gzip,deflate" request header. If the server responds with a valid "Content-Encoding" header, the compressed data will be decompressed automatically. + + "AccessType": "..." + - The connection access type. Possible values are... + + Empty string/omitted (default) + - Direct access (bypasses any proxy). + + "PreConfig" + - Use the internet configuration defined in the Windows registry. + + "Proxy" + - Use defined proxy configuration (see below). + +"**Proxy**": { "Server": "...", "Bypass": "...", Username": "...", "Password": "..." } +- Specifies the proxy configuration. The "Bypass" value is optional. + +"**Username**": "..." +- Username for HTTP authentication. + +"**Password**": "..." +- Password for HTTP authentication. + +"**ConnectTimeout**": "..." +- The initial connection timeout. A value of `0XFFFFFFFF` will disable the timeout. + +"**SendTimeout**": N +- The request send timeout. + +"**ReceiveTimeout**": N +- The response receive timeout. + +"**UnicodeOutput**": true +- The response must be read as Unicode rather than ASCII. + +"**RawOutput**": true +- The response must be read as raw text rather than JSON. + +"**Async**": true +- Perform the request asynchronously. You must use the Wait function to wait or check if the request has finished. + +"**UIAsync**": true +- For performing the request in a page callback function. This will ensure window messages are processed so that the UI does not become unresponsive. After the request has finished, the node specified by NodePath will contain the following values: + + "Output": ... + - The HTTP response as JSON. + + "StatusCode": N + - The HTTP status code. + + "ErrorCode": N + - The WinINet/Win32 error code on error. + + "ErrorMessage": "..." + - The WinINet/Win32 error message on error. + +## JSON from console application execution + +`nsJSON::Set [/tree Tree] [NodePath] /exec ConfigTree` + +Executes a console application and loads the output into the given node. The ConfigTree JSON tree specifies the console application to execute, described using JSON. Its possible values are as follows... + + "Path": "$INSTDIR\ConsoleApp.exe" + - The path to the executable to be executed. This is required. + + "Arguments": ... + - The command line arguments. The value can be a string or an array of strings. + + "WorkingDir": "..." + - The working/current directory. If not specified, the installer's working directory will be used, which is $OUTDIR. + + "Input": ... + - The input to write to STDIN. The value can be a string or an array of strings. + + "UnicodeInput": true + - The standard input must be written as Unicode rather than ASCII. + + "UnicodeOutput": true + - The standard output must be read as Unicode rather than ASCII. + + "RawOutput": true + - The standard output must be read as raw text rather than JSON. + + "Async": true + - Execute asynchronously. You must use the Wait function to wait or check if the process has finished. + + "UIAsync": true + - For performing the execution in a page callback function. This will + ensure window messages are processed so that the UI does not become unresponsive. + + After execution has finished, the node specified by NodePath will + contain the following values: + + "Output": ... + - The output of the console application, depending on the RawOutput setting. + + "ExitCode": X + - The process exit code. + +## Modifying JSON + +`nsJSON::Set [/tree Tree] [NodePath] /value "Value"` + +Sets the value of the given node. The value can be any single value or it can be JSON code. The node will be created if it does not exist. + +--- + +`nsJSON::Delete [/tree Tree] [NodePath] /end` + +Deletes the given tree or node. `/end` must be added to the end of the list to prevent stack corruption. + +--- + +``` +nsJSON::Quote [/unicode] [/always] Value +Pop $Var +``` + +Surrounds the given value with quotes if necessary and escapes any characters that require it. The quoted value will be returned on the stack. + +Optionally specify `/unicode` to escape non ASCII characters as well although it is perfectly legal to include Unicode characters in JSON. + +Specify `/always` to always surround the input string in quotes even if it is already quoted. + +--- + +`nsJSON::Sort [/tree Tree] [NodePath] [/options Options] /end` + +Sorts the given node. Add up the following values for the optional sort options: + +1. Sort in descending order. +2. Use numeric sort. +4. Sort case sensitively. +8. Sort by keys rather than by values. +16. Sort all nodes in the tree recursively. + +## Reading JSON values + +``` +nsJSON::Get [/tree Tree] [/noexpand] [NodePath] /end +Pop $Var +``` + +Gets the value of the given node. The value returned will be JSON code if the node is not a value-only node. Specify `/noexpand` when reading quoted string values to stop escape sequences being expanded. `/end` must be added to the end of the list to prevent stack corruption. + +--- + +``` +nsJSON::Get [/tree Tree] /type [NodePath] /end +Pop $Var +``` + +Gets the value-type of the given node. It can be one of "node", "array", "string" (quoted strings), "value" (which is all non-string values such as integers, floats and Booleans) or an empty string if the node does not exist. + +--- + +``` +nsJSON::Get [/tree Tree] /key [NodePath] /end +Pop $Var +``` + +Gets the name (key) of the given node. + +--- + +``` +nsJSON::Get [/tree Tree] /keys [NodePath] /end +Pop $VarKeyCount +Pop $VarKey1 +Pop $VarKey2 +Pop $VarKeyN +``` + +Gets the keys of the given node. `$VarKeyCount` will be the number of keys pushed onto the stack (to be removed via Pop). + +--- + +``` +nsJSON::Get [/tree Tree] /exists [NodePath] /end +Pop $Var +``` + +Determines whether or not the given node exists. `$Var` will be "yes" or "no". + +--- + +``` +nsJSON::Get [/tree Tree] /count [NodePath] /end +Pop $Var +``` + +Gets the number of child nodes for the given node or the number of elements if the node is an array. + +--- + +``` +nsJSON::Get [/tree Tree] /isempty [NodePath] /end +Pop $Var +``` + +Determines whether or not the given node is empty. `$Var` will be "yes" or "no". An empty node is for example: `"node": { }` or `"array": [ ]`. + +## Generating JSON + +``` +nsJSON::Serialize [/tree Tree] [/format] +Pop $Var +``` + +Serializes the current JSON tree into $Var. Add `/format` to apply formatting to the output. The error flag is set if an error occurs. + +--- + +`nsJSON::Serialize [/tree Tree] [/format] /file [/unicode] "path\to\output.json"` + +Serializes the current JSON tree into the given file. Add `/format` to apply formatting to the output. Add `/unicode` to generate a Unicode output file (applies to both ANSI and Unicode NSIS). + +## Waiting for asynchronous tasks to finish + +``` +nsJSON::Wait Tree [/timeout TimeoutInMilliseconds] +Pop $Var +``` + +Checks whether or not the asynchonous task that is being ran under the given JSON tree has finished. + +If `/timeout` is specified, the function will return after the specified number of milliseconds; after which `$Var` will be "wait" if the task has not finished; or an empty string otherwise. + +When `/timeout` is omitted, the function will wait indefinitely and nothing will be pushed onto the stack when the task finishes. + +--- + +## ⚠️ Differences in the Personal Version + +This version is based on the official v1.1.1.1 release from [GitHub](https://github.com/Pieter-Dewachter/nsJSON/releases/tag/v1.1.1.1). + +### New supported architecture: x64 (amd64-unicode) + +The original v1.1.1.1 supports only: +- x86-ansi +- x86-unicode + +The personal version adds support for: +- **amd64-unicode** (x64) + +### Visual Studio Project + +The original included: +- `Contrib/nsJSON/nsJSON.sln` - VS2012 Solution +- `Contrib/nsJSON/nsJSON.vcxproj` - VS2012 Project (x86 only) +- `Contrib/nsJSON/ConsoleApp/ConsoleApp.vcxproj` - Test application + +The personal version uses the same project updated for VS2022 with x64 support. + +### Added Files (build infrastructure) + +- `build_plugin.py` - Python script to compile the plugin for all architectures +- `.gitignore` - Git exclusion file + +### Removed Files + +Pre-compiled DLL files have been removed from the distribution (they are generated by the build): + +- `Plugins/x86-ansi/nsJSON.dll` +- `Plugins/x86-unicode/nsJSON.dll` + +### Functional Changes + +No functional changes compared to the original. Modifications are limited to the build infrastructure and x64 support. + +### Build + +```cmd +cd nsJson +python build_plugin.py +``` + +DLLs are copied to `plugins/{platform}/nsJSON.dll`. + +### Build Options + +```powershell +python build_plugin.py --configs x86-unicode # Single architecture (x86-ansi|x86-unicode|x64-unicode|all) +python build_plugin.py --vs-version 2026 # Specific toolset (auto|2022|2026) +python build_plugin.py --clean # Clean dist/ before build +python build_plugin.py --rebuild # Force full rebuild +python build_plugin.py --verbosity detailed # Extended MSBuild output +python build_plugin.py --install-dir "C:\NSIS\Plugins" # Copy to additional NSIS directory +python build_plugin.py --list # List available configurations +``` + +--- + +*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..d893217 --- /dev/null +++ b/README_IT.md @@ -0,0 +1,270 @@ +# nsJSON NSIS plug-in + +Autori: Stuart 'Afrow UK' Welch, Pieter Dewachter + +Data: 10 Ottobre 2024 + +Versione: 1.1.1.1 + +Plugin NSIS per il parsing, la manipolazione e la generazione di JSON (JavaScript Object Notation). + +Vedi Examples\nsJSON\*. + +## Informazioni su JSON + +Vedi http://www.json.org/ per informazioni su JSON, sintassi e sequenze di escape. + +## Informazioni importanti + +Tutte le funzioni del plug-in impostano il flag di errore NSIS in caso di errore. Per le funzioni che restituiscono un valore sullo stack, nessun elemento viene restituito se il flag di errore è stato impostato. Usare `IfErrors` o `${If} ${Errors}` per verificare che una chiamata sia riuscita prima di fare Pop di un valore. + +Il plug-in supporta tutte le sequenze di escape nei valori stringa: +`\r`, `\n`, `\t`, `\b`, `\f`, `\"`, `\\` e `\uXXXX` + +`[NodePath]`, usato in questo readme, è qualsiasi lista di chiavi e indici separati da spazi (gli indici sono preceduti da /index). Per esempio: + +``` +{ + "node1": { + "node2": false, + "node3": [ "Stuart", 1, { "node4": "Welch", "node5": "" }, 32.5, [ 0, "test", false ] ] + } +} +``` + +Esempi di percorsi nodo in questo JSON: + +"node1" "node2" +- Percorso per "node2" (valore: false) +- `nsJSON::Get "node1" "node2" /end` + +"node1" "node3" /index 0 +- Percorso per il primo elemento di "node3" (valore: "Stuart") +- `nsJSON::Get "node1" "node3" /index 0 /end` + +Più alberi JSON possono essere manipolati contemporaneamente. Aggiungere `/tree NomeAlbero` prima degli altri argomenti per specificare quale albero JSON si sta manipolando. + +## Lettura di file JSON + +`nsJSON::Set [/tree Albero] [NodePath] /file [/unicode] "percorso\input.json"` + +Carica il JSON dal file dato nel nodo specificato. Specificare `/unicode` se il file è Unicode. + +--- + +`nsJSON::Set [/tree Albero] /file [/unicode] "percorso\input.json"` + +Carica il JSON dal file dato in memoria, sovrascrivendo qualsiasi albero JSON esistente. + +## JSON da richieste HTTP + +`nsJSON::Set [/tree Albero] [NodePath] /http AlberoConfig` + +Esegue una richiesta HTTP e carica la risposta JSON nel nodo dato. L'AlberoConfig specifica la configurazione della richiesta in formato JSON. I valori possibili sono: + +**"Url"**: "http://..." +- L'URL della richiesta. Obbligatorio. + +**"Verb"**: "GET" +- Il verbo della richiesta (GET, POST, ecc.). Default: GET. + +**"Params"**: "..." +- Parametri opzionali da aggiungere all'URL (dopo ?). + +**"Data"**: "..." +- Dati opzionali da inviare (tipicamente con POST). + +**"Headers"**: "..." +- Header aggiuntivi da inviare. + +**"Agent"**: "nsJSON NSIS plug-in/1.0.x.x" +- La stringa dell'agente della richiesta. + +**"Decoding"**: false +- Abilita la decompressione GZIP automatica. + +**"Async"**: true +- Esegue la richiesta in modo asincrono. Usare la funzione Wait per attendere il completamento. + +## JSON dall'esecuzione di applicazioni console + +`nsJSON::Set [/tree Albero] [NodePath] /exec AlberoConfig` + +Esegue un'applicazione console e carica l'output nel nodo dato. I valori possibili della configurazione: + +**"Path"**: "$INSTDIR\ConsoleApp.exe" +- Percorso all'eseguibile. Obbligatorio. + +**"Arguments"**: ... +- Argomenti della riga di comando. + +**"WorkingDir"**: "..." +- Directory di lavoro. + +**"Input"**: ... +- Input da scrivere su STDIN. + +**"Async"**: true +- Esegue in modo asincrono. + +## Modifica del JSON + +`nsJSON::Set [/tree Albero] [NodePath] /value "Valore"` + +Imposta il valore del nodo dato. Il valore può essere un singolo valore o codice JSON. Il nodo viene creato se non esiste. + +--- + +`nsJSON::Delete [/tree Albero] [NodePath] /end` + +Elimina l'albero o il nodo dato. `/end` deve essere aggiunto alla fine della lista. + +--- + +``` +nsJSON::Quote [/unicode] [/always] Valore +Pop $Var +``` + +Racchiude il valore dato tra virgolette se necessario e fa l'escape dei caratteri che lo richiedono. + +--- + +`nsJSON::Sort [/tree Albero] [NodePath] [/options Opzioni] /end` + +Ordina il nodo dato. Sommare i seguenti valori per le opzioni di ordinamento: + +1. Ordine decrescente. +2. Ordinamento numerico. +4. Ordinamento case-sensitive. +8. Ordina per chiavi anziché per valori. +16. Ordina ricorsivamente tutti i nodi nell'albero. + +## Lettura di valori JSON + +``` +nsJSON::Get [/tree Albero] [/noexpand] [NodePath] /end +Pop $Var +``` + +Ottiene il valore del nodo dato. `/end` deve essere aggiunto alla fine della lista. + +--- + +``` +nsJSON::Get [/tree Albero] /type [NodePath] /end +Pop $Var +``` + +Ottiene il tipo di valore del nodo dato: "node", "array", "string", "value" o stringa vuota se il nodo non esiste. + +--- + +``` +nsJSON::Get [/tree Albero] /keys [NodePath] /end +Pop $VarKeyCount +Pop $VarKey1 +Pop $VarKeyN +``` + +Ottiene le chiavi del nodo dato. + +--- + +``` +nsJSON::Get [/tree Albero] /count [NodePath] /end +Pop $Var +``` + +Ottiene il numero di nodi figli o elementi di un array. + +## Generazione JSON + +``` +nsJSON::Serialize [/tree Albero] [/format] +Pop $Var +``` + +Serializza l'albero JSON corrente in `$Var`. Aggiungere `/format` per formattare l'output. + +--- + +`nsJSON::Serialize [/tree Albero] [/format] /file [/unicode] "percorso\output.json"` + +Serializza l'albero JSON corrente nel file dato. + +## Attesa di task asincroni + +``` +nsJSON::Wait Albero [/timeout TimeoutInMillisecondi] +Pop $Var +``` + +Verifica se il task asincrono in esecuzione nell'albero JSON dato è terminato. + +--- + +## ⚠️ Differenze nella versione personale + +Questa versione è basata sul rilascio ufficiale v1.1.1.1 da [GitHub](https://github.com/Pieter-Dewachter/nsJSON/releases/tag/v1.1.1.1). + +### Nuova architettura supportata: x64 (amd64-unicode) + +L'originale v1.1.1.1 supporta solo: +- x86-ansi +- x86-unicode + +La versione personale aggiunge il supporto per: +- **amd64-unicode** (x64) + +### Progetto Visual Studio + +L'originale includeva: +- `Contrib/nsJSON/nsJSON.sln` - Solution VS2012 +- `Contrib/nsJSON/nsJSON.vcxproj` - Progetto VS2012 (solo x86) +- `Contrib/nsJSON/ConsoleApp/ConsoleApp.vcxproj` - App di test + +La versione personale usa lo stesso progetto aggiornato per VS2022 con supporto x64. + +### File aggiunti (infrastruttura di build) + +- `build_plugin.cmd` - Script per compilare il plugin +- `build_plugin.py` - Script Python per compilare il plugin per tutte le architetture +- `BUILD_README.md` - Documentazione di compilazione +- `.gitignore` - File di esclusione Git + +### File rimossi + +I file DLL precompilati sono stati rimossi dalla distribuzione (vengono generati dalla compilazione): + +- `Plugins/x86-ansi/nsJSON.dll` +- `Plugins/x86-unicode/nsJSON.dll` + +### Modifiche funzionali + +Nessuna modifica funzionale rispetto all'originale. Le modifiche riguardano solo l'infrastruttura di build e il supporto x64. + +### Compilazione + +```cmd +cd nsJson +python build_plugin.py +``` + +I DLL vengono copiati in `plugins/{platform}/nsJSON.dll`. + +### Opzioni build + +```powershell +python build_plugin.py --config x86-unicode # Solo un'architettura (x86-ansi|x86-unicode|amd64-unicode|all) +python build_plugin.py --toolset 2026 # Toolset specifico (2022|2026|auto) +python build_plugin.py --jobs 4 # Numero di job MSBuild paralleli (default: CPU count) +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 --verbose # Output MSBuild esteso +python build_plugin.py --version # Stampa versione ed esce +``` + +--- + +*See [README.md](README.md) for the English version.* diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f5df862 --- /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.1.1) | ✅ | +| 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-nsjson/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..764d6eb --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.1.3 \ No newline at end of file diff --git a/build_plugin.py b/build_plugin.py new file mode 100644 index 0000000..943d8fd --- /dev/null +++ b/build_plugin.py @@ -0,0 +1,679 @@ +#!/usr/bin/env python3 +""" +Build script for nsJSON 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 = 'nsJSON.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 / 'nsJSON.vcxproj' + dist_dir = script_dir / 'dist' + return project_dir, project_file, dist_dir + + +def get_plugins_dir() -> Optional[Path]: + """Return workspace-level plugins/ dir (3 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 nsJSON 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 nsJSON 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/nsJSON.dll b/dist/amd64-unicode/nsJSON.dll new file mode 100644 index 0000000..e411443 Binary files /dev/null and b/dist/amd64-unicode/nsJSON.dll differ diff --git a/dist/x86-ansi/nsJSON.dll b/dist/x86-ansi/nsJSON.dll new file mode 100644 index 0000000..ae52402 Binary files /dev/null and b/dist/x86-ansi/nsJSON.dll differ diff --git a/dist/x86-unicode/nsJSON.dll b/dist/x86-unicode/nsJSON.dll new file mode 100644 index 0000000..cd28d64 Binary files /dev/null and b/dist/x86-unicode/nsJSON.dll differ diff --git a/examples/ConsoleExec.nsi b/examples/ConsoleExec.nsi new file mode 100644 index 0000000..b5eea0b --- /dev/null +++ b/examples/ConsoleExec.nsi @@ -0,0 +1,193 @@ +!include MUI2.nsh + +Name `nsJSON plug-in` +OutFile nsJSON_ConsoleExec.exe +RequestExecutionLevel user +ShowInstDetails show + +!define MUI_COMPONENTSPAGE_SMALLDESC +!define MUI_PAGE_CUSTOMFUNCTION_LEAVE ComponentsPage_Leave +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_LANGUAGE English + +Section + + CreateDirectory $EXEDIR\Output + +SectionEnd + +Section /o `Console App` NATIVE + + InitPluginsDir + StrCpy $R0 $PLUGINSDIR\ConsoleApp.exe + File /oname=$R0 Bin\ConsoleApp.exe + + nsJSON::Set /tree ConsoleExec /value `{ "Path": "$R0", "WorkingDir": "$PLUGINSDIR", "RawOutput": true }` + + ; Provide arguments as a single string or an array. + nsJSON::Set /tree ConsoleExec Arguments /value `[]` + nsJSON::Set /tree ConsoleExec Arguments /value `"arg1"` + nsJSON::Set /tree ConsoleExec Arguments /value `"arg2"` + + ; Provide input as a single string or an array. + nsJSON::Set /tree ConsoleExec Input /value `"hello"` + + ; Export the tree to file for debugging. + DetailPrint `Generate: $EXEDIR\Output\ConsoleExecInput.json from ConsoleExec` + nsJSON::Serialize /tree ConsoleExec /format /file $EXEDIR\Output\ConsoleExecInput.json + + ; Execute the console application. + DetailPrint `Exec: $R0` + nsJSON::Set /tree ConsoleExecOutput /exec ConsoleExec + + ; Save the output to file. + DetailPrint `Generate: $EXEDIR\Output\ConsoleExecOutput.json from ConsoleExecOutput` + nsJSON::Serialize /tree ConsoleExecOutput /format /file $EXEDIR\Output\ConsoleExecOutput.json + +SectionEnd + +Section /o `Async Console App` NATIVEASYNC + + InitPluginsDir + StrCpy $R0 $PLUGINSDIR\ConsoleApp.exe + File /oname=$R0 Bin\ConsoleApp.exe + + nsJSON::Set /tree ConsoleExec /value `{ "Async": true, "Path": "$R0", "WorkingDir": "$PLUGINSDIR", "RawOutput": true }` + + ; Provide arguments as a single string or an array. + nsJSON::Set /tree ConsoleExec Arguments /value `[]` + nsJSON::Set /tree ConsoleExec Arguments /value `"arg1"` + nsJSON::Set /tree ConsoleExec Arguments /value `"arg2"` + + ; Provide input as a single string or an array. + nsJSON::Set /tree ConsoleExec Input /value `"hello"` + + ; Export the tree to file for debugging. + DetailPrint `Generate: $EXEDIR\Output\AsyncConsoleExecInput.json from ConsoleExec` + nsJSON::Serialize /tree ConsoleExec /format /file $EXEDIR\Output\AsyncConsoleExecInput.json + + ; Execute the console application. + DetailPrint `Exec: $R0` + nsJSON::Set /tree AsyncConsoleExecOutput /exec ConsoleExec + + ; Wait until done. + ${Do} + Sleep 1000 + nsJSON::Wait ConsoleExec /timeout 0 + Pop $R0 + ${If} $R0 != wait + ${Break} + ${EndIf} + DetailPrint `Waiting...` + ${Loop} + + DetailPrint `Finished...` + + ; Save the output to file. + DetailPrint `Generate: $EXEDIR\Output\AsyncConsoleExecOutput.json from AsyncConsoleExecOutput` + nsJSON::Serialize /tree AsyncConsoleExecOutput /format /file $EXEDIR\Output\AsyncConsoleExecOutput.json + +SectionEnd + +Section /o `.NET Console App` DOTNET + + InitPluginsDir + StrCpy $R0 $PLUGINSDIR\ConsoleAppDotNet.exe + File /oname=$R0 Bin\ConsoleAppDotNet.exe + + nsJSON::Set /tree ConsoleExec /value `{ "Path": "$R0", "WorkingDir": "$PLUGINSDIR" }` + + ; Provide arguments as a single string or an array. + nsJSON::Set /tree ConsoleExec Arguments /value `[]` + nsJSON::Set /tree ConsoleExec Arguments /value `"arg1"` + nsJSON::Set /tree ConsoleExec Arguments /value `"arg2"` + + ; Provide input as a single string or an array. + nsJSON::Set /tree ConsoleExec Input /value `"hello"` + + ; Export the tree to file for debugging. + DetailPrint `Generate: $EXEDIR\Output\ConsoleExecDotNetInput.json from ConsoleExec` + nsJSON::Serialize /tree ConsoleExec /format /file $EXEDIR\Output\ConsoleExecDotNetInput.json + + ; Execute the console application. + DetailPrint `Exec: $R0` + nsJSON::Set /tree ConsoleExecOutput /exec ConsoleExec + + ; Save the output to file. + DetailPrint `Generate: $EXEDIR\Output\ConsoleExecDotNetOutput.json from ConsoleExecOutput` + nsJSON::Serialize /tree ConsoleExecOutput /format /file $EXEDIR\Output\ConsoleExecDotNetOutput.json + +SectionEnd + +Section /o `UI Thread Console App` UINATIVE + +SectionEnd + +Function ComponentsPage_Leave + + ${IfNot} ${SectionIsSelected} ${UINATIVE} + Return + ${EndIf} + + GetDlgItem $R0 $HWNDPARENT 1 + EnableWindow $R0 0 + GetDlgItem $R0 $HWNDPARENT 2 + EnableWindow $R0 0 + GetDlgItem $R0 $HWNDPARENT 3 + EnableWindow $R0 0 + FindWindow $R0 `#32770` `` $HWNDPARENT + GetDlgItem $R0 $R0 1032 + EnableWindow $R0 0 + + Banner::Show /set 76 `Executing ConsoleApp.exe` `Please wait...` + + InitPluginsDir + StrCpy $R0 $PLUGINSDIR\ConsoleApp.exe + File /oname=$R0 Bin\ConsoleApp.exe + + nsJSON::Set /tree ConsoleExec /value `{ "UIAsync": true, "Path": "$R0", "WorkingDir": "$PLUGINSDIR", "RawOutput": true }` + + ; Provide arguments as a single string or an array. + nsJSON::Set /tree ConsoleExec Arguments /value `[]` + nsJSON::Set /tree ConsoleExec Arguments /value `"arg1"` + nsJSON::Set /tree ConsoleExec Arguments /value `"arg2"` + + ; Provide input as a single string or an array. + nsJSON::Set /tree ConsoleExec Input /value `"hello"` + + ; Export the tree to file for debugging. + nsJSON::Serialize /tree ConsoleExec /format /file $EXEDIR\Output\UIConsoleExecInput.json + + ; Execute the console application. + nsJSON::Set /tree UIConsoleExecOutput /exec ConsoleExec + + ; Save the output to file. + nsJSON::Serialize /tree UIConsoleExecOutput /format /file $EXEDIR\Output\UIConsoleExecOutput.json + + Banner::Destroy + + GetDlgItem $R0 $HWNDPARENT 1 + EnableWindow $R0 1 + GetDlgItem $R0 $HWNDPARENT 2 + EnableWindow $R0 1 + GetDlgItem $R0 $HWNDPARENT 3 + EnableWindow $R0 1 + FindWindow $R0 `#32770` `` $HWNDPARENT + GetDlgItem $R0 $R0 1032 + EnableWindow $R0 1 + +FunctionEnd + +LangString NATIVEDesc ${LANG_ENGLISH} `Executes native console app with STDIN data and reads JSON from STDOUT` +LangString NATIVEASYNCDesc ${LANG_ENGLISH} `Executes native console app asynchronously with STDIN data and reads JSON from STDOUT` +LangString DOTNETDesc ${LANG_ENGLISH} `Executes .NET console app with STDIN data and reads JSON from STDOUT` +LangString UINATIVEDesc ${LANG_ENGLISH} `Executes native console app asynchronously while processing window messages` + +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${NATIVE} $(NATIVEDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${NATIVEASYNC} $(NATIVEASYNCDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${DOTNET} $(DOTNETDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${UINATIVE} $(UINATIVEDesc) +!insertmacro MUI_FUNCTION_DESCRIPTION_END \ No newline at end of file diff --git a/examples/Example.nsi b/examples/Example.nsi new file mode 100644 index 0000000..246fae0 --- /dev/null +++ b/examples/Example.nsi @@ -0,0 +1,509 @@ +!include MUI2.nsh + +Name `nsJSON plug-in` +OutFile nsJSON_Example.exe +RequestExecutionLevel user +ShowInstDetails show + +!define MUI_COMPONENTSPAGE_SMALLDESC +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_LANGUAGE English + +Section + + CreateDirectory $EXEDIR\Output + +SectionEnd + +Section /o `Parse Example1.json` EXAMPLE1 + + ; Input: Example1.json; output: Example1.json. + nsJSON::Set /file $EXEDIR\Input\Example1.json + nsJSON::Serialize /format /file $EXEDIR\Output\Example1.json + DetailPrint `Generate: $EXEDIR\Output\Example1.json` + +SectionEnd + +Section /o `Convert Example1_Unicode.json (no BOM)` EXAMPLE1B + + ; Use the /unicode switch if the input file is Unicode. + nsJSON::Set /file /unicode $EXEDIR\Input\Example1_Unicode.json + + ; Generate an ANSII output file. + nsJSON::Serialize /format /file $EXEDIR\Output\Example1B_ASCII.json + DetailPrint `Generate: $EXEDIR\Output\Example1B_ASCII.json` + + ; Generate a Unicode output file. + nsJSON::Serialize /format /file /unicode $EXEDIR\Output\Example1B_Unicode.json + DetailPrint `Generate: $EXEDIR\Output\Example1B_Unicode.json` + +SectionEnd + +Section /o `Convert Example1_Unicode_UTF16BE.json (BOM)` EXAMPLE1C + + ; Use the /unicode switch if the input file is Unicode. + nsJSON::Set /file /unicode $EXEDIR\Input\Example1_Unicode_UTF16BE.json + + ; Generate an ANSII output file. + nsJSON::Serialize /format /file $EXEDIR\Output\Example1C_ASCII.json + DetailPrint `Generate: $EXEDIR\Output\Example1C_ASCII.json` + + ; Generate a Unicode output file. + nsJSON::Serialize /format /file /unicode $EXEDIR\Output\Example1C_Unicode.json + DetailPrint `Generate: $EXEDIR\Output\Example1C_Unicode.json` + +SectionEnd + +Section /o `Convert Example1_Unicode_UTF16LE.json (BOM)` EXAMPLE1D + + ; Use the /unicode switch if the input file is Unicode. + nsJSON::Set /file /unicode $EXEDIR\Input\Example1_Unicode_UTF16LE.json + + ; Generate an ANSII output file. + nsJSON::Serialize /format /file $EXEDIR\Output\Example1D_ASCII.json + DetailPrint `Generate: $EXEDIR\Output\Example1D_ASCII.json` + + ; Generate a Unicode output file. + nsJSON::Serialize /format /file /unicode $EXEDIR\Output\Example1D_Unicode.json + DetailPrint `Generate: $EXEDIR\Output\Example1D_Unicode.json` + +SectionEnd + +Section /o `Convert Example1_Unicode_UTF8.json (sig)` EXAMPLE1E + + ; No /unicode switch is used for UTF8. + nsJSON::Set /file $EXEDIR\Input\Example1_Unicode_UTF8.json + + ; Generate an ANSII output file. + nsJSON::Serialize /format /file $EXEDIR\Output\Example1_ASCII.json + DetailPrint `Generate: $EXEDIR\Output\Example1_ASCII.json` + + ; Generate a Unicode output file. + nsJSON::Serialize /format /file /unicode $EXEDIR\Output\Example1_Unicode.json + DetailPrint `Generate: $EXEDIR\Output\Example1_Unicode.json` + +SectionEnd + +Section /o `Parse Example2.json` EXAMPLE2 + + ; Input: Example2.json; output: Example2.json. + nsJSON::Set /file $EXEDIR\Input\Example2.json + nsJSON::Serialize /format /file $EXEDIR\Output\Example2.json + DetailPrint `Generate: $EXEDIR\Output\Example2.json` + +SectionEnd + +Section /o `Parse Example3.json` EXAMPLE3 + + ; Input: Example3.json; output: Example3.json. + nsJSON::Set /file $EXEDIR\Input\Example3.json + nsJSON::Serialize /format /file $EXEDIR\Output\Example3.json + DetailPrint `Generate: $EXEDIR\Output\Example3.json` + +SectionEnd + +Section /o `Parse Example4.json` EXAMPLE4 + + ; Input: Example4.json; output: Example4.json. + nsJSON::Set /file $EXEDIR\Input\Example4.json + nsJSON::Serialize /format /file $EXEDIR\Output\Example4.json + DetailPrint `Generate: $EXEDIR\Output\Example4.json` + +SectionEnd + +Section /o `Parse Example5.json` EXAMPLE5 + + ; Input: Example5.json; output: Example5.json. + nsJSON::Set /file $EXEDIR\Input\Example5.json + nsJSON::Serialize /format /file $EXEDIR\Output\Example5.json + DetailPrint `Generate: $EXEDIR\Output\Example5.json` + +SectionEnd + +Section /o `Parse Example5.json (via $$R0)` EXAMPLE5B + + ; Input: Example5.json; output: Example5.json. + nsJSON::Set /file $EXEDIR\Input\Example5.json + nsJSON::Serialize /format + Pop $R0 + FileOpen $R1 $EXEDIR\Output\Example5B.json w + FileWrite $R1 $R0 + FileClose $R1 + DetailPrint `Generate: $EXEDIR\Output\Example5B.json` + +SectionEnd + +Section /o `Generate Example6.json` EXAMPLE6 + + nsJSON::Set /value `{}` + nsJSON::Set `html` `head` `title` /value `"Example6"` + + ; Build an array using individual calls. + nsJSON::Set `html` `body` `h1` /value `[]` + nsJSON::Set `html` `body` `h1` /value `"Hello,\tmy name is"` + nsJSON::Set `html` `body` `h1` `i` /value `"Stuart."` + nsJSON::Set `html` `body` `h1` /value `"Howdy"` + nsJSON::Set `html` `body` `h1` /value `"!"` + nsJSON::Set `html` `body` `h1` /value `[ true, "hello", false ]` + + ; Build an array using a JSON string. + nsJSON::Set `html` `body` `h2` /value `[ "I like", { "u": { "i" : "programming" } }, "very much!" ]` + + ; Quotes in node keys are allowed; they are escaped automatically. + nsJSON::Set `html` `body` `a href="http://www.afrowsoft.co.uk"` /value `"My website!"` + + ; Open the file below in Notepad. + nsJSON::Serialize /format /file $EXEDIR\Output\Example6.json + DetailPrint `Generate: $EXEDIR\Output\Example6.json` + +SectionEnd + +Section /o `Reading from Example1.json` EXAMPLE7 + + nsJSON::Set /file $EXEDIR\Input\Example1.json + + ; Read quoted string value. + ClearErrors + nsJSON::Get `glossary` `GlossDiv` `GlossList` `GlossEntry` `GlossTerm` /end + ${IfNot} ${Errors} + Pop $R0 + DetailPrint `glossary->GlossDiv->GlossList->GlossEntry->GlossTerm = $R0` + ${EndIf} + + ; Read quoted string value with escaping. + ClearErrors + nsJSON::Set `glossary` `GlossDiv` `GlossList` `GlossEntry` `GlossDef` `para2` /value `"A meta-markup language, used to create markup languages\r\nsuch as DocBook."` + nsJSON::Get `glossary` `GlossDiv` `GlossList` `GlossEntry` `GlossDef` `para2` /end + ${IfNot} ${Errors} + Pop $R0 + DetailPrint `glossary->GlossDiv->GlossList->GlossEntry->GlossDef->para2 = $R0` + ${EndIf} + + ; Read quoted string value without expanding escape sequences. + ClearErrors + nsJSON::Set `glossary` `GlossDiv` `GlossList` `GlossEntry` `GlossDef` `para3` /value `"A meta-markup language, used to create markup languages\r\nsuch as DocBook."` + nsJSON::Get /noexpand `glossary` `GlossDiv` `GlossList` `GlossEntry` `GlossDef` `para3` /end + ${IfNot} ${Errors} + Pop $R0 + DetailPrint `glossary->GlossDiv->GlossList->GlossEntry->GlossDef->para3 = $R0` + ${EndIf} + + ; Read the value of an array (returns a comma delimited list). + ClearErrors + nsJSON::Get `glossary` `GlossDiv` `GlossList` `GlossEntry` `GlossDef` `GlossSeeAlso` /end + ${IfNot} ${Errors} + Pop $R0 + DetailPrint `glossary->GlossDiv->GlossList->GlossEntry->GlossDef->GlossSeeAlso = $R0` + ${EndIf} + + ; Try reading a node that does not exist. + ClearErrors + nsJSON::Get `glossary` `GlossDiv` `GlossList` `GlossEntry` `GlossDef` `GlossSeeAlso2` /end + ${IfNot} ${Errors} + Pop $R0 + DetailPrint `glossary->GlossDiv->GlossList->GlossEntry->GlossDef->GlossSeeAlso2 = $R0` + ${Else} + DetailPrint `glossary->GlossDiv->GlossList->GlossEntry->GlossDef->GlossSeeAlso2 = Does not exist!` + ${EndIf} + + ; Try reading an array element that does not exist. + ClearErrors + nsJSON::Get `glossary` `GlossDiv` `GlossList` `GlossEntry` `GlossDef` `GlossSeeAlso` 99 /end + ${IfNot} ${Errors} + Pop $R0 + DetailPrint `glossary->GlossDiv->GlossList->GlossEntry->GlossDef->GlossSeeAlso->#99 = $R0` + ${Else} + DetailPrint `glossary->GlossDiv->GlossList->GlossEntry->GlossDef->GlossSeeAlso->#99 = Does not exist!` + ${EndIf} + +SectionEnd + +Section /o `Arrays test` EXAMPLE8 + + nsJSON::Set /value `{}` + + ; You can add an array this way. + nsJSON::Set `array1` /value `[ "value 1", "value 2", { "value 3": "node value" }, "value 4", 5 ]` + + ; Inspect array1. + nsJSON::Get `array1` /end + Pop $R0 + DetailPrint `array1 = [$R0]` + + ; Or you can build it this way. + nsJSON::Set `array2` /value `[]` + nsJSON::Set `array2` /value `"value 1"` + nsJSON::Set `array2` /value `"value 2"` + nsJSON::Set `array2` `value 3` /value `"node value"` + nsJSON::Set `array2` /value `"value 4"` + nsJSON::Set `array2` /value `5` + + ; You cannot add the same value again. + nsJSON::Set `array2` /value `5` + + ; More. + nsJSON::Set `array2` /value `blah1` + nsJSON::Set `array2` /value `blah2` + + ; Inspect array2. + nsJSON::Get `array2` /end + Pop $R0 + DetailPrint `array2 = [$R0]` + + ; Does an array element exist at the given index? + nsJSON::Get /exists `array2` /index 0 /end + Pop $R0 + DetailPrint `array2[0] exists? = $R0` + ${If} $R0 == yes + nsJSON::Get `array2` /index 0 /end + Pop $R0 + DetailPrint `array2[0] = $R0` + ${EndIf} + + ; Does an array element exist at the given index? + nsJSON::Get /exists `array2` /index -2 /end + Pop $R0 + DetailPrint `array2[-2] exists? = $R0` + ${If} $R0 == yes + nsJSON::Get `array2` /index -2 /end + Pop $R0 + DetailPrint `array2[-2] = $R0` + ${EndIf} + + ; Does an array element exist that matches the value? + nsJSON::Get /exists `array2` `5` /end + Pop $R0 + DetailPrint `array2->5 exists? = $R0` + + ; Does an array element exist at the given index? + nsJSON::Get /exists `array2` /index 6 /end + Pop $R0 + DetailPrint `array2[6] exists? = $R0` + + ; Open Example8_1.json to see what it now looks like. + nsJSON::Serialize /format /file $EXEDIR\Output\Example8_1.json + DetailPrint `Generate: $EXEDIR\Output\Example8_1.json` + + ; Now delete the element at the given index. + nsJSON::Delete `array1` /index 2 `value 3` /end + DetailPrint `Delete: array1[2]->value 3` + nsJSON::Delete `array2` /index 5 /end + DetailPrint `Delete: array2[5]` + + ; Now delete the elements with the given values. + nsJSON::Delete `array1` `value 1` /end + DetailPrint `Delete: array1->value 1` + nsJSON::Delete `array2` `value 2` /end + DetailPrint `Delete: array2->value 2` + + ; Inspect array1. + nsJSON::Get `array1` /end + Pop $R0 + DetailPrint `array1 = [$R0]` + + ; Inspect array2. + nsJSON::Get `array2` /end + Pop $R0 + DetailPrint `array2 = [$R0]` + + ; Open Example8_2.json to see what it now looks like. + nsJSON::Serialize /format /file $EXEDIR\Output\Example8_2.json + DetailPrint `Generate: $EXEDIR\Output\Example8_2.json` + +SectionEnd + +Section /o `Node iteration test` EXAMPLE9 + + nsJSON::Set /file $EXEDIR\Input\Example4.json + + ; Get the node count. + nsJSON::Get /count `web-app` `servlet` /index 0 `init-param` /end + Pop $R0 + + DetailPrint `Node web-app->servlet[0]->init-param contains $R0 children:` + ${For} $R1 0 $R0 + + nsJSON::Get /key `web-app` `servlet` /index 0 `init-param` /index $R1 /end + Pop $R2 + nsJSON::Get `web-app` `servlet` /index 0 `init-param` /index $R1 /end + Pop $R3 + nsJSON::Get /type `web-app` `servlet` /index 0 `init-param` /index $R1 /end + Pop $R4 + DetailPrint `$R2 = $R3 (type: $R4)` + + ${Next} + +SectionEnd + +Section /o `Load Example5.json into Example4.json` EXAMPLE10 + + ; Input: Example5.json; output: Example5.json. + nsJSON::Set /file $EXEDIR\Input\Example5.json + nsJSON::Set `menu` `example4` /file $EXEDIR\Input\Example4.json + nsJSON::Serialize /format /file $EXEDIR\Output\Example10.json + DetailPrint `Generate: $EXEDIR\Output\Example10.json` + +SectionEnd + +Section /o `Copies Preferences.json into PreferencesNew.json` EXAMPLE11 + + ; Input: Preferences.json; output: PreferencesNew.json. + nsJSON::Set /file $EXEDIR\Input\Preferences.json + nsJSON::Serialize /format /file $EXEDIR\Output\PreferencesNew.json + DetailPrint `Generate: $EXEDIR\Output\PreferencesNew.json` + +SectionEnd + +Section /o `Copies Preferences2.json into Preferences2New.json` EXAMPLE12 + + ; Input: Preferences2.json; output: Preferences2New.json. + nsJSON::Set /file $EXEDIR\Input\Preferences2.json + nsJSON::Serialize /format /file $EXEDIR\Output\Preferences2New.json + DetailPrint `Generate: $EXEDIR\Output\Preferences2New.json` + +SectionEnd + +Section /o `Buffer overflow` EXAMPLE13 + + ; Input: Example2.json. + nsJSON::Set /file $EXEDIR\Input\Example2.json + nsJSON::Serialize /format + Pop $R0 + StrLen $R1 $R0 + DetailPrint `Output length: $R1` + MessageBox MB_OK $R0 + +SectionEnd + +Section /o `Get tests` EXAMPLE14 + + nsJSON::Set /value `{ "a": "a", "b": 1, "c": {} }` + nsJSON::Serialize /format + Pop $R0 + DetailPrint `Test: $R0` + + nsJSON::Get /key /index 0 /end + Pop $R0 + DetailPrint `Key at index 0: $R0` + + nsJSON::Get /keys /end + Pop $R0 + DetailPrint `Total keys: $R0` + StrCpy $R1 0 + ${DoWhile} $R0 > 0 + Pop $R2 + DetailPrint `Key at index $R1: $R2` + IntOp $R0 $R0 - 1 + IntOp $R1 $R1 + 1 + ${Loop} + + nsJSON::Get /type /index 0 /end + Pop $R0 + DetailPrint `Type at index 0: $R0` + + nsJSON::Get /exists /index 0 /end + Pop $R0 + DetailPrint `Index 0 exists?: $R0` + + nsJSON::Get /count /end + Pop $R0 + DetailPrint `Count: $R0` + + nsJSON::Get /isempty /end + Pop $R0 + DetailPrint `Is empty?: $R0` + + nsJSON::Get /isempty /index 2 /end + Pop $R0 + DetailPrint `Is empty at index 2?: $R0` + + ClearErrors + nsJSON::Get /isempty /index 99 /end + IfErrors 0 +2 + DetailPrint `Is empty at index 99?: Error flag is set` + +SectionEnd + +Section /o `Delete tests` EXAMPLE15 + + nsJSON::Set /value `{ "a": "a", "b": 1, "c": {} }` + nsJSON::Serialize /format + Pop $R0 + DetailPrint `Test: $R0` + + nsJSON::Delete a /end + Pop $R0 + DetailPrint `Delete key "a": $R0` + + nsJSON::Serialize /format + Pop $R0 + DetailPrint `Now: $R0` + + ClearErrors + nsJSON::Delete /index 99 /end + IfErrors 0 +2 + DetailPrint `Delete at index 99: Error flag is set` + +SectionEnd + +Section /o `Empty keys` EXAMPLE16 + + nsJSON::Set /value `{ "": "a", 1, "c": {} }` + nsJSON::Serialize /format + Pop $R0 + DetailPrint `Test: $R0` + + nsJSON::Set a "" /value `"abc"` + nsJSON::Serialize /format + Pop $R0 + DetailPrint `Test: $R0` + +SectionEnd + +LangString Example1Desc ${LANG_ENGLISH} `Parses Example1.json and then generates Output\Example1.json` +LangString Example1BDesc ${LANG_ENGLISH} `Parses Example1_Unicode.json (no BOM) and then generates Unicode and ASCII copies` +LangString Example1CDesc ${LANG_ENGLISH} `Parses Example1_Unicode_UTF16BE.json (BOM) and then generates Unicode and ASCII copies` +LangString Example1DDesc ${LANG_ENGLISH} `Parses Example1_Unicode_UTF16LE.json (BOM) and then generates Unicode and ASCII copies` +LangString Example1EDesc ${LANG_ENGLISH} `Parses Example1_Unicode_UTF8.json (sig) and then generates Unicode and ASCII copies` +LangString Example2Desc ${LANG_ENGLISH} `Parses Example2.json and then generates Output\Example2.json` +LangString Example3Desc ${LANG_ENGLISH} `Parses Example3.json and then generates Output\Example3.json` +LangString Example4Desc ${LANG_ENGLISH} `Parses Example4.json and then generates Output\Example4.json` +LangString Example5Desc ${LANG_ENGLISH} `Parses Example5.json and then generates Output\Example5.json` +LangString Example5BDesc ${LANG_ENGLISH} `Parses Example5.json and then generates Output\Example5B.json using $$R0` +LangString Example6Desc ${LANG_ENGLISH} `Generates Output\Example6.json using Parse, Set and Serialize` +LangString Example7Desc ${LANG_ENGLISH} `Parses Example1.json and then reads values from the tree using Get` +LangString Example8Desc ${LANG_ENGLISH} `Tests JSON array manipulation while generating Output\Example8.json` +LangString Example9Desc ${LANG_ENGLISH} `Iterates through some nodes in Example4.json by index` +LangString Example10Desc ${LANG_ENGLISH} `Parses Example5.json into Example4.json and then generates Output\Example10.json` +LangString Example11Desc ${LANG_ENGLISH} `Parses Preferences.json and then generates Output\PreferencesNew.json` +LangString Example12Desc ${LANG_ENGLISH} `Parses Preferences2.json and then generates Output\Preferences2New.json` +LangString Example13Desc ${LANG_ENGLISH} `Parses Example2.json (size>NSIS_MAX_STRLEN) and outputs the result.` +LangString Example14Desc ${LANG_ENGLISH} `Simple Get function tests.` +LangString Example15Desc ${LANG_ENGLISH} `Simple Delete function tests.` +LangString Example16Desc ${LANG_ENGLISH} `Empty/missing value keys tests.` + +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE1} $(Example1Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE1B} $(Example1BDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE1C} $(Example1CDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE1D} $(Example1DDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE1E} $(Example1EDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE2} $(Example2Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE3} $(Example3Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE4} $(Example4Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE5} $(Example5Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE5B} $(Example5BDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE6} $(Example6Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE7} $(Example7Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE8} $(Example8Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE9} $(Example9Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE10} $(Example10Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE11} $(Example11Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE12} $(Example12Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE13} $(Example13Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE14} $(Example14Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE15} $(Example15Desc) + !insertmacro MUI_DESCRIPTION_TEXT ${EXAMPLE16} $(Example16Desc) +!insertmacro MUI_FUNCTION_DESCRIPTION_END \ No newline at end of file diff --git a/examples/HttpWebRequest.nsi b/examples/HttpWebRequest.nsi new file mode 100644 index 0000000..e6cb83e --- /dev/null +++ b/examples/HttpWebRequest.nsi @@ -0,0 +1,127 @@ +!include MUI2.nsh + +!define HttpWebRequestURL `http://www.afrowsoft.co.uk/test/HttpWebRequest.php` + +Name `nsJSON plug-in` +OutFile nsJSON_HttpWebRequest.exe +RequestExecutionLevel user +ShowInstDetails show + +!define MUI_COMPONENTSPAGE_SMALLDESC +!insertmacro MUI_PAGE_COMPONENTS +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_LANGUAGE English + +Section + + CreateDirectory $EXEDIR\Output + +SectionEnd + +Section /o `POST` DATAPOST + + StrCpy $R0 `{ "Name": "Jonathan Doe", "Age": 23, "Formula": "a + b == 13%!", "ViewTimes": [ "10:00", "13:43", "21:19", "03:10" ] }` + + nsJSON::Set /tree HttpWebRequest /value `{ "Url": "${HttpWebRequestURL}", "Verb": "POST", "Agent": "Mozilla/5.0 (Windows NT 10.0; rv:10.0) Gecko/20100101 Firefox/10.0" }` + DetailPrint `Send: $R0` + DetailPrint `Send to: ${HttpWebRequestURL}` + nsJSON::Set /tree HttpWebRequest Data /value $R0 + + nsJSON::Set /tree HttpWebRequest Params /value `{ "a": "string", "b": true, "c": 2 }` + + DetailPrint `Generate: $EXEDIR\Output\HttpWebRequest.json from HttpWebRequest` + nsJSON::Serialize /tree HttpWebRequest /format /file $EXEDIR\Output\HttpWebRequest.json + + DetailPrint `Download: ${HttpWebRequestURL}` + nsJSON::Set /tree HttpWebResponse /http HttpWebRequest + + DetailPrint `Generate: $EXEDIR\Output\HttpWebResponse.json from HttpWebResponse` + nsJSON::Serialize /tree HttpWebResponse /format /file $EXEDIR\Output\HttpWebResponse.json + +SectionEnd + +Section /o `Async POST` DATAPOSTASYNC + + StrCpy $R0 `{ "Name": "Jonathan Doe", "Age": 23, "Formula": "a + b == 13%!", "ViewTimes": [ "10:00", "13:43", "21:19", "03:10" ] }` + + nsJSON::Set /tree HttpWebRequest /value `{ "Url": "${HttpWebRequestURL}", "Verb": "POST", "Async": true }` + DetailPrint `Send: $R0` + DetailPrint `Send to: ${HttpWebRequestURL}` + nsJSON::Set /tree HttpWebRequest Data /value $R0 + + nsJSON::Set /tree HttpWebRequest Params /value `{ "a": "string", "b": true, "c": 2 }` + + DetailPrint `Generate: $EXEDIR\Output\HttpWebRequest.json from HttpWebRequest` + nsJSON::Serialize /tree HttpWebRequest /format /file $EXEDIR\Output\HttpWebRequest.json + + DetailPrint `Download: ${HttpWebRequestURL}` + nsJSON::Set /tree AsyncHttpWebResponse /http HttpWebRequest + + ; Wait until done. + ${Do} + Sleep 1000 + nsJSON::Wait HttpWebRequest /timeout 0 + Pop $R0 + ${If} $R0 != wait + ${Break} + ${EndIf} + DetailPrint `Waiting...` + ${Loop} + + DetailPrint `Finished...` + + DetailPrint `Generate: $EXEDIR\Output\AsyncHttpWebResponse.json from AsyncHttpWebResponse` + nsJSON::Serialize /tree AsyncHttpWebResponse /format /file $EXEDIR\Output\AsyncHttpWebResponse.json + +SectionEnd + +Section /o `Raw POST` RAWDATAPOST + + StrCpy $R0 `Name=Jonathan+Doe&Age=23&Formula=a+%2B+b+%3D%3D+13%25%21&ViewTimes[]=10%3A00&ViewTimes[]=13%3A43&ViewTimes[]=21%3A19&ViewTimes[]=03%3A10` + + nsJSON::Set /tree HttpWebRequest /value `{ "Url": "${HttpWebRequestURL}", "Verb": "POST", "DataType": "Raw" }` + DetailPrint `Send: $R0` + DetailPrint `Send to: ${HttpWebRequestURL}` + nsJSON::Set /tree HttpWebRequest Data /value `"$R0"` + + DetailPrint `Generate: $EXEDIR\Output\HttpWebRequest_Raw.json from HttpWebRequest` + nsJSON::Serialize /tree HttpWebRequest /format /file $EXEDIR\Output\HttpWebRequest_Raw.json + + DetailPrint `Download: ${HttpWebRequestURL}` + nsJSON::Set /tree HttpWebResponse /http HttpWebRequest + + DetailPrint `Generate: $EXEDIR\Output\HttpWebResponse_Raw.json from HttpWebResponse` + nsJSON::Serialize /tree HttpWebResponse /format /file $EXEDIR\Output\HttpWebResponse_Raw.json + +SectionEnd + +Section /o `JSON POST` JSONPOST + + nsJSON::Set /tree HttpWebRequest /value `{ "Url": "${HttpWebRequestURL}", "Verb": "POST", "DataType": "JSON" }` + DetailPrint `Send: $EXEDIR\Input\Example1.json` + DetailPrint `Send to: ${HttpWebRequestURL}` + nsJSON::Set /tree HttpWebRequest Data /file $EXEDIR\Input\Example1.json + + DetailPrint `Generate: $EXEDIR\Output\HttpWebRequest_JSON.json from HttpWebRequest` + nsJSON::Serialize /tree HttpWebRequest /format /file $EXEDIR\Output\HttpWebRequest_JSON.json + + DetailPrint `Download: ${HttpWebRequestURL}` + nsJSON::Set /tree HttpWebResponse /http HttpWebRequest + + DetailPrint `Generate: $EXEDIR\Output\HttpWebResponse_JSON.json from HttpWebResponse` + nsJSON::Serialize /tree HttpWebResponse /format /file $EXEDIR\Output\HttpWebResponse_JSON.json + +SectionEnd + +LangString DataPOSTDesc ${LANG_ENGLISH} `Sends POST data and parses the JSON response` +LangString DataPOSTAsyncDesc ${LANG_ENGLISH} `Asynchronously sends POST data and parses the JSON response` +LangString RawDataPOSTDesc ${LANG_ENGLISH} `Sends raw POST data and parses the JSON response` +LangString JSONPOSTDesc ${LANG_ENGLISH} `Sends Example1.json and parses the JSON response` + +!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${DATAPOST} $(DataPOSTDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${DATAPOSTASYNC} $(DataPOSTAsyncDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${RAWDATAPOST} $(RawDataPOSTDesc) + !insertmacro MUI_DESCRIPTION_TEXT ${JSONPOST} $(JSONPOSTDesc) +!insertmacro MUI_FUNCTION_DESCRIPTION_END \ No newline at end of file diff --git a/examples/Input/Example1.json b/examples/Input/Example1.json new file mode 100644 index 0000000..ac2fc3c --- /dev/null +++ b/examples/Input/Example1.json @@ -0,0 +1,22 @@ +{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +} \ No newline at end of file diff --git a/examples/Input/Example1_Unicode.json b/examples/Input/Example1_Unicode.json new file mode 100644 index 0000000..9b465ef Binary files /dev/null and b/examples/Input/Example1_Unicode.json differ diff --git a/examples/Input/Example1_Unicode_UTF16BE.json b/examples/Input/Example1_Unicode_UTF16BE.json new file mode 100644 index 0000000..c2a2ad3 Binary files /dev/null and b/examples/Input/Example1_Unicode_UTF16BE.json differ diff --git a/examples/Input/Example1_Unicode_UTF16LE.json b/examples/Input/Example1_Unicode_UTF16LE.json new file mode 100644 index 0000000..023c0db Binary files /dev/null and b/examples/Input/Example1_Unicode_UTF16LE.json differ diff --git a/examples/Input/Example1_Unicode_UTF8.json b/examples/Input/Example1_Unicode_UTF8.json new file mode 100644 index 0000000..886b30d --- /dev/null +++ b/examples/Input/Example1_Unicode_UTF8.json @@ -0,0 +1,22 @@ +{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": ["GML", "XML"] + }, + "GlossSee": "markup" + } + } + } + } +} \ No newline at end of file diff --git a/examples/Input/Example2.json b/examples/Input/Example2.json new file mode 100644 index 0000000..4b9a048 --- /dev/null +++ b/examples/Input/Example2.json @@ -0,0 +1,14 @@ +{ + "menu": { + "id": "file", + "value": "File", + "popup": { + "menuitem": [ + {"value": "New", "onclick": "CreateNewDoc()"}, + {"value": "Open", "onclick": "OpenDoc()"}, + {"value": "Close", "onclick": "CloseDoc()"}, + {"value": "Import", "onclick": "Import('somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_somereallylongvalue_')"} + ] + } + } +} \ No newline at end of file diff --git a/examples/Input/Example3.json b/examples/Input/Example3.json new file mode 100644 index 0000000..3bcb320 --- /dev/null +++ b/examples/Input/Example3.json @@ -0,0 +1,28 @@ +{ + "widget": { + "debug": "on", + "window": { + "title": "Sample Konfabulator Widget", + "name": "main_window", + "width": 500, + "height": 500 + }, + "image": { + "src": "Images/Sun.png", + "name": "sun1", + "hOffset": 250, + "vOffset": 250, + "alignment": "center" + }, + "text": { + "data": "Click Here", + "size": 36, + "style": "bold", + "name": "text1", + "hOffset": 250, + "vOffset": 100, + "alignment": "center", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;" + } + } +} \ No newline at end of file diff --git a/examples/Input/Example4.json b/examples/Input/Example4.json new file mode 100644 index 0000000..dbc7dee --- /dev/null +++ b/examples/Input/Example4.json @@ -0,0 +1,99 @@ +{ + "web-app": { + "servlet": [ + { + "servlet-name": "cofaxCDS", + "servlet-class": "org.cofax.cds.CDSServlet", + "init-param": { + "configGlossary:installationAt": "Philadelphia, PA", + "configGlossary:adminEmail": "ksm@pobox.com", + "configGlossary:poweredBy": "Cofax", + "configGlossary:poweredByIcon": "/images/cofax.gif", + "configGlossary:staticPath": "/content/static", + "templateProcessorClass": "org.cofax.WysiwygTemplate", + "templateLoaderClass": "org.cofax.FilesTemplateLoader", + "templatePath": "templates", + "templateOverridePath": "", + "defaultListTemplate": "listTemplate.htm", + "defaultFileTemplate": "articleTemplate.htm", + "useJSP": false, + "jspListTemplate": "listTemplate.jsp", + "jspFileTemplate": "articleTemplate.jsp", + "cachePackageTagsTrack": 200, + "cachePackageTagsStore": 200, + "cachePackageTagsRefresh": 60, + "cacheTemplatesTrack": 100, + "cacheTemplatesStore": 50, + "cacheTemplatesRefresh": 15, + "cachePagesTrack": 200, + "cachePagesStore": 100, + "cachePagesRefresh": 10, + "cachePagesDirtyRead": 10, + "searchEngineListTemplate": "forSearchEnginesList.htm", + "searchEngineFileTemplate": "forSearchEngines.htm", + "searchEngineRobotsDb": "WEB-INF/robots.db", + "useDataStore": true, + "dataStoreClass": "org.cofax.SqlDataStore", + "redirectionClass": "org.cofax.SqlRedirection", + "dataStoreName": "cofax", + "dataStoreDriver": "com.microsoft.jdbc.sqlserver.SQLServerDriver", + "dataStoreUrl": "jdbc:microsoft:sqlserver://LOCALHOST:1433;DatabaseName=goon", + "dataStoreUser": "sa", + "dataStorePassword": "dataStoreTestQuery", + "dataStoreTestQuery": "SET NOCOUNT ON;select test='test';", + "dataStoreLogFile": "/usr/local/tomcat/logs/datastore.log", + "dataStoreInitConns": 10, + "dataStoreMaxConns": 100, + "dataStoreConnUsageLimit": 100, + "dataStoreLogLevel": "debug", + "maxUrlLength": 500 + } + }, + { + "servlet-name": "cofaxEmail", + "servlet-class": "org.cofax.cds.EmailServlet", + "init-param": { + "mailHost": "mail1", + "mailHostOverride": "mail2" + } + }, + { + "servlet-name": "cofaxAdmin", + "servlet-class": "org.cofax.cds.AdminServlet" + }, + { + "servlet-name": "fileServlet", + "servlet-class": "org.cofax.cds.FileServlet" + }, + { + "servlet-name": "cofaxTools", + "servlet-class": "org.cofax.cms.CofaxToolsServlet", + "init-param": { + "templatePath": "toolstemplates/", + "log": 1, + "logLocation": "/usr/local/tomcat/logs/CofaxTools.log", + "logMaxSize": "", + "dataLog": 1, + "dataLogLocation": "/usr/local/tomcat/logs/dataLog.log", + "dataLogMaxSize": "", + "removePageCache": "/content/admin/remove?cache=pages&id=", + "removeTemplateCache": "/content/admin/remove?cache=templates&id=", + "fileTransferFolder": "/usr/local/tomcat/webapps/content/fileTransferFolder", + "lookInContext": 1, + "adminGroupID": 4, + "betaServer": true + } + } ], + "servlet-mapping": { + "cofaxCDS": "/", + "cofaxEmail": "/cofaxutil/aemail/*", + "cofaxAdmin": "/admin/*", + "fileServlet": "/static/*", + "cofaxTools": "/tools/*" + }, + "taglib": { + "taglib-uri": "cofax.tld", + "taglib-location": "/WEB-INF/tlds/cofax.tld" + } + } +} \ No newline at end of file diff --git a/examples/Input/Example5.json b/examples/Input/Example5.json new file mode 100644 index 0000000..0563743 --- /dev/null +++ b/examples/Input/Example5.json @@ -0,0 +1,29 @@ +{ + "menu": { + "header": "SVG Viewer", + "items": [ + {"id": "Open"}, + {"id": "OpenNew", "label": "Open New"}, + null, + {"id": "ZoomIn", "label": "Zoom In"}, + {"id": "ZoomOut", "label": "Zoom Out"}, + {"id": "OriginalView", "label": "Original View"}, + null, + {"id": "Quality"}, + {"id": "Pause"}, + {"id": "Mute"}, + null, + {"id": "Find", "label": "Find..."}, + {"id": "FindAgain", "label": "Find Again"}, + {"id": "Copy"}, + {"id": "CopyAgain", "label": "Copy Again"}, + {"id": "CopySVG", "label": "Copy SVG"}, + {"id": "ViewSVG", "label": "View SVG"}, + {"id": "ViewSource", "label": "View Source"}, + {"id": "SaveAs", "label": "Save As"}, + null, + {"id": "Help"}, + {"id": "About", "label": "About Adobe CVG Viewer..."} + ] + } +} \ No newline at end of file diff --git a/examples/Input/Preferences.json b/examples/Input/Preferences.json new file mode 100644 index 0000000..02681b7 --- /dev/null +++ b/examples/Input/Preferences.json @@ -0,0 +1,992 @@ +{ + "apps": { + "shortcuts_have_been_created": true + }, + "browser": { + "last_known_google_url": "https://www.google.co.uk/", + "last_prompted_google_url": "https://www.google.co.uk/", + "window_placement": { + "bottom": 1087, + "left": 682, + "maximized": false, + "right": 1627, + "top": 67, + "work_area_bottom": 1040, + "work_area_left": 0, + "work_area_right": 1920, + "work_area_top": 0 + } + }, + "countryid_at_install": 21843, + "default_apps_install_state": 3, + "default_search_provider": { + "alternate_urls": [ "{google:baseURL}#q={searchTerms}", "{google:baseURL}search#q={searchTerms}", "{google:baseURL}webhp#q={searchTerms}" ], + "enabled": true, + "encodings": "UTF-8", + "icon_url": "http://www.google.com/favicon.ico", + "id": "2", + "image_url": "{google:baseURL}searchbyimage/upload", + "image_url_post_params": "encoded_image={google:imageThumbnail},image_url={google:imageURL},sbisrc={google:imageSearchSource},original_width={google:imageOriginalWidth},original_height={google:imageOriginalHeight}", + "instant_url": "{google:baseURL}webhp?sourceid=chrome-instant&{google:RLZ}{google:forceInstantResults}{google:instantExtendedEnabledParameter}{google:ntpIsThemedParameter}{google:omniboxStartMarginParameter}ie={inputEncoding}", + "instant_url_post_params": "", + "keyword": "google.co.uk", + "name": "Google", + "new_tab_url": "{google:baseURL}_/chrome/newtab?{google:RLZ}{google:instantExtendedEnabledParameter}{google:ntpIsThemedParameter}ie={inputEncoding}", + "prepopulate_id": "1", + "search_terms_replacement_key": "espv", + "search_url": "{google:baseURL}search?q={searchTerms}&{google:RLZ}{google:originalQueryForSuggestion}{google:assistedQueryStats}{google:searchFieldtrialParameter}{google:bookmarkBarPinned}{google:searchClient}{google:sourceId}{google:instantExtendedEnabledParameter}{google:omniboxStartMarginParameter}ie={inputEncoding}", + "search_url_post_params": "", + "suggest_url": "{google:baseSuggestURL}search?{google:searchFieldtrialParameter}client={google:suggestClient}&gs_ri={google:suggestRid}&xssi=t&q={searchTerms}&{google:cursorPosition}{google:currentPageUrl}{google:pageClassification}sugkey={google:suggestAPIKeyParameter}", + "suggest_url_post_params": "" + }, + "extensions": { + "alerts": { + "initialized": true + }, + "autoupdate": { + "next_check": "13040578803125671", + "test": "E:\\", + "test2": "E:\\\"" + }, + "chrome_url_overrides": { + "bookmarks": [ "chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html" ] + }, + "known_disabled": [ ], + "last_chrome_version": "33.0.1750.154", + "settings": { + "ahfgeienlihckogmohjhadlkjgocpleb": { + "active_permissions": { + "api": [ "management", "webstorePrivate" ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "t", + "content_settings": [ ], + "creation_flags": 1, + "events": [ ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13040559080062671", + "location": 5, + "manifest": { + "app": { + "launch": { + "web_url": "https://chrome.google.com/webstore" + }, + "urls": [ "https://chrome.google.com/webstore" ] + }, + "description": "Chrome Web Store", + "icons": { + "128": "webstore_icon_128.png", + "16": "webstore_icon_16.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtl3tO0osjuzRsf6xtD2SKxPlTfuoy7AWoObysitBPvH5fE1NaAA1/2JkPWkVDhdLBWLaIBPYeXbzlHp3y4Vv/4XG+aN5qFE3z+1RU/NqkzVYHtIpVScf3DjTYtKVL66mzVGijSoAIwbFCC3LpGdaoe6Q1rSRDp76wR6jjFzsYwQIDAQAB", + "name": "Store", + "permissions": [ "webstorePrivate", "management" ], + "version": "0.2" + }, + "page_ordinal": "n", + "path": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\web_store", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "aohghmighlieiainnegkcijnfilokake": { + "ack_external": true, + "active_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "w", + "content_settings": [ ], + "creation_flags": 137, + "events": [ ], + "from_bookmark": false, + "from_webstore": true, + "granted_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13040559089376747", + "lastpingday": "13040550028882747", + "location": 1, + "manifest": { + "api_console_project_id": "619683526622", + "app": { + "launch": { + "local_path": "main.html" + } + }, + "container": "GOOGLE_DRIVE", + "current_locale": "en_GB", + "default_locale": "en_US", + "description": "Create and edit documents", + "icons": { + "128": "icon_128.png", + "16": "icon_16.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJhLK6fk/BWTEvJhywpk7jDe4A2r0bGXGOLZW4/AdBp3IiD9o9nx4YjLAtv0tIPxi7MvFd/GUUbQBwHT5wQWONJj1z/0Rc2qBkiJA0yqXh42p0snuA8dCfdlhOLsp7/XTMEwAVasjV5hC4awl78eKfJYlZ+8fM/UldLWJ/51iBQwIDAQAB", + "manifest_version": 2, + "name": "Google Docs", + "offline_enabled": true, + "update_url": "http://clients2.google.com/service/update2/crx", + "version": "0.5" + }, + "page_ordinal": "n", + "path": "aohghmighlieiainnegkcijnfilokake\\0.5_0", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "state": 1, + "was_installed_by_default": true + }, + "apdfllckaahabafndbhieahigkjlhalf": { + "ack_external": true, + "active_permissions": { + "api": [ "background", "clipboardRead", "clipboardWrite", "notifications", "unlimitedStorage" ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "yn", + "content_settings": [ ], + "creation_flags": 137, + "events": [ ], + "from_bookmark": false, + "from_webstore": true, + "granted_permissions": { + "api": [ "background", "clipboardRead", "clipboardWrite", "notifications", "unlimitedStorage" ], + "manifest_permissions": [ ] + }, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13040559088240747", + "lastpingday": "13040550028882747", + "location": 1, + "manifest": { + "app": { + "launch": { + "web_url": "https://drive.google.com/?usp=chrome_app" + }, + "urls": [ "http://docs.google.com/", "http://drive.google.com/", "https://docs.google.com/", "https://drive.google.com/" ] + }, + "background": { + "allow_js_access": false + }, + "current_locale": "en_GB", + "default_locale": "en_US", + "description": "Google Drive: create, share and keep all your stuff in one place.", + "icons": { + "128": "128.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIl5KlKwL2TSkntkpY3naLLz5jsN0YwjhZyObcTOK6Nda4Ie21KRqZau9lx5SHcLh7pE2/S9OiArb+na2dn7YK5EvH+aRXS1ec3uxVlBhqLdnleVgwgwlg5fH95I52IeHcoeK6pR4hW/Nv39GNlI/Uqk6O6GBCCsAxYrdxww9BiQIDAQAB", + "manifest_version": 2, + "name": "Google Drive", + "offline_enabled": true, + "options_page": "https://drive.google.com/settings", + "permissions": [ "background", "clipboardRead", "clipboardWrite", "notifications", "unlimitedStorage" ], + "update_url": "http://clients2.google.com/service/update2/crx", + "version": "6.3" + }, + "page_ordinal": "n", + "path": "apdfllckaahabafndbhieahigkjlhalf\\6.3_0", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "state": 1, + "was_installed_by_default": true + }, + "blpcfgokakmgnkcojhhkbfbldkacnbeo": { + "ack_external": true, + "active_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "x", + "content_settings": [ ], + "creation_flags": 153, + "events": [ ], + "from_bookmark": true, + "from_webstore": true, + "granted_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13040559088955747", + "lastpingday": "13040550028882747", + "location": 1, + "manifest": { + "app": { + "launch": { + "container": "tab", + "web_url": "http://www.youtube.com/?feature=ytca" + }, + "web_content": { + "enabled": true, + "origin": "http://www.youtube.com" + } + }, + "current_locale": "en_GB", + "default_locale": "en", + "description": "The world's most popular online video community.", + "icons": { + "128": "128.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC/HotmFlyuz5FaHaIbVBhhL4BwbcUtsfWwzgUMpZt5ZsLB2nW/Y5xwNkkPANYGdVsJkT2GPpRRIKBO5QiJ7jPMa3EZtcZHpkygBlQLSjMhdrAKevpKgIl6YTkwzNvExY6rzVDzeE9zqnIs33eppY4S5QcoALMxuSWlMKqgFQjHQIDAQAB", + "manifest_version": 2, + "name": "YouTube", + "permissions": [ "appNotifications" ], + "update_url": "http://clients2.google.com/service/update2/crx", + "version": "4.2.6" + }, + "page_ordinal": "n", + "path": "blpcfgokakmgnkcojhhkbfbldkacnbeo\\4.2.6_0", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "state": 1, + "was_installed_by_default": true + }, + "coobgpohoikkiipiblmjeljniedjpjpf": { + "ack_external": true, + "active_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "z", + "content_settings": [ ], + "creation_flags": 153, + "events": [ ], + "from_bookmark": true, + "from_webstore": true, + "granted_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13040559087816747", + "lastpingday": "13040550028882747", + "location": 1, + "manifest": { + "app": { + "launch": { + "web_url": "http://www.google.com/webhp?source=search_app" + }, + "urls": [ "*://www.google.com/search", "*://www.google.com/webhp", "*://www.google.com/imgres" ] + }, + "current_locale": "en_GB", + "default_locale": "en", + "description": "The fastest way to search the web.", + "icons": { + "128": "128.png", + "16": "16.png", + "32": "32.png", + "48": "48.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIiso3Loy5VJHL40shGhUl6it5ZG55XB9q/2EX6aa88jAxwPutbCgy5d9bm1YmBzLfSgpX4xcpgTU08ydWbd7b50fbkLsqWl1mRhxoqnN01kuNfv9Hbz9dWWYd+O4ZfD3L2XZs0wQqo0y6k64n+qeLkUMd1MIhf6MR8Xz1SOA8pwIDAQAB", + "manifest_version": 2, + "name": "Google Search", + "permissions": [ ], + "update_url": "http://clients2.google.com/service/update2/crx", + "version": "0.0.0.20" + }, + "page_ordinal": "n", + "path": "coobgpohoikkiipiblmjeljniedjpjpf\\0.0.0.20_0", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "state": 1, + "was_installed_by_default": true + }, + "eemcgdkfndhakfknompkggombfjjjeno": { + "active_permissions": { + "api": [ "bookmarks", "bookmarkManagerPrivate", "metricsPrivate", "systemPrivate", "tabs" ], + "explicit_host": [ "chrome://favicon/*", "chrome://resources/*" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13040559080062671", + "location": 5, + "manifest": { + "chrome_url_overrides": { + "bookmarks": "main.html" + }, + "content_security_policy": "object-src 'none'; script-src chrome://resources 'self'", + "description": "Bookmark Manager", + "icons": { + + }, + "incognito": "split", + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQcByy+eN9jzazWF/DPn7NW47sW7lgmpk6eKc0BQM18q8hvEM3zNm2n7HkJv/R6fU+X5mtqkDuKvq5skF6qqUF4oEyaleWDFhd1xFwV7JV+/DU7bZ00w2+6gzqsabkerFpoP33ZRIw7OviJenP0c0uWqDWF8EGSyMhB3txqhOtiQIDAQAB", + "manifest_version": 2, + "name": "Bookmark Manager", + "permissions": [ "bookmarks", "bookmarkManagerPrivate", "metricsPrivate", "systemPrivate", "tabs", "chrome://favicon/", "chrome://resources/" ], + "version": "0.1" + }, + "path": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\bookmark_manager", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "ennkphjdgehloodpbhlhldgbnhmacadg": { + "active_permissions": { + "api": [ "app.currentWindowInternal", "app.runtime", "app.window" ], + "explicit_host": [ "chrome://settings-frame/*" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ "app.runtime.onLaunched" ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13040559080062671", + "location": 5, + "manifest": { + "app": { + "background": { + "scripts": [ "settings_app.js" ] + } + }, + "description": "Settings", + "display_in_launcher": false, + "icons": { + "128": "settings_app_icon_128.png", + "16": "settings_app_icon_16.png", + "32": "settings_app_icon_32.png", + "48": "settings_app_icon_48.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoVDPGX6fvKPVVgc+gnkYlGqHuuapgFDyKhsy4z7UzRLO/95zXPv8h8e5EacqbAQJLUbP6DERH5jowyNEYVxq9GJyntJMwP1ejvoz/52hnY3CCGGCmttmKzzpp5zwLuq3iZf8bslwywfflNUYtaCFSDa0TtrBZz0aOPrAAd/AhNwIDAQAB", + "manifest_version": 2, + "name": "Settings", + "permissions": [ "chrome://settings-frame/" ], + "version": "0.2" + }, + "path": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\settings_app", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "running": false, + "was_installed_by_default": false + }, + "gfdkimpbcpahaombhbimeihdjnejgicl": { + "active_permissions": { + "api": [ "app.currentWindowInternal", "app.runtime", "app.window", "feedbackPrivate" ], + "explicit_host": [ "chrome://resources/*" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ "feedbackPrivate.onFeedbackRequested" ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13040559080062671", + "location": 5, + "manifest": { + "app": { + "background": { + "scripts": [ "js/event_handler.js" ] + }, + "content_security_policy": "default-src 'none'; script-src 'self' chrome://resources; style-src 'unsafe-inline' *; img-src *; media-src 'self'" + }, + "description": "User feedback extension", + "display_in_launcher": false, + "display_in_new_tab_page": false, + "icons": { + "32": "images/icon32.png", + "64": "images/icon64.png" + }, + "incognito": "split", + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMZElzFX2J1g1nRQ/8S3rg/1CjFyDltWOxQg+9M8aVgNVxbutEWFQz+oQzIP9BB67mJifULgiv12ToFKsae4NpEUR8sPZjiKDIHumc6pUdixOm8SJ5Rs16SMR6+VYxFUjlVW+5CA3IILptmNBxgpfyqoK0qRpBDIhGk1KDEZ4zqQIDAQAB", + "manifest_version": 2, + "name": "Feedback", + "permissions": [ "feedbackPrivate", "chrome://resources/" ], + "version": "1.0" + }, + "path": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\feedback", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "running": false, + "was_installed_by_default": false + }, + "mfehgcgbbipciphmccgaenjidiccnmng": { + "active_permissions": { + "api": [ "cloudPrintPrivate" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13040559080062671", + "location": 5, + "manifest": { + "app": { + "launch": { + "web_url": "https://www.google.com/cloudprint" + }, + "urls": [ "https://www.google.com/cloudprint/enable_chrome_connector" ] + }, + "description": "Cloud Print", + "display_in_launcher": false, + "icons": { + + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDqOhnwk4+HXVfGyaNsAQdU/js1Na56diW08oF1MhZiwzSnJsEaeuMN9od9q9N4ZdK3o1xXOSARrYdE+syV7Dl31nf6qz3A6K+D5NHe6sSB9yvYlIiN37jdWdrfxxE0pRYEVYZNTe3bzq3NkcYJlOdt1UPcpJB+isXpAGUKUvt7EQIDAQAB", + "name": "Cloud Print", + "permissions": [ "cloudPrintPrivate" ], + "version": "0.1" + }, + "path": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\cloud_print", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "mgndgikekgjfcpckkfioiadnlibdjbkf": { + "active_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "n", + "content_settings": [ ], + "creation_flags": 1, + "events": [ ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13040559080062671", + "location": 5, + "manifest": { + "app": { + "launch": { + "web_url": "http://THIS-WILL-BE-REPLACED" + } + }, + "description": "Chrome as an app", + "display_in_launcher": true, + "display_in_new_tab_page": false, + "icons": { + "128": "product_logo_128.png", + "16": "product_logo_16.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNuYLEQ1QPMcc5HfWI/9jiEf6FdJWqEtgRmIeI7qtjPLBM5oje+Ny2E2mTAhou5qdJiO2CHWdU1DQXY2F7Zu2gZaKZgHLfK4WimHxUT5Xd9/aro/R9PCzjguM1BLusiWYc9xlj1IsZpyiN1hcjU7SCnBhv1feQlv2WSB5KRiXwhQIDAQAB", + "name": "Chrome", + "version": "0.1" + }, + "page_ordinal": "n", + "path": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\chrome_app", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "neajdppkdcdipfabeoofebfddakdcjhd": { + "active_permissions": { + "api": [ "systemPrivate", "ttsEngine" ], + "explicit_host": [ "https://www.google.com/*" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ "ttsEngine.onPause", "ttsEngine.onResume", "ttsEngine.onSpeak", "ttsEngine.onStop" ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13040559080062671", + "location": 5, + "manifest": { + "background": { + "persistent": false, + "scripts": [ "tts_extension.js" ] + }, + "description": "Component extension providing speech via the Google network text-to-speech service.", + "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8GSbNUMGygqQTNDMFGIjZNcwXsHLzkNkHjWbuY37PbNdSDZ4VqlVjzbWqODSe+MjELdv5Keb51IdytnoGYXBMyqKmWpUrg+RnKvQ5ibWr4MW9pyIceOIdp9GrzC1WZGgTmZismYR3AjaIpufZ7xDdQQv+XrghPWCkdVqLN+qZDA1HU+DURznkMICiDDSH2sU0egm9UbWfS218bZqzKeQDiC3OnTPlaxcbJtKUuupIm5knjze3Wo9Ae9poTDMzKgchg0VlFCv3uqox+wlD8sjXBoyBCCK9HpImdVAF1a7jpdgiUHpPeV/26oYzM9/grltwNR3bzECQgSpyXp0eyoegwIDAQAB", + "manifest_version": 2, + "name": "Google Network Speech", + "permissions": [ "systemPrivate", "ttsEngine", "https://www.google.com/" ], + "tts_engine": { + "voices": [ { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "en-US", + "remote": true, + "voice_name": "Google US English" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "male", + "lang": "en-GB", + "remote": true, + "voice_name": "Google UK English Male" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "en-GB", + "remote": true, + "voice_name": "Google UK English Female" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "es-ES", + "remote": true, + "voice_name": "Google Español" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "fr-FR", + "remote": true, + "voice_name": "Google Français" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "it-IT", + "remote": true, + "voice_name": "Google Italiano" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "de-DE", + "remote": true, + "voice_name": "Google Deutsch" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "ja-JP", + "remote": true, + "voice_name": "Google 日本人" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "ko-KR", + "remote": true, + "voice_name": "Google 한국의" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "zh-CN", + "remote": true, + "voice_name": "Google 中国的" + } ] + }, + "version": "1.0" + }, + "path": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\network_speech_synthesis", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "nkeimhogjdpnpccoofpliimaahmaaome": { + "active_permissions": { + "api": [ "alarms", "desktopCapture", "webConnectable", "webrtcAudioPrivate", "webrtcLoggingPrivate", "system.cpu" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ "alarms.onAlarm", "runtime.onStartup" ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13040559080062671", + "location": 5, + "manifest": { + "background": { + "page": "background.html", + "persistent": false + }, + "externally_connectable": { + "matches": [ "https://*.google.com/hangouts*", "*://localhost/*" ] + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAQt2ZDdPfoSe/JI6ID5bgLHRCnCu9T36aYczmhw/tnv6QZB2I6WnOCMZXJZlRdqWc7w9jo4BWhYS50Vb4weMfh/I0On7VcRwJUgfAxW2cHB+EkmtI1v4v/OU24OqIa1Nmv9uRVeX0GjhQukdLNhAE6ACWooaf5kqKlCeK+1GOkQIDAQAB", + "manifest_version": 2, + "name": "Hangout Services", + "permissions": [ "alarms", "desktopCapture", "system.cpu", "webrtcAudioPrivate", "webrtcLoggingPrivate" ], + "version": "1.0" + }, + "path": "C:\\Program Files (x86)\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\hangout_services", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "nmmhkkegccagdldgiimedpiccmgmieda": { + "ack_external": true, + "active_permissions": { + "api": [ "app.currentWindowInternal", "app.runtime", "app.window", "identity", "webRequestInternal", "webview" ], + "explicit_host": [ "https://checkout.google.com/*", "https://sandbox.google.com/*", "https://www.google.com/*", "https://www.googleapis.com/*" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 137, + "events": [ "app.runtime.onLaunched" ], + "from_bookmark": false, + "from_webstore": true, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13040559086619747", + "lastpingday": "13040550028882747", + "location": 10, + "manifest": { + "app": { + "background": { + "scripts": [ "craw_background.js" ] + } + }, + "current_locale": "en_GB", + "default_locale": "en", + "description": "Google Wallet for digital goods", + "display_in_launcher": false, + "display_in_new_tab_page": false, + "icons": { + "128": "images/icon_128.png", + "16": "images/icon_16.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrKfMnLqViEyokd1wk57FxJtW2XXpGXzIHBzv9vQI/01UsuP0IV5/lj0wx7zJ/xcibUgDeIxobvv9XD+zO1MdjMWuqJFcKuSS4Suqkje6u+pMrTSGOSHq1bmBVh0kpToN8YoJs/P/yrRd7FEtAXTaFTGxQL4C385MeXSjaQfiRiQIDAQAB", + "manifest_version": 2, + "minimum_chrome_version": "29", + "name": "Google Wallet", + "oauth2": { + "auto_approve": true, + "client_id": "203784468217.apps.googleusercontent.com", + "scopes": [ "https://www.googleapis.com/auth/sierra", "https://www.googleapis.com/auth/sierrasandbox", "https://www.googleapis.com/auth/chromewebstore", "https://www.googleapis.com/auth/chromewebstore.readonly" ] + }, + "permissions": [ "identity", "webview", "https://checkout.google.com/", "https://sandbox.google.com/checkout/", "https://www.google.com/", "https://www.googleapis.com/*" ], + "update_url": "https://clients2.google.com/service/update2/crx", + "version": "0.0.6.1" + }, + "path": "nmmhkkegccagdldgiimedpiccmgmieda\\0.0.6.1_0", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "running": false, + "state": 1, + "was_installed_by_default": true + }, + "pjkljhegncpnkpknbcohdijeoejaedia": { + "ack_external": true, + "active_permissions": { + "api": [ "notifications" ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "y", + "content_settings": [ ], + "creation_flags": 137, + "events": [ ], + "from_bookmark": false, + "from_webstore": true, + "granted_permissions": { + "api": [ "notifications" ], + "manifest_permissions": [ ] + }, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13040559088625747", + "lastpingday": "13040550028882747", + "location": 1, + "manifest": { + "app": { + "launch": { + "container": "tab", + "web_url": "https://mail.google.com/mail/ca" + }, + "urls": [ "*://mail.google.com/mail/ca" ] + }, + "current_locale": "en_GB", + "default_locale": "en", + "description": "Fast, searchable email with less spam.", + "icons": { + "128": "128.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCuGglK43iAz3J9BEYK/Mz6ZhloIMMDqQSAaf3vJt4eHbTbSDsu4WdQ9dQDRcKlg8nwQdePBt0C3PSUBtiSNSS37Z3qEGfS7LCju3h6pI1Yr9MQtxw+jUa7kXXIS09VV73pEFUT/F7c6Qe8L5ZxgAcBvXBh1Fie63qb02I9XQ/CQIDAQAB", + "name": "Gmail", + "options_page": "https://mail.google.com/mail/ca/#settings", + "permissions": [ "notifications" ], + "update_url": "http://clients2.google.com/service/update2/crx", + "version": "7" + }, + "page_ordinal": "n", + "path": "pjkljhegncpnkpknbcohdijeoejaedia\\7_1", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "state": 1, + "was_installed_by_default": true + } + } + }, + "google": { + "services": { + "signin": { + "LSID": "", + "SID": "" + } + } + }, + "intl": { + "accept_languages": "en-GB,en-US,en" + }, + "invalidator": { + "client_id": "XAP6G30QHyob9Zo8hpj1aA==" + }, + "media": { + "device_id_salt": "mKmmRGOM8msXbtxkKMl0Hw==" + }, + "net": { + "http_server_properties": { + "servers": { + "accounts.google.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "accounts.youtube.com:443": { + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "apis.google.com:443": { + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "clients2.google.com:443": { + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "clients2.googleusercontent.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "developer.android.com:443": { + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "fonts.googleapis.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "oauth.googleusercontent.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "supports_spdy": false + }, + "s.ytimg.com:443": { + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "ssl.google-analytics.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "ssl.gstatic.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "themes.googleusercontent.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "tools.google.com:80": { + "alternate_protocol": { + "port": 80, + "protocol_str": "quic" + }, + "supports_spdy": false + }, + "www.google.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "www.youtube.com:443": { + "settings": { + "4": 100 + }, + "supports_spdy": true + } + }, + "version": 2 + } + }, + "plugins": { + "migrated_to_pepper_flash": true, + "plugins_list": [ ], + "removed_old_component_pepper_flash_settings": true + }, + "profile": { + "avatar_index": 0, + "content_settings": { + "clear_on_exit_migrated": true, + "pattern_pairs": { + + }, + "pref_version": 1 + }, + "created_by_version": "33.0.1750.154", + "exit_type": "Crashed", + "exited_cleanly": true, + "icon_version": 2, + "managed_user_id": "", + "name": "First user" + }, + "session": { + "restore_on_startup_migrated": true, + "startup_urls_migration_time": "13040559080052671" + }, + "sync_promo": { + "startup_count": 1 + }, + "translate_blocked_languages": [ "en" ], + "translate_whitelists": { + + } +} diff --git a/examples/Input/Preferences2.json b/examples/Input/Preferences2.json new file mode 100644 index 0000000..0562602 --- /dev/null +++ b/examples/Input/Preferences2.json @@ -0,0 +1,988 @@ +{ + "apps": { + "shortcuts_have_been_created": true + }, + "browser": { + "last_known_google_url": "https://www.google.com.ua/", + "last_prompted_google_url": "https://www.google.com.ua/", + "window_placement": { + "bottom": 919, + "left": 10, + "maximized": false, + "right": 1060, + "top": 10, + "work_area_bottom": 929, + "work_area_left": 0, + "work_area_right": 1280, + "work_area_top": 0 + } + }, + "countryid_at_install": 21077, + "default_apps_install_state": 3, + "distribution": { + "make_chrome_default_for_user": true + }, + "dns_prefetching": { + "host_referral_list": [ 2, [ "https://accounts.google.com/", [ "https://accounts.youtube.com/", 1.500430932, "https://fonts.googleapis.com/", 1.500430932, "https://oauth.googleusercontent.com/", 2.2733802, "https://ssl.gstatic.com/", 2.4847851279999995, "https://themes.googleusercontent.com/", 1.7184422639999999 ] ], [ "https://apis.google.com/", [ "https://apis.google.com/", 2.6037003999999997, "https://ssl.gstatic.com/", 2.2733802 ] ], [ "https://www.google.ru/", [ "https://accounts.google.com/", 2.2733802, "https://apis.google.com/", 3.2643407999999994, "https://developer.android.com/", 2.2733802, "https://fonts.googleapis.com/", 2.2733802, "https://ssl.google-analytics.com/", 2.6037003999999997, "https://ssl.gstatic.com/", 2.2733802, "https://themes.googleusercontent.com/", 3.2643407999999994, "https://www.google.com/", 2.2733802, "https://www.google.ru/", 5.576582199999999 ] ] ], + "startup_list": [ 1, "http://tools.google.com/", "https://accounts.google.com/", "https://accounts.youtube.com/", "https://clients2.google.com/", "https://clients2.googleusercontent.com/", "https://developer.android.com/", "https://fonts.googleapis.com/", "https://ssl.gstatic.com/", "https://www.google.com/", "https://www.google.ru/" ] + }, + "extensions": { + "alerts": { + "initialized": true + }, + "autoupdate": { + "next_check": "13039550398202000" + }, + "chrome_url_overrides": { + "bookmarks": [ "chrome-extension://eemcgdkfndhakfknompkggombfjjjeno/main.html" ] + }, + "known_disabled": [ ], + "last_chrome_version": "33.0.1750.154", + "settings": { + "ahfgeienlihckogmohjhadlkjgocpleb": { + "active_permissions": { + "api": [ "management", "webstorePrivate" ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "t", + "content_settings": [ ], + "creation_flags": 1, + "events": [ ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13039532121147000", + "location": 5, + "manifest": { + "app": { + "launch": { + "web_url": "https://chrome.google.com/webstore" + }, + "urls": [ "https://chrome.google.com/webstore" ] + }, + "description": "Chrome Web Store", + "icons": { + "128": "webstore_icon_128.png", + "16": "webstore_icon_16.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCtl3tO0osjuzRsf6xtD2SKxPlTfuoy7AWoObysitBPvH5fE1NaAA1/2JkPWkVDhdLBWLaIBPYeXbzlHp3y4Vv/4XG+aN5qFE3z+1RU/NqkzVYHtIpVScf3DjTYtKVL66mzVGijSoAIwbFCC3LpGdaoe6Q1rSRDp76wR6jjFzsYwQIDAQAB", + "name": "???????", + "permissions": [ "webstorePrivate", "management" ], + "version": "0.2" + }, + "page_ordinal": "n", + "path": "C:\\Program Files\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\web_store", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "aohghmighlieiainnegkcijnfilokake": { + "ack_external": true, + "active_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "w", + "content_settings": [ ], + "creation_flags": 137, + "events": [ ], + "from_bookmark": false, + "from_webstore": true, + "granted_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13039532128470599", + "lastpingday": "13039513199557599", + "location": 1, + "manifest": { + "api_console_project_id": "619683526622", + "app": { + "launch": { + "local_path": "main.html" + } + }, + "container": "GOOGLE_DRIVE", + "current_locale": "ru", + "default_locale": "en_US", + "description": "???????? ? ?????????????? ??????????", + "icons": { + "128": "icon_128.png", + "16": "icon_16.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJhLK6fk/BWTEvJhywpk7jDe4A2r0bGXGOLZW4/AdBp3IiD9o9nx4YjLAtv0tIPxi7MvFd/GUUbQBwHT5wQWONJj1z/0Rc2qBkiJA0yqXh42p0snuA8dCfdlhOLsp7/XTMEwAVasjV5hC4awl78eKfJYlZ+8fM/UldLWJ/51iBQwIDAQAB", + "manifest_version": 2, + "name": "????????? Google", + "offline_enabled": true, + "update_url": "http://clients2.google.com/service/update2/crx", + "version": "0.5" + }, + "page_ordinal": "n", + "path": "aohghmighlieiainnegkcijnfilokake\\0.5_0", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "state": 1, + "was_installed_by_default": true + }, + "apdfllckaahabafndbhieahigkjlhalf": { + "ack_external": true, + "active_permissions": { + "api": [ "background", "clipboardRead", "clipboardWrite", "notifications", "unlimitedStorage" ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "x", + "content_settings": [ ], + "creation_flags": 137, + "events": [ ], + "from_bookmark": false, + "from_webstore": true, + "granted_permissions": { + "api": [ "background", "clipboardRead", "clipboardWrite", "notifications", "unlimitedStorage" ], + "manifest_permissions": [ ] + }, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13039532126820599", + "lastpingday": "13039513199557599", + "location": 1, + "manifest": { + "app": { + "launch": { + "web_url": "https://drive.google.com/?usp=chrome_app" + }, + "urls": [ "http://docs.google.com/", "http://drive.google.com/", "https://docs.google.com/", "https://drive.google.com/" ] + }, + "background": { + "allow_js_access": false + }, + "current_locale": "ru", + "default_locale": "en_US", + "description": "???? Google: ??????????, ?????????? ? ??????? ??? ???? ????? ? ??????? ?????? ???????.", + "icons": { + "128": "128.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIl5KlKwL2TSkntkpY3naLLz5jsN0YwjhZyObcTOK6Nda4Ie21KRqZau9lx5SHcLh7pE2/S9OiArb+na2dn7YK5EvH+aRXS1ec3uxVlBhqLdnleVgwgwlg5fH95I52IeHcoeK6pR4hW/Nv39GNlI/Uqk6O6GBCCsAxYrdxww9BiQIDAQAB", + "manifest_version": 2, + "name": "???? Google", + "offline_enabled": true, + "options_page": "https://drive.google.com/settings", + "permissions": [ "background", "clipboardRead", "clipboardWrite", "notifications", "unlimitedStorage" ], + "update_url": "http://clients2.google.com/service/update2/crx", + "version": "6.3" + }, + "page_ordinal": "n", + "path": "apdfllckaahabafndbhieahigkjlhalf\\6.3_0", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "state": 1, + "was_installed_by_default": true + }, + "blpcfgokakmgnkcojhhkbfbldkacnbeo": { + "ack_external": true, + "active_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "yn", + "content_settings": [ ], + "creation_flags": 153, + "events": [ ], + "from_bookmark": true, + "from_webstore": true, + "granted_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13039532126409599", + "lastpingday": "13039513199557599", + "location": 1, + "manifest": { + "app": { + "launch": { + "container": "tab", + "web_url": "http://www.youtube.com/?feature=ytca" + }, + "web_content": { + "enabled": true, + "origin": "http://www.youtube.com" + } + }, + "current_locale": "ru", + "default_locale": "en", + "description": "????? ?????????? ? ???? ???????????????.", + "icons": { + "128": "128.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC/HotmFlyuz5FaHaIbVBhhL4BwbcUtsfWwzgUMpZt5ZsLB2nW/Y5xwNkkPANYGdVsJkT2GPpRRIKBO5QiJ7jPMa3EZtcZHpkygBlQLSjMhdrAKevpKgIl6YTkwzNvExY6rzVDzeE9zqnIs33eppY4S5QcoALMxuSWlMKqgFQjHQIDAQAB", + "manifest_version": 2, + "name": "YouTube", + "permissions": [ "appNotifications" ], + "update_url": "http://clients2.google.com/service/update2/crx", + "version": "4.2.6" + }, + "page_ordinal": "n", + "path": "blpcfgokakmgnkcojhhkbfbldkacnbeo\\4.2.6_0", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "state": 1, + "was_installed_by_default": true + }, + "coobgpohoikkiipiblmjeljniedjpjpf": { + "ack_external": true, + "active_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "z", + "content_settings": [ ], + "creation_flags": 153, + "events": [ ], + "from_bookmark": true, + "from_webstore": true, + "granted_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13039532127382599", + "lastpingday": "13039513199557599", + "location": 1, + "manifest": { + "app": { + "launch": { + "web_url": "http://www.google.com/webhp?source=search_app" + }, + "urls": [ "*://www.google.com/search", "*://www.google.com/webhp", "*://www.google.com/imgres" ] + }, + "current_locale": "ru", + "default_locale": "en", + "description": "????? ??????? ????? ? ?????????.", + "icons": { + "128": "128.png", + "16": "16.png", + "32": "32.png", + "48": "48.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIiso3Loy5VJHL40shGhUl6it5ZG55XB9q/2EX6aa88jAxwPutbCgy5d9bm1YmBzLfSgpX4xcpgTU08ydWbd7b50fbkLsqWl1mRhxoqnN01kuNfv9Hbz9dWWYd+O4ZfD3L2XZs0wQqo0y6k64n+qeLkUMd1MIhf6MR8Xz1SOA8pwIDAQAB", + "manifest_version": 2, + "name": "????? Google", + "permissions": [ ], + "update_url": "http://clients2.google.com/service/update2/crx", + "version": "0.0.0.20" + }, + "page_ordinal": "n", + "path": "coobgpohoikkiipiblmjeljniedjpjpf\\0.0.0.20_0", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "state": 1, + "was_installed_by_default": true + }, + "eemcgdkfndhakfknompkggombfjjjeno": { + "active_permissions": { + "api": [ "bookmarks", "bookmarkManagerPrivate", "metricsPrivate", "systemPrivate", "tabs" ], + "explicit_host": [ "chrome://favicon/*", "chrome://resources/*" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13039532121147000", + "location": 5, + "manifest": { + "chrome_url_overrides": { + "bookmarks": "main.html" + }, + "content_security_policy": "object-src 'none'; script-src chrome://resources 'self'", + "description": "Bookmark Manager", + "icons": { + + }, + "incognito": "split", + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQcByy+eN9jzazWF/DPn7NW47sW7lgmpk6eKc0BQM18q8hvEM3zNm2n7HkJv/R6fU+X5mtqkDuKvq5skF6qqUF4oEyaleWDFhd1xFwV7JV+/DU7bZ00w2+6gzqsabkerFpoP33ZRIw7OviJenP0c0uWqDWF8EGSyMhB3txqhOtiQIDAQAB", + "manifest_version": 2, + "name": "Bookmark Manager", + "permissions": [ "bookmarks", "bookmarkManagerPrivate", "metricsPrivate", "systemPrivate", "tabs", "chrome://favicon/", "chrome://resources/" ], + "version": "0.1" + }, + "path": "C:\\Program Files\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\bookmark_manager", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "ennkphjdgehloodpbhlhldgbnhmacadg": { + "active_permissions": { + "api": [ "app.currentWindowInternal", "app.runtime", "app.window" ], + "explicit_host": [ "chrome://settings-frame/*" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ "app.runtime.onLaunched" ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13039532121163000", + "location": 5, + "manifest": { + "app": { + "background": { + "scripts": [ "settings_app.js" ] + } + }, + "description": "Settings", + "display_in_launcher": false, + "icons": { + "128": "settings_app_icon_128.png", + "16": "settings_app_icon_16.png", + "32": "settings_app_icon_32.png", + "48": "settings_app_icon_48.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoVDPGX6fvKPVVgc+gnkYlGqHuuapgFDyKhsy4z7UzRLO/95zXPv8h8e5EacqbAQJLUbP6DERH5jowyNEYVxq9GJyntJMwP1ejvoz/52hnY3CCGGCmttmKzzpp5zwLuq3iZf8bslwywfflNUYtaCFSDa0TtrBZz0aOPrAAd/AhNwIDAQAB", + "manifest_version": 2, + "name": "Settings", + "permissions": [ "chrome://settings-frame/" ], + "version": "0.2" + }, + "path": "C:\\Program Files\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\settings_app", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "running": false, + "was_installed_by_default": false + }, + "gfdkimpbcpahaombhbimeihdjnejgicl": { + "active_permissions": { + "api": [ "app.currentWindowInternal", "app.runtime", "app.window", "feedbackPrivate" ], + "explicit_host": [ "chrome://resources/*" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ "feedbackPrivate.onFeedbackRequested" ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13039532121163000", + "location": 5, + "manifest": { + "app": { + "background": { + "scripts": [ "js/event_handler.js" ] + }, + "content_security_policy": "default-src 'none'; script-src 'self' chrome://resources; style-src 'unsafe-inline' *; img-src *; media-src 'self'" + }, + "description": "User feedback extension", + "display_in_launcher": false, + "display_in_new_tab_page": false, + "icons": { + "32": "images/icon32.png", + "64": "images/icon64.png" + }, + "incognito": "split", + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMZElzFX2J1g1nRQ/8S3rg/1CjFyDltWOxQg+9M8aVgNVxbutEWFQz+oQzIP9BB67mJifULgiv12ToFKsae4NpEUR8sPZjiKDIHumc6pUdixOm8SJ5Rs16SMR6+VYxFUjlVW+5CA3IILptmNBxgpfyqoK0qRpBDIhGk1KDEZ4zqQIDAQAB", + "manifest_version": 2, + "name": "Feedback", + "permissions": [ "feedbackPrivate", "chrome://resources/" ], + "version": "1.0" + }, + "path": "C:\\Program Files\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\feedback", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "running": false, + "was_installed_by_default": false + }, + "mfehgcgbbipciphmccgaenjidiccnmng": { + "active_permissions": { + "api": [ "cloudPrintPrivate" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13039532121147000", + "location": 5, + "manifest": { + "app": { + "launch": { + "web_url": "https://www.google.com/cloudprint" + }, + "urls": [ "https://www.google.com/cloudprint/enable_chrome_connector" ] + }, + "description": "Cloud Print", + "display_in_launcher": false, + "icons": { + + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDqOhnwk4+HXVfGyaNsAQdU/js1Na56diW08oF1MhZiwzSnJsEaeuMN9od9q9N4ZdK3o1xXOSARrYdE+syV7Dl31nf6qz3A6K+D5NHe6sSB9yvYlIiN37jdWdrfxxE0pRYEVYZNTe3bzq3NkcYJlOdt1UPcpJB+isXpAGUKUvt7EQIDAQAB", + "name": "Cloud Print", + "permissions": [ "cloudPrintPrivate" ], + "version": "0.1" + }, + "path": "C:\\Program Files\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\cloud_print", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "mgndgikekgjfcpckkfioiadnlibdjbkf": { + "active_permissions": { + "api": [ ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "n", + "content_settings": [ ], + "creation_flags": 1, + "events": [ ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13039532121163000", + "location": 5, + "manifest": { + "app": { + "launch": { + "web_url": "http://THIS-WILL-BE-REPLACED" + } + }, + "description": "Chrome as an app", + "display_in_launcher": true, + "display_in_new_tab_page": false, + "icons": { + "128": "product_logo_128.png", + "16": "product_logo_16.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNuYLEQ1QPMcc5HfWI/9jiEf6FdJWqEtgRmIeI7qtjPLBM5oje+Ny2E2mTAhou5qdJiO2CHWdU1DQXY2F7Zu2gZaKZgHLfK4WimHxUT5Xd9/aro/R9PCzjguM1BLusiWYc9xlj1IsZpyiN1hcjU7SCnBhv1feQlv2WSB5KRiXwhQIDAQAB", + "name": "Chrome", + "version": "0.1" + }, + "page_ordinal": "n", + "path": "C:\\Program Files\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\chrome_app", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "neajdppkdcdipfabeoofebfddakdcjhd": { + "active_permissions": { + "api": [ "systemPrivate", "ttsEngine" ], + "explicit_host": [ "https://www.google.com/*" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ "ttsEngine.onPause", "ttsEngine.onResume", "ttsEngine.onSpeak", "ttsEngine.onStop" ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13039532121163000", + "location": 5, + "manifest": { + "background": { + "persistent": false, + "scripts": [ "tts_extension.js" ] + }, + "description": "Component extension providing speech via the Google network text-to-speech service.", + "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8GSbNUMGygqQTNDMFGIjZNcwXsHLzkNkHjWbuY37PbNdSDZ4VqlVjzbWqODSe+MjELdv5Keb51IdytnoGYXBMyqKmWpUrg+RnKvQ5ibWr4MW9pyIceOIdp9GrzC1WZGgTmZismYR3AjaIpufZ7xDdQQv+XrghPWCkdVqLN+qZDA1HU+DURznkMICiDDSH2sU0egm9UbWfS218bZqzKeQDiC3OnTPlaxcbJtKUuupIm5knjze3Wo9Ae9poTDMzKgchg0VlFCv3uqox+wlD8sjXBoyBCCK9HpImdVAF1a7jpdgiUHpPeV/26oYzM9/grltwNR3bzECQgSpyXp0eyoegwIDAQAB", + "manifest_version": 2, + "name": "Google Network Speech", + "permissions": [ "systemPrivate", "ttsEngine", "https://www.google.com/" ], + "tts_engine": { + "voices": [ { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "en-US", + "remote": true, + "voice_name": "Google US English" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "male", + "lang": "en-GB", + "remote": true, + "voice_name": "Google UK English Male" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "en-GB", + "remote": true, + "voice_name": "Google UK English Female" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "es-ES", + "remote": true, + "voice_name": "Google Espaol" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "fr-FR", + "remote": true, + "voice_name": "Google Franais" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "it-IT", + "remote": true, + "voice_name": "Google Italiano" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "de-DE", + "remote": true, + "voice_name": "Google Deutsch" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "ja-JP", + "remote": true, + "voice_name": "Google ???" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "ko-KR", + "remote": true, + "voice_name": "Google ???" + }, { + "event_types": [ "start", "end", "error" ], + "gender": "female", + "lang": "zh-CN", + "remote": true, + "voice_name": "Google ???" + } ] + }, + "version": "1.0" + }, + "path": "C:\\Program Files\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\network_speech_synthesis", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "nkeimhogjdpnpccoofpliimaahmaaome": { + "active_permissions": { + "api": [ "alarms", "desktopCapture", "webConnectable", "webrtcAudioPrivate", "webrtcLoggingPrivate", "system.cpu" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 1, + "events": [ "alarms.onAlarm", "runtime.onStartup" ], + "from_bookmark": false, + "from_webstore": false, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13039532121163000", + "location": 5, + "manifest": { + "background": { + "page": "background.html", + "persistent": false + }, + "externally_connectable": { + "matches": [ "https://*.google.com/hangouts*", "*://localhost/*" ] + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAQt2ZDdPfoSe/JI6ID5bgLHRCnCu9T36aYczmhw/tnv6QZB2I6WnOCMZXJZlRdqWc7w9jo4BWhYS50Vb4weMfh/I0On7VcRwJUgfAxW2cHB+EkmtI1v4v/OU24OqIa1Nmv9uRVeX0GjhQukdLNhAE6ACWooaf5kqKlCeK+1GOkQIDAQAB", + "manifest_version": 2, + "name": "Hangout Services", + "permissions": [ "alarms", "desktopCapture", "system.cpu", "webrtcAudioPrivate", "webrtcLoggingPrivate" ], + "version": "1.0" + }, + "path": "C:\\Program Files\\Google\\Chrome\\Application\\33.0.1750.154\\resources\\hangout_services", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "was_installed_by_default": false + }, + "nmmhkkegccagdldgiimedpiccmgmieda": { + "ack_external": true, + "active_permissions": { + "api": [ "app.currentWindowInternal", "app.runtime", "app.window", "identity", "webRequestInternal", "webview" ], + "explicit_host": [ "https://checkout.google.com/*", "https://sandbox.google.com/*", "https://www.google.com/*", "https://www.googleapis.com/*" ], + "manifest_permissions": [ ] + }, + "content_settings": [ ], + "creation_flags": 137, + "events": [ "app.runtime.onLaunched" ], + "from_bookmark": false, + "from_webstore": true, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "initial_keybindings_set": true, + "install_time": "13039532125950599", + "lastpingday": "13039513199557599", + "location": 10, + "manifest": { + "app": { + "background": { + "scripts": [ "craw_background.js" ] + } + }, + "current_locale": "ru", + "default_locale": "en", + "description": "Google ??????? ??? ???????? ???????", + "display_in_launcher": false, + "display_in_new_tab_page": false, + "icons": { + "128": "images/icon_128.png", + "16": "images/icon_16.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrKfMnLqViEyokd1wk57FxJtW2XXpGXzIHBzv9vQI/01UsuP0IV5/lj0wx7zJ/xcibUgDeIxobvv9XD+zO1MdjMWuqJFcKuSS4Suqkje6u+pMrTSGOSHq1bmBVh0kpToN8YoJs/P/yrRd7FEtAXTaFTGxQL4C385MeXSjaQfiRiQIDAQAB", + "manifest_version": 2, + "minimum_chrome_version": "29", + "name": "Google ???????", + "oauth2": { + "auto_approve": true, + "client_id": "203784468217.apps.googleusercontent.com", + "scopes": [ "https://www.googleapis.com/auth/sierra", "https://www.googleapis.com/auth/sierrasandbox", "https://www.googleapis.com/auth/chromewebstore", "https://www.googleapis.com/auth/chromewebstore.readonly" ] + }, + "permissions": [ "identity", "webview", "https://checkout.google.com/", "https://sandbox.google.com/checkout/", "https://www.google.com/", "https://www.googleapis.com/*" ], + "update_url": "https://clients2.google.com/service/update2/crx", + "version": "0.0.6.1" + }, + "path": "nmmhkkegccagdldgiimedpiccmgmieda\\0.0.6.1_0", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "running": false, + "state": 1, + "was_installed_by_default": true + }, + "pjkljhegncpnkpknbcohdijeoejaedia": { + "ack_external": true, + "active_permissions": { + "api": [ "notifications" ], + "manifest_permissions": [ ] + }, + "app_launcher_ordinal": "y", + "content_settings": [ ], + "creation_flags": 137, + "events": [ ], + "from_bookmark": false, + "from_webstore": true, + "granted_permissions": { + "api": [ "notifications" ], + "manifest_permissions": [ ] + }, + "incognito_content_settings": [ ], + "incognito_preferences": { + + }, + "install_time": "13039532128003599", + "lastpingday": "13039513199557599", + "location": 1, + "manifest": { + "app": { + "launch": { + "container": "tab", + "web_url": "https://mail.google.com/mail/ca" + }, + "urls": [ "*://mail.google.com/mail/ca" ] + }, + "current_locale": "ru", + "default_locale": "en", + "description": "??????? ????? ??? ?????, ? ??????? ????? ????? ????? ??????.", + "icons": { + "128": "128.png" + }, + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCuGglK43iAz3J9BEYK/Mz6ZhloIMMDqQSAaf3vJt4eHbTbSDsu4WdQ9dQDRcKlg8nwQdePBt0C3PSUBtiSNSS37Z3qEGfS7LCju3h6pI1Yr9MQtxw+jUa7kXXIS09VV73pEFUT/F7c6Qe8L5ZxgAcBvXBh1Fie63qb02I9XQ/CQIDAQAB", + "name": "Gmail", + "options_page": "https://mail.google.com/mail/ca/#settings", + "permissions": [ "notifications" ], + "update_url": "http://clients2.google.com/service/update2/crx", + "version": "7" + }, + "page_ordinal": "n", + "path": "pjkljhegncpnkpknbcohdijeoejaedia\\7_1", + "preferences": { + + }, + "regular_only_preferences": { + + }, + "state": 1, + "was_installed_by_default": true + } + } + }, + "google": { + "services": { + "signin": { + "LSID": "", + "SID": "" + } + } + }, + "intl": { + "accept_languages": "ru-RU,ru,en-US,en" + }, + "invalidator": { + "client_id": "74Cf4DoEgWG6IJy/9sVadA==" + }, + "media": { + "device_id_salt": "Pnq2GfzhDdJpVy0Q1hZhCg==" + }, + "net": { + "http_server_properties": { + "servers": { + "accounts.google.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "accounts.youtube.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "apis.google.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "clients2.google.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "clients2.googleusercontent.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "developer.android.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "fonts.googleapis.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "oauth.googleusercontent.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "ssl.google-analytics.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "ssl.gstatic.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "themes.googleusercontent.com:443": { + "alternate_protocol": { + "port": 443, + "protocol_str": "quic" + }, + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "tools.google.com:80": { + "alternate_protocol": { + "port": 80, + "protocol_str": "quic" + }, + "supports_spdy": false + }, + "www.google.com:443": { + "settings": { + "4": 100 + }, + "supports_spdy": true + }, + "www.google.ru:443": { + "settings": { + "4": 100 + }, + "supports_spdy": true + } + }, + "version": 2 + } + }, + "pinned_tabs": [ ], + "plugins": { + "migrated_to_pepper_flash": true, + "plugins_list": [ ], + "removed_old_component_pepper_flash_settings": true + }, + "profile": { + "avatar_index": 0, + "content_settings": { + "clear_on_exit_migrated": true, + "pattern_pairs": { + + }, + "pref_version": 1 + }, + "exit_type": "Normal", + "exited_cleanly": true, + "icon_version": 2, + "managed_user_id": "", + "name": "?????? ????????????" + }, + "session": { + "restore_on_startup_migrated": true, + "startup_urls_migration_time": "13039532121147000" + }, + "sync_promo": { + "startup_count": 1 + }, + "translate_blocked_languages": [ "ru" ], + "translate_whitelists": { + + }, + "variations_seed": "CigyMjQ0ZTcyMjY2MThkZGRiOWQwMjAwOTUzZjIxYzFlNzBkM2UyYTVmEoABChtBVkZvdW5kYXRpb25NYWNWaWRlb0NhcHR1cmUYxP7/lwU4AUIIRGlzYWJsZWRKJgoNRW5hYmxlZEJ5RmxhZxAAKhNlbmFibGUtYXZmb3VuZGF0aW9uSgsKB0VuYWJsZWQQMkoMCghEaXNhYmxlZBAyUgwSBDMzLiogACABKAESqAEKCEFzeW5jRG5zGMSUtpcFOABCCGRpc2FibGVkSg4KClN5c3RlbURuc0EQAEoOCgpTeXN0ZW1EbnNCEABKFwoTQXN5bmNEbnNOb0ZhbGxiYWNrQRAAShcKE0FzeW5jRG5zTm9GYWxsYmFja0IQAEoNCglBc3luY0Ruc0EQAEoNCglBc3luY0Ruc0IQAEoMCghkaXNhYmxlZBBkUgwSBDI2LiogAiADKAMSbgoIQXN5bmNEbnMYxPfplAU4AEIKU3lzdGVtRG5zQUoOCgpTeXN0ZW1EbnNBEBlKDgoKU3lzdGVtRG5zQhAZSg0KCUFzeW5jRG5zQRAZSg0KCUFzeW5jRG5zQhAZUhASBDI5LiogACABKAAoASgCEmkKCEFzeW5jRG5zGMSMw44FOABCCUFzeW5jRG5zQUoOCgpTeXN0ZW1EbnNBEDJKDgoKU3lzdGVtRG5zQhAySg0KCUFzeW5jRG5zQRAySg0KCUFzeW5jRG5zQhAyUgwSBDI4LiogAigBKAISawoIQXN5bmNEbnMYxIaKkQU4AEIJQXN5bmNEbnNBSg4KClN5c3RlbURuc0EQGUoOCgpTeXN0ZW1EbnNCEBlKDgoJQXN5bmNEbnNBENsDSg4KCUFzeW5jRG5zQhDbA1IMEgQyNi4qIAMoASgCEmoKCEFzeW5jRG5zGMT36ZQFOABCClN5c3RlbURuc0FKDwoKU3lzdGVtRG5zQRDCA0oPCgpTeXN0ZW1EbnNCEMIDSg0KCUFzeW5jRG5zQRAySg0KCUFzeW5jRG5zQhAyUgoSBDMxLiogAigAEqcIChpBdXRvY29tcGxldGVEeW5hbWljVHJpYWxfMhiAvOmjBTgBQhlEZWZhdWx0Q29udHJvbF9Nb2JpbGVCZXRhSjsKLUVuYWJsZUVudGl0eVN1Z2dlc3RfUjFfQW5kcm9pZEJldGFfRXhwZXJpbWVudBBGGJ+TygEgn5PKAUo4CipFbmFibGVFbnRpdHlTdWdnZXN0X1IxX0FuZHJvaWRCZXRhX0NvbnRyb2wQRhigk8oBIKCTygFKPAouRW5hYmxlUHJvZmlsZVN1Z2dlc3RfUjFfQW5kcm9pZEJldGFfRXhwZXJpbWVudBBGGKGTygEgoZPKAUo5CitFbmFibGVQcm9maWxlU3VnZ2VzdF9SMV9BbmRyb2lkQmV0YV9Db250cm9sEEYYopPKASCik8oBSkEKM0VuYWJsZVBlcnNvbmFsaXplZFN1Z2dlc3RfUjFfQW5kcm9pZEJldGFfRXhwZXJpbWVudBBGGKOTygEgo5PKAUo+CjBFbmFibGVQZXJzb25hbGl6ZWRTdWdnZXN0X1IxX0FuZHJvaWRCZXRhX0NvbnRyb2wQRhikk8oBIKSTygFKPAouRW5hYmxlUG9zdGZpeFN1Z2dlc3RfUjFfQW5kcm9pZEJldGFfRXhwZXJpbWVudBBGGKWTygEgpZPKAUo5CitFbmFibGVQb3N0Zml4U3VnZ2VzdF9SMV9BbmRyb2lkQmV0YV9Db250cm9sEEYYppPKASCmk8oBSksKPUVuYWJsZVplcm9TdWdnZXN0X1F1ZXJpZXNBbmRVcmxzX05vU0VSUF9Nb2JpbGVfUjFfQW5kcm9pZEJldGEQRhjPksoBIM+SygFKUgpERW5hYmxlWmVyb1N1Z2dlc3RfUXVlcmllc0FuZFVybHNDb250cm9sX05vU0VSUF9Nb2JpbGVfUjFfQW5kcm9pZEJldGEQRhjQksoBINCSygFKRgo4RW5hYmxlWmVyb1N1Z2dlc3RfUXVlcmllc19Pbmx5U0VSUF9Nb2JpbGVfUjJfQW5kcm9pZEJldGEQRhjRksoBINGSygFKTQo/RW5hYmxlWmVyb1N1Z2dlc3RfUXVlcmllc0NvbnRyb2xfT25seVNFUlBfTW9iaWxlX1IyX0FuZHJvaWRCZXRhEEYY0pLKASDSksoBSkAKMkVuYWJsZVplcm9TdWdnZXN0TW9zdFZpc2l0ZWRfTW9iaWxlX1IxX0FuZHJvaWRCZXRhEEYY05LKASDTksoBSkcKOUVuYWJsZVplcm9TdWdnZXN0Q29udHJvbE1vc3RWaXNpdGVkX01vYmlsZV9SMV9BbmRyb2lkQmV0YRBGGNSSygEg1JLKAUodChlEZWZhdWx0Q29udHJvbF9Nb2JpbGVCZXRhEBRSEhIMMzMuMC4xNzUwLjczIAIoBFjd0vGqBhKyBgoaQXV0b2NvbXBsZXRlRHluYW1pY1RyaWFsXzIYgLzpowU4AUIVRGVmYXVsdENvbnRyb2xfTW9iaWxlSjwKLkVuYWJsZVplcm9TdWdnZXN0X1F1ZXJpZXNBbmRVcmxzX05vU0VSUF9Nb2JpbGUQChjLhMoBIMuEygFKQwo1RW5hYmxlWmVyb1N1Z2dlc3RfUXVlcmllc0FuZFVybHNDb250cm9sX05vU0VSUF9Nb2JpbGUQChjMhMoBIMyEygFKPgowRW5hYmxlWmVyb1N1Z2dlc3RfUXVlcmllc0FuZFVybHNfSW5jbFNFUlBfTW9iaWxlEAoYzYTKASDNhMoBSkUKN0VuYWJsZVplcm9TdWdnZXN0X1F1ZXJpZXNBbmRVcmxzQ29udHJvbF9JbmNsU0VSUF9Nb2JpbGUQChjOhMoBIM6EygFKMQojRW5hYmxlWmVyb1N1Z2dlc3RNb3N0VmlzaXRlZF9Nb2JpbGUQChjPhMoBIM+EygFKOAoqRW5hYmxlWmVyb1N1Z2dlc3RDb250cm9sTW9zdFZpc2l0ZWRfTW9iaWxlEAoY0ITKASDQhMoBSjAKFkRpc2FibGVaZXJvU3VnZ2VzdEZsYWcQACoUZGlzYWJsZS16ZXJvLXN1Z2dlc3RKXgozRW5hYmxlWmVyb1N1Z2dlc3RfUXVlcmllc0FuZFVybHNfTm9TRVJQX01vYmlsZV9GbGFnEAAYy4TKASogZW5hYmxlLXplcm8tc3VnZ2VzdC1ldGhlci1ub3NlcnBKXgo1RW5hYmxlWmVyb1N1Z2dlc3RfUXVlcmllc0FuZFVybHNfSW5jbFNFUlBfTW9iaWxlX0ZsYWcQABjNhMoBKh5lbmFibGUtemVyby1zdWdnZXN0LWV0aGVyLXNlcnBKUwooRW5hYmxlWmVyb1N1Z2dlc3RNb3N0VmlzaXRlZF9Nb2JpbGVfRmxhZxAAGM+EygEqIGVuYWJsZS16ZXJvLXN1Z2dlc3QtbW9zdC12aXNpdGVkSiMKFURlZmF1bHRDb250cm9sX01vYmlsZRAoGNGEygEg0YTKAVIMEgQzMi4qIAAgASgEWN3S8aoGEosDChpBdXRvY29tcGxldGVEeW5hbWljVHJpYWxfMhiAvOmjBTgBQhtEZWZhdWx0Q29udHJvbF9Nb2JpbGVTdGFibGVKNAomTmF2U3VnZ2VzdFVuaWZpY2F0aW9uX0ExX0FuZHJvaWRTdGFibGUQARj6k8oBIPqTygFKNAomTmF2U3VnZ2VzdFVuaWZpY2F0aW9uX0EyX0FuZHJvaWRTdGFibGUQARj7k8oBIPuTygFKNAomTmF2U3VnZ2VzdFVuaWZpY2F0aW9uX0EzX0FuZHJvaWRTdGFibGUQARj8k8oBIPyTygFKNAomTmF2U3VnZ2VzdFVuaWZpY2F0aW9uX0E0X0FuZHJvaWRTdGFibGUQARj9k8oBIP2TygFKNAomTmF2U3VnZ2VzdFVuaWZpY2F0aW9uX0E1X0FuZHJvaWRTdGFibGUQARj+k8oBIP6TygFKHwobRGVmYXVsdENvbnRyb2xfTW9iaWxlU3RhYmxlEF9SExINMzMuMC4xNzUwLjEzNiADKARYoYj0pgwSwgUKGkF1dG9jb21wbGV0ZUR5bmFtaWNUcmlhbF8yGICUn5wFOAFCEERlZmF1bHRDb250cm9sXzBKMwokRW5hYmxlWmVyb1N1Z2dlc3RfUjhfQmV0YV9FeHBlcmltZW50EOgHGMuSygEgy5LKAUowCiFQUF9FbmFibGVaZXJvU3VnZ2VzdF9SN19CZXRhX0FybUIQ6AcYhJPKASCEk8oBSjgKKVBQX0VuYWJsZUVudGl0eVN1Z2dlc3RfUjlfQmV0YV9FeHBlcmltZW50EPQDGJmTygEgmZPKAUo1CiZQUF9FbmFibGVFbnRpdHlTdWdnZXN0X1I5X0JldGFfQ29udHJvbBD0Axiak8oBIJqTygFKMwokRW5hYmxlWmVyb1N1Z2dlc3RfUjdfQmV0YV9FeHBlcmltZW50EOgHGNqEygEg2oTKAUo5CipQUF9FbmFibGVQcm9maWxlU3VnZ2VzdF9SOV9CZXRhX0V4cGVyaW1lbnQQ9AMYm5PKASCbk8oBSjYKJ1BQX0VuYWJsZVByb2ZpbGVTdWdnZXN0X1I5X0JldGFfQ29udHJvbBD0Axick8oBIJyTygFKPgovUFBfRW5hYmxlUGVyc29uYWxpemVkU3VnZ2VzdF9SOV9CZXRhX0V4cGVyaW1lbnQQ9AMYnZPKASCdk8oBSjsKLFBQX0VuYWJsZVBlcnNvbmFsaXplZFN1Z2dlc3RfUjlfQmV0YV9Db250cm9sEPQDGJ6TygEgnpPKAUowCiFFbmFibGVaZXJvU3VnZ2VzdF9SN19CZXRhX0NvbnRyb2wQ6AcY24TKASDbhMoBSjAKIUVuYWJsZVplcm9TdWdnZXN0X1I4X0JldGFfQ29udHJvbBDoBxjMksoBIMySygFKFAoQRGVmYXVsdENvbnRyb2xfMBAAUg4SBDMzLiogAigAKAEoAli8ztRAElIKGkF1dG9jb21wbGV0ZUR5bmFtaWNUcmlhbF8yGICE3I8FOAFCDkRlZmF1bHRDb250cm9sShIKDkRlZmF1bHRDb250cm9sEGRSCCAAKAAoASgCElIKGkF1dG9jb21wbGV0ZUR5bmFtaWNUcmlhbF8yGICE3I8FOAFCDkRlZmF1bHRDb250cm9sShIKDkRlZmF1bHRDb250cm9sEGRSCCABKAAoASgCEo8BChpBdXRvY29tcGxldGVEeW5hbWljVHJpYWxfMBiAw9CMBTgBQhNMaXZlU3BlbGxpbmdDb250cm9sSiAKFkxpdmVTcGVsbGluZ0V4cGVyaW1lbnQQtgcYhoTKAUocChNMaXZlU3BlbGxpbmdDb250cm9sEDIYh4TKAVIUEgQyNi4qIAAgASACIAMoACgBKAIS6wYKGkF1dG9jb21wbGV0ZUR5bmFtaWNUcmlhbF8yGICUn5wFOAFCGERlZmF1bHRDb250cm9sX1IyX1N0YWJsZUo0CiZFbmFibGVaZXJvU3VnZ2VzdF9SNV9TdGFibGVfRXhwZXJpbWVudBABGM2SygEgzZLKAUo0CiZFbmFibGVaZXJvU3VnZ2VzdF9SM19TdGFibGVfRXhwZXJpbWVudBABGNiEygEg2ITKAUo0CiZFbmFibGVaZXJvU3VnZ2VzdF9SNl9TdGFibGVfRXhwZXJpbWVudBABGNWSygEg1ZLKAUoxCiNFbmFibGVaZXJvU3VnZ2VzdF9SM19TdGFibGVfQ29udHJvbBABGNmEygEg2YTKAUoxCiNFbmFibGVaZXJvU3VnZ2VzdF9SNV9TdGFibGVfQ29udHJvbBABGM6SygEgzpLKAUoxCiNFbmFibGVaZXJvU3VnZ2VzdF9SNl9TdGFibGVfQ29udHJvbBABGNaSygEg1pLKAUouCiBQb3N0cGVyaW9kX01vc3RWaXNpdGVkX1I2X1N0YWJsZRABGKeTygEgp5PKAUoxCiNQb3N0cGVyaW9kX1Jlc3RvcmVOYXZzdWdnZXN0X1N0YWJsZRABGJeTygEgl5PKAUo1CidQb3N0cGVyaW9kX01vc3RWaXNpdGVkQ29udHJvbF9SNl9TdGFibGUQARiok8oBIKiTygFKOAoqUG9zdHBlcmlvZF9SZXN0b3JlTmF2c3VnZ2VzdENvbnRyb2xfU3RhYmxlEAEYmJPKASCYk8oBSi0KH05hdlN1Z2dlc3RVbmlmaWNhdGlvbl9BMV9TdGFibGUQARirk8oBIKuTygFKLQofTmF2U3VnZ2VzdFVuaWZpY2F0aW9uX0EyX1N0YWJsZRABGKyTygEgrJPKAUotCh9OYXZTdWdnZXN0VW5pZmljYXRpb25fQTNfU3RhYmxlEAEYrZPKASCtk8oBSi0KH05hdlN1Z2dlc3RVbmlmaWNhdGlvbl9BNF9TdGFibGUQARiuk8oBIK6TygFKLQofTmF2U3VnZ2VzdFVuaWZpY2F0aW9uX0E1X1N0YWJsZRABGP+TygEg/5PKAUocChhEZWZhdWx0Q29udHJvbF9SMl9TdGFibGUQVVIOEgQzMC4qIAMoACgBKAJYvM7UQBKGAQoVQXV0b21hdGljUHJvZmlsZVJlc2V0GMTxsJcFOAFCCERpc2FibGVkShgKFERpc2FibGVkQ29udHJvbEdyb3VwEApKCgoGRHJ5UnVuEApKCwoHRW5hYmxlZBAASgwKCERpc2FibGVkEFBSFRILMzIuMC4xNzAwLjAgAigAKAEoAljulLVREoMBChVBdXRvbWF0aWNQcm9maWxlUmVzZXQYxPGwlwU4AUIIRGlzYWJsZWRKGAoURGlzYWJsZWRDb250cm9sR3JvdXAQIUoKCgZEcnlSdW4QIUoLCgdFbmFibGVkEABKDAoIRGlzYWJsZWQQIVIXEgszMi4wLjE2ODYuMCAAIAEoACgBKAISpw8KFUF1dG9tYXRpY1Byb2ZpbGVSZXNldBjE2sCXBTgBQghEaXNhYmxlZEoYChREaXNhYmxlZENvbnRyb2xHcm91cBAASgoKBkRyeVJ1bhAASgsKB0VuYWJsZWQQAUqFDgoIRW5hYmxlZDMQATItCgloYXNoX3NlZWQSIGY1ODVlYWI4YzVjZGJiMzk1MDViYmFmZDQwMjYwNDQ3MscNCgdwcm9ncmFtErsNAEQ0MjNDNzQ4OTlCNDA4NDIxRjRFODA1OTczRTBGMjI0IUQxMjM0MkQ5RkNENjkzQUM1RjIyNUQwOEY5ODc2RTY4EENERkJGOTJCODVFRkI5QjI4NjlERkZDQUQ4Q0NFMjM0ARA5NERCRTFDQ0YxQzU2RUEzODBGN0E5MjBGNUI1NEIwMAExADdBMzdGQ0I5N0RFODZFMjFEQjNDNjExQjZBRDg5NzJDIUQxMjM0MkQ5RkNENjkzQUM1RjIyNUQwOEY5ODc2RTY4EDQ3NDk3QUMzQTFFMEUzNkJEMEY0MzU1RkFBRDU3QjQ3ARA5NERCRTFDQ0YxQzU2RUEzODBGN0E5MjBGNUI1NEIwMAExAEJCNzFDRjE5MTdEQzFCMjY4NEQ2MDlGNDI4NzNCRUJCIUQxMjM0MkQ5RkNENjkzQUM1RjIyNUQwOEY5ODc2RTY4EEY5QzcwQkQ4RkRDMTkwRjg5MkU3ODNENjcxQkYzQzJGARA5NERCRTFDQ0YxQzU2RUEzODBGN0E5MjBGNUI1NEIwMAExEkRGNDAwRUMyOTBEQ0QyNDBERTU3NkUxNzQ5MDI2MERBODE0MDgzNjdGNzcxNjBENjUyN0Q2RTA4MjlFMDc0OTExAEZDOTk2NzE4OTBFOUQ3QUE0NzJBQUVBRjhDNzQzRTREAEIzNzcxNTcwMUI2QTAwMTg2MEZGMTQ3MTQ3NkYzMjg3FURGNDAwRUMyOTBEQ0QyNDBERTU3NkUxNzQ5MDI2MERBMQA1MEIzMUUwNzZFRDZFOThGNzVERDAzMjAxMEQwQzgxQQEAQjM3NzE1NzAxQjZBMDAxODYwRkYxNDcxNDc2RjMyODckREY0MDBFQzI5MERDRDI0MERFNTc2RTE3NDkwMjYwREEQRTU2MEIzRTQ5MzY5QjkwMDY2NzZCOTFDQkM3NUFFQTYBMQAwN0U4QUY3NEZCOThGN0NGMTRDNEZBNjdFNzhDQzBCMyABEUU1NjBCM0U0OTM2OUI5MDA2Njc2QjkxQ0JDNzVBRUE2AAAQM0I1MTJFMjNBNEY2MEU3Qjk5QTk4N0Y4RTUwN0NBNjYBMQA5QkVDMjQyMUNDQzRENTFEMzU1RDY3NkJFOTE5QzdBQgBFMTIwQUNGNUVFRURBQjQwNDExQTE2RTUxNDcyRjdFMAA5NDdBQjA3MzA0NzI0QUQ0MEQ0MDA4MjJERTlGRUQ4OAEARTU4RDY1OEIwNUVGRTA1MzEwQThEM0E2NzcyRURFMzUhOUY1OTdBQTAwOEJBRDdCNThGQUNCN0Q2OEJDRTc5RDUCADBBMjQ3MjZGNzJGRkNDNzRBODc2NDkyRTA3QjlERDBDIjJCQkQ4M0E3RUZGOUNBNzQ0NEY4RkE4RTQ1QTNDNkMyIjE0RDJENTAwNDlFMUE1M0VDQzZCQzVFMzg4NTI4QTMwIjRFNEY0M0VDREQ3QkU4REE3M0JDNjU3N0FBRDhFRDY0AgA5QTdEQjI3RDQzQTYyMzA1QkIyNTEzQjcyODcxOEFCNCAAAgBBRDExRENCQkZBNDdFN0FDMkEyQzEwNkVCNDYxMTMwMwA3RDYwMzY2N0Y2RUI2QTlFQzkxMDFGOTFFRUY0M0Q4NwAwQTAxNDE2ODU2NkM4ODcxRDhGMjAyQzRBNjY3ODNEMSJGRUM1OTM0RTY0MkI3RDUxREM4MkEzM0M5M0VBRUZBNwICAhAyMTgxOUY3ODU2Q0UyQUI1REYwNzQzQkJCOUY3QTY0RgExETNCNTEyRTIzQTRGNjBFN0I5OUE5ODdGOEU1MDdDQTY2AQAQMzRCNkMyNTBDODRCRTYzMTM3REJGMTRDNkI0ODhDMTcBEEExN0QzRDMzRkY0OUE0OTBCQkI4MjUwRkFFODNBMjMwATERMjE4MTlGNzg1NkNFMkFCNURGMDc0M0JCQjlGN0E2NEYBABAzNEI2QzI1MEM4NEJFNjMxMzdEQkYxNEM2QjQ4OEMxNwEQQTE3RDNEMzNGRjQ5QTQ5MEJCQjgyNTBGQUU4M0EyMzABMRJENDIzQzc0ODk5QjQwODQyMUY0RTgwNTk3M0UwRjIyNDg5MWU0MmMyMTQ2YjM3YjNkMTY5OGM1ZDg0YzJhYmQzMRI3QTM3RkNCOTdERTg2RTIxREIzQzYxMUI2QUQ4OTcyQzg5MWU0MmMyMTQ2YjM3YjNkMTY5OGM1ZDg0YzJhYmQzMRJCQjcxQ0YxOTE3REMxQjI2ODRENjA5RjQyODczQkVCQjg5MWU0MmMyMTQ2YjM3YjNkMTY5OGM1ZDg0YzJhYmQzMUoMCghFbmFibGVkNBBiSgwKCERpc2FibGVkEABSHwiQra+XBRINMzIuMC4xNzAwLjEwMhoEMzIuKiADKABYxpKv7Q4SUQoQQnJvd3NlckJsYWNrbGlzdBjAosWdBTgBQgtOb0JsYWNrbGlzdEoLCgdFbmFibGVkEDJKDwoLTm9CbGFja2xpc3QQMlIKEgQzNC4qIAIoABJTChBCcm93c2VyQmxhY2tsaXN0GMCixZ0FOAFCC05vQmxhY2tsaXN0SgsKB0VuYWJsZWQQZEoPCgtOb0JsYWNrbGlzdBAAUgwSBDM0LiogACABKAASVAoKQ0xEMVZzQ0xEMhjE3oWUBTgBQgdEZWZhdWx0SggKBENMRDEQAUoICgRDTEQyEAFKCwoHRGVmYXVsdBBiUhQaBDMxLiogACABIAIoACgBKAMoAhKSAQoYQ2FjaGVTZW5zaXRpdml0eUFuYWx5c2lzGMT10o4FOABCAk5vSgwKCENvbnRyb2xBEAVKDAoIQ29udHJvbEIQBUoICgQxMDBBEAVKCAoEMTAwQhAFSggKBDIwMEEQBUoICgQyMDBCEAVKCAoENDAwQRAFSggKBDQwMEIQBUoGCgJObxA8UgoSBDI4LiogAigEEn8KGENhY2hlU2Vuc2l0aXZpdHlBbmFseXNpcxjEjrePBTgAQgJOb0oMCghDb250cm9sQRABSgwKCENvbnRyb2xCEAFKCAoEMTAwQRABSggKBDEwMEIQAUoICgQyMDBBEAFKCAoEMjAwQhABSgcKAk5vEOIHUgoSBDI4LiogAygEEpoBChhDYWNoZVNlbnNpdGl2aXR5QW5hbHlzaXMYxMr9igU4AEICTm9KBgoCTm8QJEoMCghDb250cm9sQRAISgwKCENvbnRyb2xCEAhKCAoEMTAwQRAISggKBDEwMEIQCEoICgQyMDBBEAhKCAoEMjAwQhAISggKBDQwMEEQCEoICgQ0MDBCEAhSEhIEMjUuKiAAIAEgAigAKAEoAhKdAQoYQ2FjaGVTZW5zaXRpdml0eUFuYWx5c2lzGMSy04oFOABCAk5vSgcKAk5vEOAHSgwKCENvbnRyb2xBEAFKDAoIQ29udHJvbEIQAUoICgQxMDBBEAFKCAoEMTAwQhABSggKBDIwMEEQAUoICgQyMDBCEAFKCAoENDAwQRABSggKBDQwMEIQAVIUCICnvooFEgQyNS4qIAMoACgBKAISsAEKEUNocm9tZVN1Z2dlc3Rpb25zOAFCB0RlZmF1bHRKFQoHQ29udHJvbBAAGMqIygEgyojKAUpcCgdFbmFibGVkEAAYy4jKASDLiMoBMhAKBXN0YXRlEgdlbmFibGVkMjMKA3VybBIsaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS9jaHJvbWVzdWdnZXN0aW9ucz90PTFKCwoHRGVmYXVsdBBkUgwSBDM0LiogACABKARgARLdAQoRQ2hyb21lU3VnZ2VzdGlvbnMYxPyunAU4AUIHRGVmYXVsdEoaCgxDb250cm9sIGJldGEQFBiziMoBILOIygFKIwoVTUwgTm8tS29kYWNocm9tZSBiZXRhEBQYtIjKASC0iMoBSiAKEk1MIEtvZGFjaHJvbWUgYmV0YRAUGMeIygEgx4jKAUolChdNTCBOZXdzLUNvbnRpZ3VvdXMgYmV0YRAAGNeIygEg14jKAUoVCgdEZWZhdWx0ECgYtYjKASC1iMoBUg4SBDMyLiogAigAKAEoA1jul+34C2ABEt0BChFDaHJvbWVTdWdnZXN0aW9ucxjE/K6cBTgBQgdEZWZhdWx0ShkKC0NvbnRyb2wgZGV2EBQYsIjKASCwiMoBSiIKFE1MIE5vLUtvZGFjaHJvbWUgZGV2EBQYsYjKASCxiMoBSh8KEU1MIEtvZGFjaHJvbWUgZGV2EBQYxojKASDGiMoBSiQKFk1MIE5ld3MtQ29udGlndW91cyBkZXYQABi9iMoBIL2IygFKFQoHRGVmYXVsdBAoGLKIygEgsojKAVISEgQzMi4qIAAgASgAKAEoAygCWO6X7fgLYAESwwEKEUNocm9tZVN1Z2dlc3Rpb25zGMSZ2JkFOAFCB0RlZmF1bHRKFQoHQ29udHJvbBAFGLaIygEgtojKAUosCh5Nb3N0IExpa2VseSB3aXRob3V0IEtvZGFjaHJvbWUQBRi3iMoBILeIygFKKQobTW9zdCBMaWtlbHkgd2l0aCBLb2RhY2hyb21lEAUYuIjKASC4iMoBShUKB0RlZmF1bHQQVRi5iMoBILmIygFSDhIEMzIuKiADKAAoASgDWOTyxPcGYAESvAEKEENvbnRleHR1YWxTZWFyY2gYxL6XpQU4AUIHRGVmYXVsdEopCgNUYXAQABjIiMoBIMiIygEyFgoPZXhwZXJpbWVudF9raW5kEgNUYXBKFQoHQ29udHJvbBAAGMmIygEgyYjKAUo4CglUYXBGb3JjZWQQACoRY29udGV4dHVhbC1zZWFyY2gyFgoPZXhwZXJpbWVudF9raW5kEgNUYXBKCwoHRGVmYXVsdBBkUg4SBDM0LiogACABKAQ4ARJ9ChxDb29raWVSZXRlbnRpb25Qcmlvcml0eVN0dWR5GMCXx5gFOAFCDEV4cGVyaW1lbnRPbkoWCg1FeHBlcmltZW50T2ZmEAAYlYrKAUoVCgxFeHBlcmltZW50T24QZBiWisoBUhgaBDMxLiogACABIAIgAygAKAEoAigDKAQSSAoPRDNEMTFFeHBlcmltZW50GIDa45EFOAFCCERpc2FibGVkSgwKCERpc2FibGVkEABKCwoHRW5hYmxlZBBkUggSBDI5LiooABJsCiNEYXRhQ29tcHJlc3Npb25Qcm94eVByZWNvbm5lY3RIaW50cxjEgPSYBTgBQghEaXNhYmxlZEoLCgdFbmFibGVkEGRKCwoHQ29udHJvbBBkSg0KCERpc2FibGVkEKAGUgoSBDMzLiogAigEEmEKI0RhdGFDb21wcmVzc2lvblByb3h5UHJlY29ubmVjdEhpbnRzGMSA9JgFOAFCCERpc2FibGVkSgwKB0VuYWJsZWQQ6AdKDAoIRGlzYWJsZWQQAFIMEgQzMy4qIAAgASgEElwKHkRhdGFDb21wcmVzc2lvblByb3h5RGV2Um9sbG91dBjE2amcBTgBQghEaXNhYmxlZEoLCgdFbmFibGVkEGRKDQoIRGlzYWJsZWQQhAdSDBIEMzQuKiAAIAEoBBJiCiNEYXRhQ29tcHJlc3Npb25Qcm94eVByb21vVmlzaWJpbGl0eRjEgpe0BTgBQgdFbmFibGVkSgwKCERpc2FibGVkEABKDAoHRW5hYmxlZBDoB1IOEgQzMS4qIAAgASACKAQSZgojRGF0YUNvbXByZXNzaW9uUHJveHlQcm9tb1Zpc2liaWxpdHkYxIKXtAU4AUIIRGlzYWJsZWRKDQoIRGlzYWJsZWQQ9ANKDAoHRW5hYmxlZBD0A1IQEgQzMS4qGgQzMS4qIAMoBBJfCiNEYXRhQ29tcHJlc3Npb25Qcm94eVByb21vVmlzaWJpbGl0eRjEgpe0BTgBQghEaXNhYmxlZEoNCghEaXNhYmxlZBCEB0oLCgdFbmFibGVkEGRSChIEMzIuKiADKAQSZQojRGF0YUNvbXByZXNzaW9uUHJveHlQcm9tb1Zpc2liaWxpdHkYxN6FlAU4AUIIRGlzYWJsZWRKDAoHRW5hYmxlZBDoB0oMCghEaXNhYmxlZBAAUhASBDMxLioaBDMxLiogAygFEmIKI0RhdGFDb21wcmVzc2lvblByb3h5UHJvbW9WaXNpYmlsaXR5GMSCl7QFOAFCB0VuYWJsZWRKDAoIRGlzYWJsZWQQAEoMCgdFbmFibGVkEOgHUg4SBDMwLiogACABIAIoBRKzAQojRGF0YUNvbXByZXNzaW9uUHJveHlQcm9tb1Zpc2liaWxpdHkYxIKXtAU4AUIIRGlzYWJsZWRKCwoHRW5hYmxlZBBkSg0KCERpc2FibGVkEIQHUl4SBDMyLiogAygFMgVlbi1VUzICZXMyAmphMgVlbi1HQjICdHIyAmZyMgJkZTIFemgtVFcyAmtvMgJpdDICYXIyAm5sMgJzdjICcGwyAmRhMgJuYjIFcHQtUFQyAmhlEpIBCiNEYXRhQ29tcHJlc3Npb25Qcm94eVByb21vVmlzaWJpbGl0eRjEgpe0BTgBQghEaXNhYmxlZEoMCgdFbmFibGVkEPQDSg0KCERpc2FibGVkEPQDUjwSBDMyLiogAygFMgJydTIFemgtQ04yAnB0MgJ2aTICdGgyBWVzLU1YMgJpZDIFcHQtQlIyA2ZpbDICaGkSWgobRGF0YUNvbXByZXNzaW9uUHJveHlSb2xsb3V0GMSCl7QFOAFCB0VuYWJsZWRKDAoIRGlzYWJsZWQQAEoMCgdFbmFibGVkEOgHUg4SBDI4LiogACABIAIoBBJdChtEYXRhQ29tcHJlc3Npb25Qcm94eVJvbGxvdXQYxIKXtAU4AUIIRGlzYWJsZWRKCwoHRW5hYmxlZBB4Sg0KCERpc2FibGVkEPAGUhASBDI4LioaBDMxLiogAygEElYKG0RhdGFDb21wcmVzc2lvblByb3h5Um9sbG91dBjEgpe0BTgBQgdFbmFibGVkSgwKCERpc2FibGVkEABKDAoHRW5hYmxlZBDoB1IKEgQzMi4qIAMoBBJdChtEYXRhQ29tcHJlc3Npb25Qcm94eVJvbGxvdXQYxLCllAU4AUIIRGlzYWJsZWRKDAoHRW5hYmxlZBDoB0oMCghEaXNhYmxlZBAAUhASBDMxLioaBDMxLiogAygFEloKG0RhdGFDb21wcmVzc2lvblByb3h5Um9sbG91dBjEgpe0BTgBQgdFbmFibGVkSgwKCERpc2FibGVkEABKDAoHRW5hYmxlZBDoB1IOEgQyOC4qIAAgASACKAUSagobRGF0YUNvbXByZXNzaW9uUHJveHlSb2xsb3V0GMSCl7QFOAFCCERpc2FibGVkSgsKB0VuYWJsZWQQeEoLCgdDb250cm9sEHhKDQoIRGlzYWJsZWQQ+AVSEBIEMjguKhoEMzEuKiADKAUSVwobRGF0YUNvbXByZXNzaW9uUHJveHlSb2xsb3V0GMSCl7QFOAFCCERpc2FibGVkSgwKB0VuYWJsZWQQ6AdKDAoIRGlzYWJsZWQQAFIKEgQzMi4qIAMoBRJXChZEYXRhQ29tcHJlc3Npb25JT1NXZWJQGMSCl7QFOAFCCERpc2FibGVkSgwKB0VuYWJsZWQQ9ANKDQoIRGlzYWJsZWQQ9ANSDhIEMzIuKiAAIAEgAigFElEKFkRhdGFDb21wcmVzc2lvbklPU1dlYlAYxIKXtAU4AUIHRW5hYmxlZEoMCghEaXNhYmxlZBAASgwKB0VuYWJsZWQQ6AdSChIEMzIuKiADKAUSXwoURGF0ZUV4dGVuc2lvbkVuYWJsZWQYxNeSlgU4AEIHRGVmYXVsdEoLCgdFbmFibGVkEDJKDAoIRGlzYWJsZWQQMkoLCgdEZWZhdWx0EABSDhIEMzIuKiAAIAEoACgEEnQKIERlZmVyQmFja2dyb3VuZEV4dGVuc2lvbkNyZWF0aW9uGMTtjqAFOAFCC1JhdGVMaW1pdGVkSgwKCERlZmVycmVkEABKDwoLUmF0ZUxpbWl0ZWQQZFIcEgQzMS4qGgQzMy4qIAAgASACIAMoACgBKAMoAhJEChFEbnNQcm9iZS1BdHRlbXB0cxiAtY2WBTgBQgdkZWZhdWx0SgsKB2RlZmF1bHQQWkoFCgExEApSChIEMjUuKiAAIAESRwoPRG5zUHJvYmUtRW5hYmxlGIC1jZYFOAFCB2Rpc2FibGVKCwoHZGlzYWJsZRAASgoKBmVuYWJsZRBkUgoSBDI1LiogACABEmMKI1VNQS1EeW5hbWljLUJpbmFyeS1Vbmlmb3JtaXR5LVRyaWFsGICckqUFOAFCB2RlZmF1bHRKEAoHZGVmYXVsdBABGKm2yQFKEQoIZ3JvdXBfMDEQARiqtskBUgYgACABIAISXwojVU1BLUR5bmFtaWMtQmluYXJ5LVVuaWZvcm1pdHktVHJpYWwYgJySpQU4AUIHZGVmYXVsdEoQCgdkZWZhdWx0EGMYqbbJAUoRCghncm91cF8wMRABGKq2yQFSAiADErMFCg5FbWJlZGRlZFNlYXJjaBjE0tmjBTgBQgxEZWZhdWx0R3JvdXBKHQoOR3JvdXAxIGJldGE6cjEQkAMYz4jKASDPiMoBSjAKIUdyb3VwMiBiZXRhOnIxIHByZWZldGNoX3Jlc3VsdHM6MRDIARjQiMoBINCIygFKUQpCR3JvdXAzIGJldGE6cjEgcHJlZmV0Y2hfcmVzdWx0czoxIHJldXNlX2luc3RhbnRfc2VhcmNoX2Jhc2VfcGFnZToxEMgBGNGIygEg0YjKAUocCg5Hcm91cDQgYmV0YTpyMhBkGNKIygEg0ojKAUo4CipHcm91cDUgYmV0YTpyMiBlc3B2OjIxMyBxdWVyeV9leHRyYWN0aW9uOjEQGRjTiMoBINOIygFKUApCR3JvdXA2IGJldGE6cjIgZXNwdjoyMTMgcXVlcnlfZXh0cmFjdGlvbjoxIGRpc3BsYXlfc2VhcmNoX2J1dHRvbjoxEBkY1IjKASDUiMoBSlAKQkdyb3VwNyBiZXRhOnIyIGVzcHY6MjEzIHF1ZXJ5X2V4dHJhY3Rpb246MSBkaXNwbGF5X3NlYXJjaF9idXR0b246MhAZGNWIygEg1YjKAUpECjZHcm91cDggYmV0YTpyMiBkaXNwbGF5X3NlYXJjaF9idXR0b246MiBoaWRlX3ZlcmJhdGltOjEQGRjWiMoBINaIygFKPwoOR3JvdXA5IGJldGE6ZjEQACorZW5hYmxlLW9yaWdpbi1jaGlwLXYyLWhpZGUtb24tbW91c2UtcmVsZWFzZUo9Cg9Hcm91cDEwIGJldGE6ZjEQACooZW5hYmxlLW9yaWdpbi1jaGlwLXYyLWhpZGUtb24tdXNlci1pbnB1dEoQCgxEZWZhdWx0R3JvdXAQAFIOEgQzMy4qIAIoACgBKANYxK+vXBLKCAoORW1iZWRkZWRTZWFyY2g4AUIMRGVmYXVsdEdyb3VwShsKDUdyb3VwMSBkZXY6cjMQDxjEiMoBIMSIygFKTwpBR3JvdXAyIGRldjpyMyBlc3B2OjIxMyBxdWVyeV9leHRyYWN0aW9uOjEgZGlzcGxheV9zZWFyY2hfYnV0dG9uOjEQDxi/iMoBIL+IygFKTwpBR3JvdXAzIGRldjpyMyBlc3B2OjIxMyBxdWVyeV9leHRyYWN0aW9uOjEgZGlzcGxheV9zZWFyY2hfYnV0dG9uOjIQDxjAiMoBIMCIygFKXwpRR3JvdXA0IGRldjpyMyBlc3B2OjIxMyBxdWVyeV9leHRyYWN0aW9uOjEgZGlzcGxheV9zZWFyY2hfYnV0dG9uOjIgaGlkZV92ZXJiYXRpbToxEA8YwYjKASDBiMoBShsKDUdyb3VwNSBkZXY6cjQQZBjMiMoBIMyIygFKLgogR3JvdXA2IGRldjpyNCBwcmVmZXRjaF9yZXN1bHRzOjEQZBjNiMoBIM2IygFKTwpBR3JvdXA3IGRldjpyNCBwcmVmZXRjaF9yZXN1bHRzOjEgcmV1c2VfaW5zdGFudF9zZWFyY2hfYmFzZV9wYWdlOjEQZBjOiMoBIM6IygFKHgoPR3JvdXAxMiBkZXY6cHA0EIwBGMWIygEgxYjKAUpPCjdHcm91cDggZGV2OmYxIGVzcHY6MjEzIHF1ZXJ5X2V4dHJhY3Rpb246MSBvcmlnaW5fY2hpcDoxEAAqEmVuYWJsZS1vcmlnaW4tY2hpcEptCkFHcm91cDkgZGV2OmYxIGVzcHY6MjEzIHF1ZXJ5X2V4dHJhY3Rpb246MSBkaXNwbGF5X3NlYXJjaF9idXR0b246MxAAKiZlbmFibGUtc2VhcmNoLWJ1dHRvbi1pbi1vbW5pYm94LWFsd2F5c0pvCkJHcm91cDEwIGRldjpmMSBlc3B2OjIxMyBxdWVyeV9leHRyYWN0aW9uOjEgZGlzcGxheV9zZWFyY2hfYnV0dG9uOjEQAConZW5hYmxlLXNlYXJjaC1idXR0b24taW4tb21uaWJveC1mb3Itc3RySnYKQkdyb3VwMTEgZGV2OmYxIGVzcHY6MjEzIHF1ZXJ5X2V4dHJhY3Rpb246MSBkaXNwbGF5X3NlYXJjaF9idXR0b246MhAAKi5lbmFibGUtc2VhcmNoLWJ1dHRvbi1pbi1vbW5pYm94LWZvci1zdHItb3ItaWlwSj8KDkdyb3VwMTIgZGV2OmYxEAAqK2VuYWJsZS1vcmlnaW4tY2hpcC12Mi1oaWRlLW9uLW1vdXNlLXJlbGVhc2VKPAoOR3JvdXAxMyBkZXY6ZjEQACooZW5hYmxlLW9yaWdpbi1jaGlwLXYyLWhpZGUtb24tdXNlci1pbnB1dEoQCgxEZWZhdWx0R3JvdXAQAFIQEgQzMy4qIAAgASgAKAEoAxLeDQoORW1iZWRkZWRTZWFyY2gYgKX5owU4AUIMRGVmYXVsdEdyb3VwSiYKGEdyb3VwMSBwY3Q6MTBhIHN0YWJsZTpyMRAKGNiIygEg2IjKAUpWCkhHcm91cDIgcGN0OjEwYiBzdGFibGU6cHAxIHVzZV9jYWNoZWFibGVfbnRwOjEgZXNwdjoyMTAgc3VwcHJlc3Nfb25fc3JwOjEQChieiMoBIJ6IygFKVgpIR3JvdXAzIHBjdDoxMGMgc3RhYmxlOnBwMSB1c2VfY2FjaGVhYmxlX250cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAoYn4jKASCfiMoBSlYKSEdyb3VwNCBwY3Q6MTBkIHN0YWJsZTpwcDEgdXNlX2NhY2hlYWJsZV9udHA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRAKGKCIygEgoIjKAUpWCkhHcm91cDUgcGN0OjEwZSBzdGFibGU6cHAxIHVzZV9jYWNoZWFibGVfbnRwOjEgZXNwdjoyMTAgc3VwcHJlc3Nfb25fc3JwOjEQChihiMoBIKGIygFKVgpIR3JvdXA2IHBjdDoxMGYgc3RhYmxlOnBwMSB1c2VfY2FjaGVhYmxlX250cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAoYoojKASCiiMoBSlYKSEdyb3VwNyBwY3Q6MTBnIHN0YWJsZTpwcDEgdXNlX2NhY2hlYWJsZV9udHA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRAKGKOIygEgo4jKAUpWCkhHcm91cDggcGN0OjEwaCBzdGFibGU6cHAxIHVzZV9jYWNoZWFibGVfbnRwOjEgZXNwdjoyMTAgc3VwcHJlc3Nfb25fc3JwOjEQChikiMoBIKSIygFKVgpIR3JvdXA5IHBjdDoxMGkgc3RhYmxlOnBwMSB1c2VfY2FjaGVhYmxlX250cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAoYpYjKASCliMoBSiYKGEdyb3VwMTAgcGN0OjFhIHN0YWJsZTpyMRABGNmIygEg2YjKAUo5CitHcm91cDExIHBjdDoxYiBzdGFibGU6cjEgcHJlZmV0Y2hfcmVzdWx0czoxEAEY2ojKASDaiMoBSlYKSEdyb3VwMTIgcGN0OjFjIHN0YWJsZTpwcDEgdXNlX2NhY2hlYWJsZV9udHA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRABGKiIygEgqIjKAUpWCkhHcm91cDEzIHBjdDoxZCBzdGFibGU6cHAxIHVzZV9jYWNoZWFibGVfbnRwOjEgZXNwdjoyMTAgc3VwcHJlc3Nfb25fc3JwOjEQARipiMoBIKmIygFKVgpIR3JvdXAxNCBwY3Q6MWUgc3RhYmxlOnBwMSB1c2VfY2FjaGVhYmxlX250cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAEYqojKASCqiMoBSlYKSEdyb3VwMTUgcGN0OjFmIHN0YWJsZTpwcDEgdXNlX2NhY2hlYWJsZV9udHA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRABGKuIygEgq4jKAUpWCkhHcm91cDE2IHBjdDoxZyBzdGFibGU6cHAxIHVzZV9jYWNoZWFibGVfbnRwOjEgZXNwdjoyMTAgc3VwcHJlc3Nfb25fc3JwOjEQARisiMoBIKyIygFKVgpIR3JvdXAxNyBwY3Q6MWggc3RhYmxlOnBwMSB1c2VfY2FjaGVhYmxlX250cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAEYrYjKASCtiMoBSlYKSEdyb3VwMTggcGN0OjFpIHN0YWJsZTpwcDEgdXNlX2NhY2hlYWJsZV9udHA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRABGK6IygEgrojKAUpWCkhHcm91cDE5IHBjdDoxaiBzdGFibGU6cHAxIHVzZV9jYWNoZWFibGVfbnRwOjEgZXNwdjoyMTAgc3VwcHJlc3Nfb25fc3JwOjEQARiviMoBIK+IygFKQgoRR3JvdXAyMCBzdGFibGU6ZjEQACorZW5hYmxlLW9yaWdpbi1jaGlwLXYyLWhpZGUtb24tbW91c2UtcmVsZWFzZUo/ChFHcm91cDIxIHN0YWJsZTpmMRAAKihlbmFibGUtb3JpZ2luLWNoaXAtdjItaGlkZS1vbi11c2VyLWlucHV0ShAKDERlZmF1bHRHcm91cBAAUg4SBDMzLiogAygAKAMoAVivusqaARJOChZFbmZvcmNlU2lnbmluVG9Vc2VBcHBzGMSlnLQFOAFCB0VuYWJsZWRKDAoIRGlzYWJsZWQQAEoLCgdFbmFibGVkEGRSCCAAIAEoACgBEuUDChFFbmhhbmNlZEJvb2ttYXJrcxiA2qmcBTgBQgdEZWZhdWx0SpMBCg9FeHRlbnNpb24gKG9sZCkQADIZChRlbmFibGUtZG9tLWRpc3RpbGxlchIBMTIgChtlbmFibGUtb3ZlcnJpZGUtYm9va21hcmstdWkSATEyGQoUZW5hYmxlLXN5bmMtYXJ0aWNsZXMSATEyJgoCaWQSIGdtbGxsYmdobmZrcGZsZW1paGxqZWtiYXBqb3BmamlrSpMBCg9FeHRlbnNpb24gKG5ldykQADIZChRlbmFibGUtZG9tLWRpc3RpbGxlchIBMTIgChtlbmFibGUtb3ZlcnJpZGUtYm9va21hcmstdWkSATEyGQoUZW5hYmxlLXN5bmMtYXJ0aWNsZXMSATEyJgoCaWQSIG1lb2Vlb2FvaGJtZ2JvY3BkcG5qa2xtZm1qamFna2tmSi0KDk1hbnVhbCBpbnN0YWxsEAAqGW1hbnVhbC1lbmhhbmNlZC1ib29rbWFya3NKQwodTWFudWFsIGluc3RhbGwgKHVzZXIgb3B0LW91dCkQACogbWFudWFsLWVuaGFuY2VkLWJvb2ttYXJrcy1vcHRvdXRKCwoHRGVmYXVsdBBkUhISBDMzLiogASAAKAAoAygBKAISpgIKGUV4dGVuc2lvblBlcm1pc3Npb25EaWFsb2cYwMqPpQU4AUIHR3JvdXBfMEoQCgdHcm91cF8xEAEYtJPKAUoQCgdHcm91cF8yEAEYtZPKAUoQCgdHcm91cF8zEAEYtpPKAUoQCgdHcm91cF80EAEYt5PKAUoQCgdHcm91cF81EAEYuJPKAUoQCgdHcm91cF82EAEYuZPKAUoQCgdHcm91cF83EAEYupPKAUoQCgdHcm91cF84EAEYu5PKAUoQCgdHcm91cF85EAEYvJPKAUoRCghHcm91cF8xMBABGL2TygFKEQoIR3JvdXBfMTEQARi+k8oBShEKCEdyb3VwXzEyEAEYv5PKAUoRCgdHcm91cF8wELwBGLOTygFSChIEMzQuKiADKAASpwIKGUV4dGVuc2lvblBlcm1pc3Npb25EaWFsb2cYwMqPpQU4AUIHR3JvdXBfMEoQCgdHcm91cF8xEA8YtJPKAUoQCgdHcm91cF8yEA8YtZPKAUoQCgdHcm91cF8zEA8YtpPKAUoQCgdHcm91cF80EA8Yt5PKAUoQCgdHcm91cF81EA8YuJPKAUoQCgdHcm91cF82EA8YuZPKAUoQCgdHcm91cF83EA8YupPKAUoQCgdHcm91cF84EA8Yu5PKAUoQCgdHcm91cF85EA8YvJPKAUoRCghHcm91cF8xMBAPGL2TygFKEQoIR3JvdXBfMTEQDxi+k8oBShEKCEdyb3VwXzEyEA8Yv5PKAUoQCgdHcm91cF8wEBQYs5PKAVIMEgQzNC4qIAAgASgAEkwKHEV4dGVuc2lvbkluc3RhbGxWZXJpZmljYXRpb24YxIH0owU4AUIETm9uZUoICgROb25lEGRSFBIEMzMuKiAAIAEgAiADKAEoAygCEnMKHEV4dGVuc2lvbkluc3RhbGxWZXJpZmljYXRpb24YxIH0owU4AUIETm9uZUoLCgdFbmZvcmNlEGRKDQoJQm9vdHN0cmFwEABKCwoHQ29udHJvbBAASggKBE5vbmUQAFISEgwzMy4wLjE3NTAuOTIgAigAEoEBChxFeHRlbnNpb25JbnN0YWxsVmVyaWZpY2F0aW9uGMSB9KMFOAFCBE5vbmVKCwoHRW5mb3JjZRAoSg0KCUJvb3RzdHJhcBAKSgsKB0NvbnRyb2wQKEoICgROb25lEApSIBIMMzMuMC4xNzUwLjE4GgwzMy4wLjE3NTAuOTEgAigAEnUKHEV4dGVuc2lvbkluc3RhbGxWZXJpZmljYXRpb24YxIH0owU4AUIETm9uZUoLCgdFbmZvcmNlEABKDQoJQm9vdHN0cmFwEDxKCwoHQ29udHJvbBAASggKBE5vbmUQKFIUEgwzMy4wLjE3NTAuMTggACABKAAScwocRXh0ZW5zaW9uSW5zdGFsbFZlcmlmaWNhdGlvbhjEgfSjBTgBQgROb25lSgsKB0VuZm9yY2UQAEoNCglCb290c3RyYXAQAUoLCgdDb250cm9sEAFKCAoETm9uZRBiUhISDDMzLjAuMTc1MC4xOCADKAASXAoYRmxhc2hIYXJkd2FyZVZpZGVvRGVjb2RlGMS33pwFOAFCCERpc2FibGVkSgwKB0h3VmlkZW8Q9ANKDAoHQ29udHJvbBD0A0oMCghEaXNhYmxlZBAAUgQgAigAEl4KGEZsYXNoSGFyZHdhcmVWaWRlb0RlY29kZRjEt96cBTgBQghEaXNhYmxlZEoMCgdId1ZpZGVvEPQDSgwKB0NvbnRyb2wQ9ANKDAoIRGlzYWJsZWQQAFIGIAAgASgAElcKFEZvcmNlQ29tcG9zaXRpbmdNb2RlGIC3pJwFOAFCB2Rpc2FibGVKCwoHZGlzYWJsZRBkSgsKB2VuYWJsZWQQAEoKCgZ0aHJlYWQQAFIIEgQyNC4qKAISVgoURm9yY2VDb21wb3NpdGluZ01vZGUYgLeknAU4AUIGdGhyZWFkSgsKB2Rpc2FibGUQAEoLCgdlbmFibGVkEABKCgoGdGhyZWFkEGRSCBIEMjguKigBElYKFEZvcmNlQ29tcG9zaXRpbmdNb2RlGIC3pJwFOAFCBnRocmVhZEoLCgdkaXNhYmxlEABKCwoHZW5hYmxlZBAASgoKBnRocmVhZBBkUggSBDI0LiooABI5Cg5Gb3JtRmFjdG9yVGVzdBjE1Z6VBTgBQgVQaG9uZUoKCgVQaG9uZRDoB1IKIAAgASgFKAQ4AWABEjsKDkZvcm1GYWN0b3JUZXN0GMTVnpUFOAFCBlRhYmxldEoLCgZUYWJsZXQQ6AdSCiAAIAEoBSgEOAJgARL+AQoJR29vZ2xlTm93OAFCB0RlZmF1bHRKCgoGRW5hYmxlEEZKNAoSRW5hYmxlTm9CYWNrZ3JvdW5kEA8yHAoTY2FuRW5hYmxlQmFja2dyb3VuZBIFZmFsc2VKCwoHQ29udHJvbBAPSgsKB0RlZmF1bHQQAEozCg9EaXNhYmxlZFZpYUZsYWcQACoeZGlzYWJsZS1nb29nbGUtbm93LWludGVncmF0aW9uSjEKDkVuYWJsZWRWaWFGbGFnEAAqHWVuYWJsZS1nb29nbGUtbm93LWludGVncmF0aW9uUhwSCzMzLjAuMTc1MC4qIAIoACgBKAMyBWVuLVVTWPPS4dQHEogCCglHb29nbGVOb3cYxJuSpQU4AUIHRGVmYXVsdEoKCgZFbmFibGUQZEo0ChJFbmFibGVOb0JhY2tncm91bmQQADIcChNjYW5FbmFibGVCYWNrZ3JvdW5kEgVmYWxzZUoLCgdDb250cm9sEABKCwoHRGVmYXVsdBAASjMKD0Rpc2FibGVkVmlhRmxhZxAAKh5kaXNhYmxlLWdvb2dsZS1ub3ctaW50ZWdyYXRpb25KMQoORW5hYmxlZFZpYUZsYWcQACodZW5hYmxlLWdvb2dsZS1ub3ctaW50ZWdyYXRpb25SIBILMzQuMC4xNzk3LiogACABKAAoASgDKAIyBWVuLVVTWPPS4dQHEqsCCglHb29nbGVOb3cYxJuSpQU4AUIHRGVmYXVsdEoKCgZFbmFibGUQGUo0ChJFbmFibGVOb0JhY2tncm91bmQQADIcChNjYW5FbmFibGVCYWNrZ3JvdW5kEgVmYWxzZUoLCgdDb250cm9sEABKCwoHRGVmYXVsdBBLSjMKD0Rpc2FibGVkVmlhRmxhZxAAKh5kaXNhYmxlLWdvb2dsZS1ub3ctaW50ZWdyYXRpb25KMQoORW5hYmxlZFZpYUZsYWcQACodZW5hYmxlLWdvb2dsZS1ub3ctaW50ZWdyYXRpb25SQxIEMzQuKiAAIAEgAigAKAEoAzIFZW4tR0IyAmZyMgJpdDICZGUyAmVzMgV6aC1DTjIFemgtVFcyAmphMgJrbzICcnVY89Lh1AcSiwEKDUhvc3RDYWNoZVNpemUYxM2IhwU4AEIHRGVmYXVsdEoLCgdEZWZhdWx0EABKCAoEMTAwQRAKSggKBDEwMEIQCkoICgQzMDBBEApKCAoEMzAwQhAKSgkKBTEwMDBBEApKCQoFMTAwMEIQCkoJCgUzMDAwQRAKSgkKBTMwMDBCEApSCBIEMjUuKigDEpMBCg1Ib3N0Q2FjaGVTaXplGMSs94cFOABCB0RlZmF1bHRKCwoHRGVmYXVsdBAASggKBDEwMEEQCkoICgQxMDBCEApKCAoEMzAwQRAKSggKBDMwMEIQCkoJCgUxMDAwQRAKSgkKBTEwMDBCEApKCQoFMzAwMEEQCkoJCgUzMDAwQhAKUhASBDI1LiogACABKAAoASgCEpECCg5JT1NQaG9uZU5ld05UUBjEtoGXBTgBQghEaXNhYmxlZEoTCglFbmFibGVkVjEQ6AcYr5PKAUoSCglDb250cm9sVjEQABiwk8oBShIKCUVuYWJsZWRWMhAAGLGTygFKEgoJQ29udHJvbFYyEAAYspPKAUorChBGb3JjZWRfRW5hYmxlZFYxEAAqFWVuYWJsZS1pb3MtbmV3LW50cC12MUorChBGb3JjZWRfRW5hYmxlZFYyEAAqFWVuYWJsZS1pb3MtbmV3LW50cC12MkooCg9Gb3JjZWRfRGlzYWJsZWQQACoTZGlzYWJsZS1pb3MtbmV3LW50cEoMCghEaXNhYmxlZBAAUgoSBDMyLiogAygFEpkCCg5JT1NQaG9uZU5ld05UUBjEm5KlBTgBQghEaXNhYmxlZEoTCglFbmFibGVkVjEQyAEYr5PKAUoTCglDb250cm9sVjEQyAEYsJPKAUoTCglFbmFibGVkVjIQyAEYsZPKAUoTCglDb250cm9sVjIQyAEYspPKAUorChBGb3JjZWRfRW5hYmxlZFYxEAAqFWVuYWJsZS1pb3MtbmV3LW50cC12MUorChBGb3JjZWRfRW5hYmxlZFYyEAAqFWVuYWJsZS1pb3MtbmV3LW50cC12MkooCg9Gb3JjZWRfRGlzYWJsZWQQACoTZGlzYWJsZS1pb3MtbmV3LW50cEoNCghEaXNhYmxlZBDIAVIOEgQzMi4qIAAgASACKAUSoQIKDklPU1Bob25lTmV3TlRQGMSbkqUFOAFCCERpc2FibGVkShcKDkVuYWJsZWRPbW5pYm94EDIY8pPKAUoXCg5Db250cm9sT21uaWJveBAyGPOTygFKEgoJRW5hYmxlZFYyEDIY9JPKAUoSCglDb250cm9sVjIQMhj1k8oBSisKEEZvcmNlZF9FbmFibGVkVjEQACoVZW5hYmxlLWlvcy1uZXctbnRwLXYxSisKEEZvcmNlZF9FbmFibGVkVjIQACoVZW5hYmxlLWlvcy1uZXctbnRwLXYySigKD0ZvcmNlZF9EaXNhYmxlZBAAKhNkaXNhYmxlLWlvcy1uZXctbnRwSg0KCERpc2FibGVkEKAGUgoSBDMzLiogAygFWJ7vtMULEnQKGklPU1N0b3BMb2FkT25BcHBCYWNrZ3JvdW5kGMSC6JkFOAFCB0RlZmF1bHRKGgoWRW5hYmxlZFJlbG9hZEZyb250bW9zdBAySgwKCERpc2FibGVkEDJKCwoHRGVmYXVsdBAAUg4SBDMyLiogACABIAIoBRJwChpJT1NTdG9wTG9hZE9uQXBwQmFja2dyb3VuZBjEguiZBTgBQgdEZWZhdWx0ShoKFkVuYWJsZWRSZWxvYWRGcm9udG1vc3QQBUoMCghEaXNhYmxlZBAFSgsKB0RlZmF1bHQQWlIKEgQzMi4qIAMoBRKaAgoPSU9TVGFibGV0TmV3TlRQGMSbkqUFOAFCCERpc2FibGVkShMKCUVuYWJsZWRWMRDIARjok8oBShMKCUNvbnRyb2xWMRDIARjpk8oBShMKCUVuYWJsZWRWMhDIARjqk8oBShMKCUNvbnRyb2xWMhDIARjrk8oBSisKEEZvcmNlZF9FbmFibGVkVjEQACoVZW5hYmxlLWlvcy1uZXctbnRwLXYxSisKEEZvcmNlZF9FbmFibGVkVjIQACoVZW5hYmxlLWlvcy1uZXctbnRwLXYySigKD0ZvcmNlZF9EaXNhYmxlZBAAKhNkaXNhYmxlLWlvcy1uZXctbnRwSg0KCERpc2FibGVkEMgBUg4SBDMzLiogACABIAIoBRKcAgoPSU9TVGFibGV0TmV3TlRQGMSbkqUFOAFCCERpc2FibGVkShcKDkVuYWJsZWRPbW5pYm94EDIY7JPKAUoXCg5Db250cm9sT21uaWJveBAyGO2TygFKEgoJRW5hYmxlZFYyEDIY7pPKAUoSCglDb250cm9sVjIQMhjvk8oBSisKEEZvcmNlZF9FbmFibGVkVjEQACoVZW5hYmxlLWlvcy1uZXctbnRwLXYxSisKEEZvcmNlZF9FbmFibGVkVjIQACoVZW5hYmxlLWlvcy1uZXctbnRwLXYySigKD0ZvcmNlZF9EaXNhYmxlZBAAKhNkaXNhYmxlLWlvcy1uZXctbnRwSg0KCERpc2FibGVkEKAGUgoSBDMzLiogAygFEpMBCh1JT1NVUkxDb250aW51b3VzQ2hlY2tJbnRlcnZhbBjEj7eaBTgAQgdEZWZhdWx0ShcKE0NoZWNrSW50ZXJ2YWxNUzYwMDAQCkoXChNDaGVja0ludGVydmFsTVM5MDAwEApKGAoUQ2hlY2tJbnRlcnZhbERlZmF1bHQQCkoLCgdEZWZhdWx0EEZSCCAAIAEgAigFEpYBCh1JT1NVUkxDb250aW51b3VzQ2hlY2tJbnRlcnZhbBjEj7eaBTgAQgdEZWZhdWx0ShcKE0NoZWNrSW50ZXJ2YWxNUzYwMDAQAUoXChNDaGVja0ludGVydmFsTVM5MDAwEAFKGAoUQ2hlY2tJbnRlcnZhbERlZmF1bHQQAUoMCgdEZWZhdWx0EOUHUgoSBDMzLiogAygFEkQKDUluZmluaXRlQ2FjaGUYxLSNlgU4AUICTm9KBwoCTm8Q1AdKBwoDWWVzEApKCwoHQ29udHJvbBAKUgggAigAKAEoAhJGCg1JbmZpbml0ZUNhY2hlGMS0jZYFOAFCAk5vSgcKAk5vEIQHSgcKA1llcxAySgsKB0NvbnRyb2wQMlIKIAAgASgAKAEoAhJECg1JbmZpbml0ZUNhY2hlGMS0jZYFOAFCAk5vSgcKAk5vEOYHSgcKA1llcxABSgsKB0NvbnRyb2wQAVIIIAMoACgBKAIS3wQKDEluc3RhbnREdW1teRiAhNyPBTgBQgxEZWZhdWx0R3JvdXBKLwohRHVtbXlHcm91cDEgY2hhbm5lbDpzdGFibGUgbW9kczoxEAEYx4XKASDHhcoBSi8KIUR1bW15R3JvdXAyIGNoYW5uZWw6c3RhYmxlIG1vZHM6MRABGMiFygEgyIXKAUovCiFEdW1teUdyb3VwMyBjaGFubmVsOnN0YWJsZSBtb2RzOjEQARjJhcoBIMmFygFKLwohRHVtbXlHcm91cDQgY2hhbm5lbDpzdGFibGUgbW9kczoxEAEYyoXKASDKhcoBSi8KIUR1bW15R3JvdXA1IGNoYW5uZWw6c3RhYmxlIG1vZHM6ORAJGMuFygEgy4XKAUovCiFEdW1teUdyb3VwNiBjaGFubmVsOnN0YWJsZSBtb2RzOjkQCRjMhcoBIMyFygFKLwohRHVtbXlHcm91cDcgY2hhbm5lbDpzdGFibGUgbW9kczo5EAkYzYXKASDNhcoBSi8KIUR1bW15R3JvdXA4IGNoYW5uZWw6c3RhYmxlIG1vZHM6ORAJGM6FygEgzoXKAUoxCiNEdW1teUdyb3VwOSBjaGFubmVsOnN0YWJsZSBtb2RzOjEwMBBkGM+FygEgz4XKAUoyCiREdW1teUdyb3VwMTAgY2hhbm5lbDpzdGFibGUgbW9kczoxMDAQZBjQhcoBINCFygFKKgobRHVtbXlQYWRkaW5nIGNoYW5uZWw6c3RhYmxlEPgFGNGFygEg0YXKAUoQCgxEZWZhdWx0R3JvdXAQAFIMEgQyNy4qIAMoACgBEp8OCg9JbnN0YW50RXh0ZW5kZWQYgPfSmQU4AUIMRGVmYXVsdEdyb3VwSlwKTkdyb3VwMjAgcGN0OjUgc3RhYmxlOnI0IHVzZV9yZW1vdGVfbnRwX29uX3N0YXJ0dXA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRAFGISIygEghIjKAUpcCk5Hcm91cDEgcGN0OjI1IHN0YWJsZTpyNCB1c2VfcmVtb3RlX250cF9vbl9zdGFydHVwOjEgZXNwdjoyMTAgc3VwcHJlc3Nfb25fc3JwOjEQGRiFiMoBIIWIygFKXQpPR3JvdXAyIHBjdDoxMGEgc3RhYmxlOnI1IHVzZV9yZW1vdGVfbnRwX29uX3N0YXJ0dXA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRAKGIaIygEghojKAUpdCk9Hcm91cDMgcGN0OjEwYiBzdGFibGU6cjcgdXNlX3JlbW90ZV9udHBfb25fc3RhcnR1cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAoYkojKASCSiMoBSnEKY0dyb3VwNCBwY3Q6MTBjIHN0YWJsZTpyNyB1c2VfY2FjaGVhYmxlX250cDoxIHVzZV9yZW1vdGVfbnRwX29uX3N0YXJ0dXA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRAKGJOIygEgk4jKAUpeClBHcm91cDUgcGN0OjEwZCBzdGFibGU6cHAzIHVzZV9yZW1vdGVfbnRwX29uX3N0YXJ0dXA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRAKGJSIygEglIjKAUpeClBHcm91cDYgcGN0OjEwZSBzdGFibGU6cHAxIHVzZV9yZW1vdGVfbnRwX29uX3N0YXJ0dXA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRAKGNyHygEg3IfKAUpeClBHcm91cDcgcGN0OjEwZiBzdGFibGU6cHAxIHVzZV9yZW1vdGVfbnRwX29uX3N0YXJ0dXA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRAKGN2HygEg3YfKAUpwCmJHcm91cDggcGN0OjFhIHN0YWJsZTpyNSB1c2VfY2FjaGVhYmxlX250cDoxIHVzZV9yZW1vdGVfbnRwX29uX3N0YXJ0dXA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRABGIeIygEgh4jKAUpdCk9Hcm91cDkgcGN0OjFiIHN0YWJsZTpwcDIgdXNlX3JlbW90ZV9udHBfb25fc3RhcnR1cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAEYgojKASCCiMoBSl4KUEdyb3VwMTAgcGN0OjFjIHN0YWJsZTpwcDIgdXNlX3JlbW90ZV9udHBfb25fc3RhcnR1cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAEYg4jKASCDiMoBSl4KUEdyb3VwMTEgcGN0OjFkIHN0YWJsZTpwcDMgdXNlX3JlbW90ZV9udHBfb25fc3RhcnR1cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAEYlYjKASCViMoBSl4KUEdyb3VwMTIgcGN0OjFlIHN0YWJsZTpwcDEgdXNlX3JlbW90ZV9udHBfb25fc3RhcnR1cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAEY4ofKASDih8oBSl4KUEdyb3VwMTMgcGN0OjFmIHN0YWJsZTpwcDEgdXNlX3JlbW90ZV9udHBfb25fc3RhcnR1cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAEY44fKASDjh8oBSl4KUEdyb3VwMTQgcGN0OjFnIHN0YWJsZTpwcDEgdXNlX3JlbW90ZV9udHBfb25fc3RhcnR1cDoxIGVzcHY6MjEwIHN1cHByZXNzX29uX3NycDoxEAEY5IfKASDkh8oBSl0KT0dyb3VwMTUgcGN0OjFoIHN0YWJsZTpyMyB1c2VfcmVtb3RlX250cF9vbl9zdGFydHVwOjEgZXNwdjoyMTAgc3VwcHJlc3Nfb25fc3JwOjEQARiAiMoBIICIygFKSwo9R3JvdXAxNiBwY3Q6MWkgc3RhYmxlOnIzIHVzZV9yZW1vdGVfbnRwX29uX3N0YXJ0dXA6MSBlc3B2OjIxNRABGIGIygEggYjKAUpeClBHcm91cDE3IHBjdDoxaiBzdGFibGU6cHAxIHVzZV9yZW1vdGVfbnRwX29uX3N0YXJ0dXA6MSBlc3B2OjIxMCBzdXBwcmVzc19vbl9zcnA6MRABGOeHygEg54fKAUoQCgxEZWZhdWx0R3JvdXAQAFIUEgQzMC4qGgQzMS4qIAMoACgDKAFYvt/3rAUSlAIKFkludGVyc3RpdGlhbE1hbHdhcmUzMTAYwJekkwU4AUIHRGVmYXVsdEoXChNjb25kMU1hbHdhcmVDb250cm9sEANKFwoTY29uZDJNYWx3YXJlTm9CcmFuZBADShcKE2NvbmQ1TWFsd2FyZU9uZVN0ZXAQA0oXChNjb25kN01hbHdhcmVGZWFyTXNnEANKGQoVY29uZDlNYWx3YXJlQ29sbGFiTXNnEANKGQoVY29uZDExTWFsd2FyZVF1ZXN0aW9uEANKFwoTY29uZDEzTWFsd2FyZUdvQmFjaxADSgwKB0RlZmF1bHQQswFSKAjA2aOMBRIEMjkuKhoEMzIuKigAKAEoAigDMgVlbi1VUzIFZW4tR0ISnQIKF0ludGVyc3RpdGlhbFBoaXNoaW5nNTY0GMCXpJMFOAFCB0RlZmF1bHRKGAoUY29uZDNQaGlzaGluZ0NvbnRyb2wQA0oYChRjb25kNFBoaXNoaW5nTm9CcmFuZBADShgKFGNvbmQ2UGhpc2hpbmdPbmVTdGVwEANKGAoUY29uZDhQaGlzaGluZ0ZlYXJNc2cQA0obChdjb25kMTBQaGlzaGluZ0NvbGxhYk1zZxADShoKFmNvbmQxMlBoaXNoaW5nUXVlc3Rpb24QA0oYChRjb25kMTRQaGlzaGluZ0dvQmFjaxADSgwKB0RlZmF1bHQQswFSKAjA2aOMBRIEMjkuKhoEMzIuKigAKAEoAigDMgVlbi1VUzIFZW4tR0ISqgIKEkludGVyc3RpdGlhbFNTTDUxNxjAnpeRBTgBQgdEZWZhdWx0ShkKFUNvbmRpdGlvbjE1U1NMQ29udHJvbBADShkKFUNvbmRpdGlvbjE2U1NMRmlyZWZveBADSh4KGkNvbmRpdGlvbjE3U1NMRmFuY3lGaXJlZm94EANKGgoWQ29uZGl0aW9uMThTU0xOb0ltYWdlcxADShsKF0NvbmRpdGlvbjE5U1NMUG9saWNlbWFuEANKGwoXQ29uZGl0aW9uMjBTU0xTdG9wbGlnaHQQA0oYChRDb25kaXRpb24yMVNTTEJhZGd1eRADSgwKB0RlZmF1bHQQswFSLwjA2aOMBRILMjkuMC4xNTQzLioaBDMwLiooACgBKAIoAzIFZW4tVVMyBWVuLUdCElgKGUxhdW5jaGVyVXNlV2Vic3RvcmVTZWFyY2gYxJuSpQU4AUIGRW5hYmxlSgoKBkVuYWJsZRBkSgsKB0Rpc2FibGUQAFISEgwyOS4wLjE1NDcuMzIoACgDEtgCChZNYWx3YXJlRG93bmxvYWRXYXJuaW5nGMToppMFOAFCB0RlZmF1bHRKFQoRQ29uZGl0aW9uMUNvbnRyb2wQAUoVChFDb25kaXRpb24yQ29udHJvbBABShcKE0NvbmRpdGlvbjNNYWxpY2lvdXMQAUoUChBDb25kaXRpb240VW5zYWZlEAFKFwoTQ29uZGl0aW9uNURhbmdlcm91cxABShUKEUNvbmRpdGlvbjZIYXJtZnVsEAFKGwoXQ29uZGl0aW9uN0Rpc2NhcmRTZWNvbmQQAUoaChZDb25kaXRpb244RGlzY2FyZEZpcnN0EAFKGQoVQ29uZGl0aW9uOVNhZmVEaXNjYXJkEAFKGgoWQ29uZGl0aW9uMTBTYWZlRG9udFJ1bhABSgsKB0RlZmF1bHQQWlIjCMSSn5EFEgQzMC4qGgQzMi4qIAAgASACIAMoADIFZW4tVVMSUQoRTWFuYWdlZE1vZGVMYXVuY2gYgNqpnAU4AUIISW5hY3RpdmVKCgoGQWN0aXZlEGRKDAoISW5hY3RpdmUQAFIQEgQzMC4qIAAgASgAKAEoAhJNChFNYW5hZ2VkTW9kZUxhdW5jaBiA2qmcBTgBQghJbmFjdGl2ZUoKCgZBY3RpdmUQZEoMCghJbmFjdGl2ZRAAUgwSBDMwLiogACABKAMSUQoRTWFuYWdlZE1vZGVMYXVuY2gYgNqpnAU4AUIISW5hY3RpdmVKCgoGQWN0aXZlEGRKDAoISW5hY3RpdmUQAFIQEgQzMS4qIAIoACgBKAIoAxJWChFNYW5hZ2VkTW9kZUxhdW5jaBiA7o6gBTgBQghJbmFjdGl2ZUoKCgZBY3RpdmUQZEoMCghJbmFjdGl2ZRAAUhASBDMyLiogAygAKAEoAigDWKarvBgSWwofTWVkaWFTdHJlYW1BdWRpb1RyYWNrUHJvY2Vzc2luZxjE88edBTgBQgdFbmFibGVkSgwKCERpc2FibGVkEABKCwoHRW5hYmxlZBBkUgwgACgAKAEoAygCKAQSkgIKGE1vc3RWaXNpdGVkVGlsZVBsYWNlbWVudBiA/qKSBTgBQgdEZWZhdWx0ShYKEk9uZUVpZ2h0X0FfRmxpcHBlZBADSg4KCk9uZUVpZ2h0X0IQA0oWChJPbmVFaWdodF9DX0ZsaXBwZWQQA0oOCgpPbmVFaWdodF9EEANKFQoRT25lRm91cl9BX0ZsaXBwZWQQA0oNCglPbmVGb3VyX0IQA0oVChFPbmVGb3VyX0NfRmxpcHBlZBADSg0KCU9uZUZvdXJfRBADSgsKB0NvbnRyb2wQA0oUChBEb250U2hvd09wZW5UYWJzEANKCwoHRGVmYXVsdBBGUhkSCzI4LjAuMTQ5Ni4wIAEgACgAKAEoAigDEpIBChRNb3VzZUV2ZW50UHJlY29ubmVjdBjEtI2WBTgAQghEaXNhYmxlZEoNCglNb3VzZURvd24QHkoNCglNb3VzZU92ZXIQAEoSCg5UYXBVbmNvbmZpcm1lZBAASgsKB1RhcERvd24QAEoLCgdDb250cm9sEB5KDAoIRGlzYWJsZWQQKFIOEgQyOS4qIAIoACgBKAISmAEKFE1vdXNlRXZlbnRQcmVjb25uZWN0GMS0jZYFOABCCERpc2FibGVkSg0KCU1vdXNlRG93bhAKSg0KCU1vdXNlT3ZlchAAShIKDlRhcFVuY29uZmlybWVkEABKCwoHVGFwRG93bhAASgsKB0NvbnRyb2wQCkoMCghEaXNhYmxlZBBQUhQSBDI5LiogACABKAAoASgCKAMoBBK2AQoPTW9iaWxlTmF0aXZlTlRQGIDE0JcFOAFCEUxlZ2FjeU5UUENvbnRyb2wwShoKEUxlZ2FjeU5UUENvbnRyb2wxEBkYspLKAUogChdOYXRpdmVOVFBXaXRoVHdvVXJsQmFycxAZGLOSygFKIgoZTmF0aXZlTlRQV2l0aFNpbmdsZVVybEJhchAZGLSSygFKGgoRTGVnYWN5TlRQQ29udHJvbDAQGRixksoBUgoSBDMxLiogAigEEqQBCg9Nb2JpbGVOYXRpdmVOVFAYgMTQlwU4AUIRTGVnYWN5TlRQQ29udHJvbDBKFQoRTGVnYWN5TlRQQ29udHJvbDEQBUobChdOYXRpdmVOVFBXaXRoVHdvVXJsQmFycxAtSh0KGU5hdGl2ZU5UUFdpdGhTaW5nbGVVcmxCYXIQLUoVChFMZWdhY3lOVFBDb250cm9sMBAFUgwSBDMxLiogACABKAQStwEKD01vYmlsZU5hdGl2ZU5UUBiAl6qYBTgBQhFMZWdhY3lOVFBDb250cm9sMEobChFMZWdhY3lOVFBDb250cm9sMRDhBhi4ksoBSiAKF05hdGl2ZU5UUFdpdGhUd29VcmxCYXJzEC0YuZLKAUoiChlOYXRpdmVOVFBXaXRoU2luZ2xlVXJsQmFyEC0YupLKAUoaChFMZWdhY3lOVFBDb250cm9sMBAtGLeSygFSChIEMzIuKiADKAQSfAoVTW9iaWxlVGFibGV0TmF0aXZlTlRQGIDE0JcFOAFCEExlZ2FjeU5UUENvbnRyb2xKIgoZTmF0aXZlTlRQV2l0aFNpbmdsZVVybEJhchAyGLaSygFKGQoQTGVnYWN5TlRQQ29udHJvbBAyGLWSygFSChIEMzIuKiACKAQSdAoVTW9iaWxlVGFibGV0TmF0aXZlTlRQGIDE0JcFOAFCEExlZ2FjeU5UUENvbnRyb2xKHQoZTmF0aXZlTlRQV2l0aFNpbmdsZVVybEJhchBfShQKEExlZ2FjeU5UUENvbnRyb2wQBVIMEgQzMS4qIAAgASgEEpoBChVNb2JpbGVUYWJsZXROYXRpdmVOVFAYgJeqmAU4AUIRTGVnYWN5TlRQQ29udHJvbDBKIgoZTmF0aXZlTlRQV2l0aFNpbmdsZVVybEJhchAJGLySygFKGgoRTGVnYWN5TlRQQ29udHJvbDEQUhi9ksoBShoKEUxlZ2FjeU5UUENvbnRyb2wwEAkYu5LKAVIKEgQzMi4qIAMoBBJdCgxOZXdNZW51U3R5bGUYgNCfjQU4AUIHRGVmYXVsdEoLCgdEZWZhdWx0EABKDQoITmV3U3R5bGUQ5gdKDAoIT2xkU3R5bGUQAlISEgQyNi4qGgQyNy4qIAMoACgDEn4KDE5ld01lbnVTdHlsZRiA/qKSBTgBQgdEZWZhdWx0SgsKB0RlZmF1bHQQAEoMCghDb21wYWN0MRAASgwKCENvbXBhY3QyEGRKEgoOSGlnaGVyQ29udHJhc3QQAEoQCgxDb250cm9sR3JvdXAQAFIOEgQyOC4qIAEgACgAKAMShAEKDE5ld01lbnVTdHlsZRiA74KWBTgBQgdEZWZhdWx0SgsKB0RlZmF1bHQQAEoMCghDb21wYWN0MRAASgwKCENvbXBhY3QyEGRKEgoOSGlnaGVyQ29udHJhc3QQAEoQCgxDb250cm9sR3JvdXAQAFIUEgQyOC4qGgQzMC4qIAMgAigAKAMSsAEKFE5ld1Byb2ZpbGVNYW5hZ2VtZW50GMSCl7QFOAFCB0NvbnRyb2xKNQoUQ29tbWFuZC1MaW5lLUVuYWJsZWQQABjIksoBKhZuZXctcHJvZmlsZS1tYW5hZ2VtZW50SgwKCERpc2FibGVkEABKEAoHRW5hYmxlZBAAGMiSygFKCwoHQ29udHJvbBAyUiEIxJS2lwUSCzM0LjAuMTc4Mi4yIAAgASgAKAEoAigEKAVgARJXCgxOZXdUYWJCdXR0b24YgLWNlgU4AUIHZGVmYXVsdEoLCgdkZWZhdWx0EGJKCwoHQ29udHJvbBABSggKBFBsdXMQAVISEgwyMS4wLjExODAuMTUgAygAEuEDChpPbW5pYm94QnVuZGxlZEV4cGVyaW1lbnRWMRiAnYabBTgBQhtCZXRhSFFQRXhwZXJpbWVudHNDb250cm9sUjJKnQEKL0JldGFSZW9kZXJIUVBBbmREaXNjb3VudEZyZWNlbmN5V2hlbkZld1Zpc2l0c1IyEAoyKAogSFFQRGlzY291bnRGcmVjZW5jeVdoZW5GZXdWaXNpdHMSBHRydWUyPgofUmVvcmRlckZvckxlZ2FsRGVmYXVsdE1hdGNoOio6KhIbUmVvcmRlckZvckxlZ2FsRGVmYXVsdE1hdGNoSjQKGEJldGFIUVBCb29rbWFya1ZhbHVlMTBSMhAKMhYKEEhRUEJvb2ttYXJrVmFsdWUSAjEwSjIKF0JldGFIUVBCb29rbWFya1ZhbHVlNVIyEAoyFQoQSFFQQm9va21hcmtWYWx1ZRIBNUpdChVCZXRhUmVvcmRlckhvbGRiYWNrUjIQCjJCCh9SZW9yZGVyRm9yTGVnYWxEZWZhdWx0TWF0Y2g6KjoqEh9Eb250UmVvcmRlckZvckxlZ2FsRGVmYXVsdE1hdGNoSh8KG0JldGFIUVBFeHBlcmltZW50c0NvbnRyb2xSMhA8Ug4IxICujgUSBDMzLiogAljO6qDxARL2BwoaT21uaWJveEJ1bmRsZWRFeHBlcmltZW50VjEYgJ2GmwU4AUIIU3RhbmRhcmRKNgoUU2VhcmNoSGlzdG9yeURpc2FibGUQADIcChFTZWFyY2hIaXN0b3J5Oio6KhIHRGlzYWJsZUofChtEZW1vdGVOYXZGYWtlYm94IFBPU1RQRVJJT0QQAUomCiJEZW1vdGVOYXZGYWtlYm94Q29udHJvbCBQT1NUUEVSSU9EEAFKKwonRGV2SFVQRXhwZXJpbWVudGFsU2NvcmluZ05vRGVjYXlDb250cm9sEApKyQEKIERldkhVUEV4cGVyaW1lbnRhbFNjb3JpbmdOb0RlY2F5EAoyIgodSFVQRXhwZXJpbWVudGFsU2NvcmluZ0VuYWJsZWQSATEyHAoWVHlwZWRDb3VudEhhbGZMaWZlVGltZRICLTEyLAoWVHlwZWRDb3VudFNjb3JlQnVja2V0cxISMC45NzoxMzYwLDAuMDoxMjgwMjMKGFZpc2l0ZWRDb3VudEhhbGZMaWZlVGltZRIXNC4wOjc5MCwwLjU6NTkwLDAuMDoxMDBKJAogRGV2SFVQRXhwZXJpbWVudGFsU2NvcmluZ0NvbnRyb2wQCkrCAQoZRGV2SFVQRXhwZXJpbWVudGFsU2NvcmluZxAKMiIKHUhVUEV4cGVyaW1lbnRhbFNjb3JpbmdFbmFibGVkEgExMhwKFlR5cGVkQ291bnRIYWxmTGlmZVRpbWUSAjMwMiwKFlR5cGVkQ291bnRTY29yZUJ1Y2tldHMSEjAuOTc6MTM2MCwwLjA6MTI4MDIzChhWaXNpdGVkQ291bnRIYWxmTGlmZVRpbWUSFzQuMDo3OTAsMC41OjU5MCwwLjA6MTAwSjYKMkRldlJlb3JkZXJBbmRIUVBTY29yaW5nU2hhcmVkQ29udHJvbCByNCBQT1NUUEVSSU9EEApKIAocRGVtb3RlTmF2RmFrZWJveDIgUE9TVFBFUklPRBAKSk4KSkRldkhRUFNjb3JpbmdBbGxvd01hdGNoSW5TY2hlbWVBbmREaXNjb3VudEZyZWNlbmN5V2hlbkZld1Zpc2l0cyBQT1NUUEVSSU9EEApKSwpHRGV2SFFQU2NvcmluZ0FsbG93TWF0Y2hJblRMREFuZERpc2NvdW50RnJlY2VuY3lXaGVuRmV3VmlzaXRzIFBPU1RQRVJJT0QQCko7CjdEZXZSZW9kZXJIUVBBbmREaXNjb3VudEZyZWNlbmN5V2hlbkZld1Zpc2l0cyBQT1NUUEVSSU9EEApKDAoIU3RhbmRhcmQQCFIfCMTFoZMFEgszMy4wLjE3MTEuKiAAIAEoACgBKAMoAhLFCwoaT21uaWJveEJ1bmRsZWRFeHBlcmltZW50VjEYgJ2GmwU4AUIFRW1wdHlKCQoFRW1wdHkQAEqmAQolUG9zdFBlcmlvZF9EZW1vdGVOYXZGYWtlYm94X1N0YWJsZV9SNBABGJOTygEgk5PKATItChBEZW1vdGVCeVR5cGU6ODoqEhkxOjYxLDI6NjEsMzo2MSw0OjYxLDEyOjYxMkIKH1Jlb3JkZXJGb3JMZWdhbERlZmF1bHRNYXRjaDoqOioSH0RvbnRSZW9yZGVyRm9yTGVnYWxEZWZhdWx0TWF0Y2hKrQEKLFBvc3RQZXJpb2RfRGVtb3RlTmF2RmFrZWJveENvbnRyb2xfU3RhYmxlX1I0EAEYlJPKASCUk8oBMi0KEERlbW90ZUJ5VHlwZTo4OioSGTE6NjEsMjo2MSwzOjYxLDQ6NjEsMTI6NjEyQgofUmVvcmRlckZvckxlZ2FsRGVmYXVsdE1hdGNoOio6KhIfRG9udFJlb3JkZXJGb3JMZWdhbERlZmF1bHRNYXRjaEqbAQoaUFBfT21uaWJveEdyb3VwQV9TdGFibGVfUjMQARj+ksoBIP6SygEyLQoQRGVtb3RlQnlUeXBlOjg6KhIZMTo2MSwyOjYxLDM6NjEsNDo2MSwxMjo2MTJCCh9SZW9yZGVyRm9yTGVnYWxEZWZhdWx0TWF0Y2g6KjoqEh9Eb250UmVvcmRlckZvckxlZ2FsRGVmYXVsdE1hdGNoSpsBChpQUF9PbW5pYm94R3JvdXBCX1N0YWJsZV9SMxABGP+SygEg/5LKATItChBEZW1vdGVCeVR5cGU6ODoqEhkxOjYxLDI6NjEsMzo2MSw0OjYxLDEyOjYxMkIKH1Jlb3JkZXJGb3JMZWdhbERlZmF1bHRNYXRjaDoqOioSH0RvbnRSZW9yZGVyRm9yTGVnYWxEZWZhdWx0TWF0Y2hKmwEKGlBQX09tbmlib3hHcm91cENfU3RhYmxlX1IzEAEYgJPKASCAk8oBMi0KEERlbW90ZUJ5VHlwZTo4OioSGTE6NjEsMjo2MSwzOjYxLDQ6NjEsMTI6NjEyQgofUmVvcmRlckZvckxlZ2FsRGVmYXVsdE1hdGNoOio6KhIfRG9udFJlb3JkZXJGb3JMZWdhbERlZmF1bHRNYXRjaEqbAQoaRGVtb3RlTmF2RmFrZWJveF9TdGFibGVfUjQQARiLk8oBIIuTygEyLQoQRGVtb3RlQnlUeXBlOjg6KhIZMTo2MSwyOjYxLDM6NjEsNDo2MSwxMjo2MTJCCh9SZW9yZGVyRm9yTGVnYWxEZWZhdWx0TWF0Y2g6KjoqEh9Eb250UmVvcmRlckZvckxlZ2FsRGVmYXVsdE1hdGNoSo4BCiFEZW1vdGVOYXZGYWtlYm94Q29udHJvbF9TdGFibGVfUjQQARiMk8oBIIyTygEyGQoQRGVtb3RlQnlUeXBlOjg6KhIFMToxMDAyQgofUmVvcmRlckZvckxlZ2FsRGVmYXVsdE1hdGNoOio6KhIfRG9udFJlb3JkZXJGb3JMZWdhbERlZmF1bHRNYXRjaEqOAQoXU3RhYmxlUmVvcmRlckhvbGRiYWNrUjIQCjItChBEZW1vdGVCeVR5cGU6ODoqEhkxOjYxLDI6NjEsMzo2MSw0OjYxLDEyOjYxMkIKH1Jlb3JkZXJGb3JMZWdhbERlZmF1bHRNYXRjaDoqOioSH0RvbnRSZW9yZGVyRm9yTGVnYWxEZWZhdWx0TWF0Y2hKgQEKClN0YW5kYXJkUjIQUzItChBEZW1vdGVCeVR5cGU6ODoqEhkxOjYxLDI6NjEsMzo2MSw0OjYxLDEyOjYxMkIKH1Jlb3JkZXJGb3JMZWdhbERlZmF1bHRNYXRjaDoqOioSH0RvbnRSZW9yZGVyRm9yTGVnYWxEZWZhdWx0TWF0Y2hSFgiAhP+UBRIEMzEuKiADKAAoASgDKAISzQEKHk9tbmlib3hSZXBsYWNlSFVQQW5kTmV3U2NvcmluZxjAhbOQBTgBQghTdGFuZGFyZEoMCghTdGFuZGFyZBA8Sg0KCVN0YW5kYXJkMhAKSh0KGUhRUC1wb3N0cGVyaW9kLVJlcGxhY2VIVVAQCkoOCgpOZXdTY29yaW5nEApKNAowSFFQLXBvc3RwZXJpb2QtUmVwbGFjZUhVUF9OZXctcG9zdHBlcmlvZC1TY29yaW5nEApSFwjAn4uHBRILMjguMC4xNDk5LiogASACEmQKFE9tbmlib3hTZWFyY2hIaXN0b3J5GMSQq5AFOAFCCFN0YW5kYXJkShMKD1ByZXZlbnRJbmxpbmluZxAKSgsKB0Rpc2FibGUQCkoMCghTdGFuZGFyZBBQUgoSBDMwLiogACABEm8KF09tbmlib3hTaG9ydGN1dHNTY29yaW5nGMTKiaAFOAFCCFN0YW5kYXJkSgwKCFN0YW5kYXJkEFpKFQoRTWF4UmVsZXZhbmNlXzEzOTkQClIdCMSAro4FEgsyOS4wLjE1NDcuMBoEMjkuKiABIAISZQoQT21uaWJveFN0b3BUaW1lchjA0ZmTBTgBQghTdGFuZGFyZEoSCghTdGFuZGFyZBD0Axi3hcoBShYKDFVzZVN0b3BUaW1lchD0Axi4hcoBUhMIwMzUiwUSCzI4LjAuMTQ4OC4wEmcKFE91dGRhdGVkSW5zdGFsbENoZWNrGMS0jZYFOAFCB0RlZmF1bHRKGgoWMTJXZWVrc091dGRhdGVkSW5zdGFsbBABSgsKB0RlZmF1bHQQAFIVEgsyNy4wLjE0NDcuMBoEMzAuKigAEq8BChJQYXNzd29yZEdlbmVyYXRpb24YxPPHnQU4AUIIRGlzYWJsZWRKCwoHRW5hYmxlZBAKSgwKCERpc2FibGVkEFpKLwoORGlzYWJsZWRCeUZsYWcQACobZGlzYWJsZS1wYXNzd29yZC1nZW5lcmF0aW9uSi0KDUVuYWJsZWRCeUZsYWcQACoaZW5hYmxlLXBhc3N3b3JkLWdlbmVyYXRpb25SDCAAIAEoACgBKAIoAxJGCghQcmVjYWNoZRjE6PiyBTgBQghEaXNhYmxlZEoLCgdFbmFibGVkEApKDQoIRGlzYWJsZWQQ3gdSDBIEMzQuKiAAIAEoBBKcAQoIUHJlZmV0Y2gYxJuSpQU4AUIHRGVmYXVsdEoWCg1FeHBlcmltZW50WWVzEAQY9pPKAUoXCg5FeHBlcmltZW50WWVzMhABGPeTygFKFgoNRXhwZXJpbWVudE5vMhABGPmTygFKFQoMRXhwZXJpbWVudE5vEAQY+JPKAUoLCgdEZWZhdWx0EFpSDBIEMzMuKiACIAAgAVii792IARKZAQoUUHJlcmVuZGVyRnJvbU9tbmlib3gYxLaknAU4AUIXT21uaWJveFByZXJlbmRlckVuYWJsZWRKGwoXT21uaWJveFByZXJlbmRlckVuYWJsZWQQAEocChhPbW5pYm94UHJlcmVuZGVyRGlzYWJsZWQQZFIlEgQzMS4qGgszNC4wLjE0ODIuMCAAIAEgAiADKAAoASgCKAMoBBJwChRQcmVyZW5kZXJGcm9tT21uaWJveBjEguiZBTgBQhhPbW5pYm94UHJlcmVuZGVyRGlzYWJsZWRKHAoYT21uaWJveFByZXJlbmRlckRpc2FibGVkEGRSGBIEMjkuKhoEMzAuKiADKAAoASgCKAMoBBJWChdQcmVyZW5kZXJMb2NhbFByZWRpY3RvchjEtI2WBTgAQghEaXNhYmxlZEoLCgdFbmFibGVkEGRKDAoIRGlzYWJsZWQQAFIOEgQyOS4qIAIoACgBKAISbAoXUHJlcmVuZGVyTG9jYWxQcmVkaWN0b3IYxIehjAU4AEIIRGlzYWJsZWRKDQoIRGlzYWJsZWQQ1AdKCwoHRW5hYmxlZBAKSgsKB0NvbnRyb2wQClIWEgQyNS4qGgQyOC4qIAMgAigAKAEoAhJmChdQcmVyZW5kZXJMb2NhbFByZWRpY3RvchjEtI2WBTgAQghEaXNhYmxlZEoMCghEaXNhYmxlZBBkSgwKB0VuYWJsZWQQoAZKCwoHQ29udHJvbBBkUhASBDI1LiogACABKAAoASgCElkKLlByZXJlbmRlckxvY2FsUHJlZGljdG9yTWF4Q29uY3VycmVudFByZXJlbmRlcnMYxLSNlgU4AEIBMUoFCgEzEGRKBQoBMRAAUg4SBDI5LiogAigAKAEoAhJwCi5QcmVyZW5kZXJMb2NhbFByZWRpY3Rvck1heENvbmN1cnJlbnRQcmVyZW5kZXJzGMS0jZYFOAFCATFKBQoBMRAUSgUKATIQFEoFCgEzEBRKBQoBNBAUSgUKATUQFFIQEgQyOS4qIAAgASgAKAEoAhJjCiVQcmVyZW5kZXJMb2NhbFByZWRpY3RvclByZXJlbmRlckxhbmNoGMS0jZYFOABCB0VuYWJsZWRKDAoIRGlzYWJsZWQQAEoLCgdFbmFibGVkEGRSDhIEMjkuKiACKAAoASgCEmMKJVByZXJlbmRlckxvY2FsUHJlZGljdG9yUHJlcmVuZGVyTGFuY2gYxLSNlgU4AUIHRW5hYmxlZEoMCghEaXNhYmxlZBBkSgsKB0VuYWJsZWQQAFIOEgQyOS4qIAMoACgBKAISZwo7UHJlcmVuZGVyTG9jYWxQcmVkaWN0b3JQcmVyZW5kZXJQcmlvcml0eUhhbGZMaWZlVGltZVNlY29uZHMYxLSNlgU4AEIBMEoGCgIzMBBkSgUKATAQAFIOEgQyOS4qIAIoACgBKAIScQo7UHJlcmVuZGVyTG9jYWxQcmVkaWN0b3JQcmVyZW5kZXJQcmlvcml0eUhhbGZMaWZlVGltZVNlY29uZHMYxLSNlgU4AUIBMEoFCgEwEDJKBgoCMzAQGUoGCgI2MBAZUhASBDI5LiogACABKAAoASgCEm4KKlByZXJlbmRlckxvY2FsUHJlZGljdG9yUHJlcmVuZGVyVFRMU2Vjb25kcxjEtI2WBTgBQgMxODBKBwoDMTgwEDRKBwoDMTIwEBBKBwoDMjQwEBBKBgoCNjAQEFIQEgQyOS4qIAAgASgAKAEoAhLRCgobUHJlcmVuZGVyTG9jYWxQcmVkaWN0b3JTcGVjGMSbkqUFOAFCF0xvY2FsUHJlZGljdG9yPURpc2FibGVkSoIBCn1pZD0xOkxvY2FsUHJlZGljdG9yPUVuYWJsZWQ6U2lkZUVmZmVjdEZyZWVXaGl0ZWxpc3Q9RW5hYmxlZDpNYXhDb25jdXJyZW50UHJlcmVuZGVycz0zOlByZXJlbmRlclByaW9yaXR5SGFsZkxpZmVUaW1lU2Vjb25kcz0zMBDFAkqBAQp9aWQ9MjpMb2NhbFByZWRpY3Rvcj1FbmFibGVkOlNpZGVFZmZlY3RGcmVlV2hpdGVsaXN0PUVuYWJsZWQ6TWF4Q29uY3VycmVudFByZXJlbmRlcnM9MzpQcmVyZW5kZXJQcmlvcml0eUhhbGZMaWZlVGltZVNlY29uZHM9MzAQS0p8CnhMb2NhbFByZWRpY3Rvcj1FbmFibGVkOlNpZGVFZmZlY3RGcmVlV2hpdGVsaXN0PUVuYWJsZWQ6TWF4Q29uY3VycmVudFByZXJlbmRlcnM9MjpQcmVyZW5kZXJQcmlvcml0eUhhbGZMaWZlVGltZVNlY29uZHM9MzAQS0p8CnhMb2NhbFByZWRpY3Rvcj1FbmFibGVkOlNpZGVFZmZlY3RGcmVlV2hpdGVsaXN0PUVuYWJsZWQ6TWF4Q29uY3VycmVudFByZXJlbmRlcnM9MTpQcmVyZW5kZXJQcmlvcml0eUhhbGZMaWZlVGltZVNlY29uZHM9MzAQS0p7CndMb2NhbFByZWRpY3Rvcj1FbmFibGVkOlNpZGVFZmZlY3RGcmVlV2hpdGVsaXN0PUVuYWJsZWQ6TWF4Q29uY3VycmVudFByZXJlbmRlcnM9MzpQcmVyZW5kZXJQcmlvcml0eUhhbGZMaWZlVGltZVNlY29uZHM9MBBLSnwKeExvY2FsUHJlZGljdG9yPUVuYWJsZWQ6U2lkZUVmZmVjdEZyZWVXaGl0ZWxpc3Q9RW5hYmxlZDpNYXhDb25jdXJyZW50UHJlcmVuZGVycz0zOlByZXJlbmRlclByaW9yaXR5SGFsZkxpZmVUaW1lU2Vjb25kcz02MBBLSpYBCpEBTG9jYWxQcmVkaWN0b3I9RW5hYmxlZDpTaWRlRWZmZWN0RnJlZVdoaXRlbGlzdD1FbmFibGVkOk1heENvbmN1cnJlbnRQcmVyZW5kZXJzPTM6UHJlcmVuZGVyUHJpb3JpdHlIYWxmTGlmZVRpbWVTZWNvbmRzPTMwOlByZXJlbmRlckxhdW5jaD1EaXNhYmxlZBBLSpwBCpcBTG9jYWxQcmVkaWN0b3I9RW5hYmxlZDpTaWRlRWZmZWN0RnJlZVdoaXRlbGlzdD1FbmFibGVkOk1heENvbmN1cnJlbnRQcmVyZW5kZXJzPTM6UHJlcmVuZGVyUHJpb3JpdHlIYWxmTGlmZVRpbWVTZWNvbmRzPTMwOlByZXJlbmRlckFsd2F5c0NvbnRyb2w9RW5hYmxlZBBLSqsBCqYBTG9jYWxQcmVkaWN0b3I9RW5hYmxlZDpTaWRlRWZmZWN0RnJlZVdoaXRlbGlzdD1FbmFibGVkOk1heENvbmN1cnJlbnRQcmVyZW5kZXJzPTM6UHJlcmVuZGVyUHJpb3JpdHlIYWxmTGlmZVRpbWVTZWNvbmRzPTMwOlByZXJlbmRlckxhdW5jaD1EaXNhYmxlZDpTa2lwTG9nZ2VkSW49RW5hYmxlZBBLShsKF0xvY2FsUHJlZGljdG9yPURpc2FibGVkEEtSDhIEMzAuKiACKAAoASgCEvgGChtQcmVyZW5kZXJMb2NhbFByZWRpY3RvclNwZWMYxJuSpQU4AUIcY2Q9MzpMb2NhbFByZWRpY3Rvcj1EaXNhYmxlZEqCAQp+Y2Q9MWE6TG9jYWxQcmVkaWN0b3I9RW5hYmxlZDpTaWRlRWZmZWN0RnJlZVdoaXRlbGlzdD1FbmFibGVkOk1heENvbmN1cnJlbnRQcmVyZW5kZXJzPTM6UHJlcmVuZGVyUHJpb3JpdHlIYWxmTGlmZVRpbWVTZWNvbmRzPTMwEDJKsgEKrQFjZD0xZTpMb2NhbFByZWRpY3Rvcj1FbmFibGVkOlNpZGVFZmZlY3RGcmVlV2hpdGVsaXN0PUVuYWJsZWQ6TWF4Q29uY3VycmVudFByZXJlbmRlcnM9MzpQcmVyZW5kZXJQcmlvcml0eUhhbGZMaWZlVGltZVNlY29uZHM9MzA6RGlzYWJsZVNlc3Npb25TdG9yYWdlTmFtZXNwYWNlTWVyZ2luZz1EaXNhYmxlZBAySs4BCskBY2Q9MmE6TG9jYWxQcmVkaWN0b3I9RW5hYmxlZDpTaWRlRWZmZWN0RnJlZVdoaXRlbGlzdD1FbmFibGVkOk1heENvbmN1cnJlbnRQcmVyZW5kZXJzPTM6UHJlcmVuZGVyUHJpb3JpdHlIYWxmTGlmZVRpbWVTZWNvbmRzPTMwOlByZXJlbmRlclF1ZXJ5UHJlcmVuZGVyU2VydmljZT1FbmFibGVkOlByZXJlbmRlclNlcnZpY2VGZXRjaFRpbWVvdXRNcz01MDAwEDJK8wEK7gFjZD0yYjpMb2NhbFByZWRpY3Rvcj1FbmFibGVkOlNpZGVFZmZlY3RGcmVlV2hpdGVsaXN0PUVuYWJsZWQ6TWF4Q29uY3VycmVudFByZXJlbmRlcnM9MzpQcmVyZW5kZXJQcmlvcml0eUhhbGZMaWZlVGltZVNlY29uZHM9MzA6UHJlcmVuZGVyUXVlcnlQcmVyZW5kZXJTZXJ2aWNlPUVuYWJsZWQ6UHJlcmVuZGVyU2VydmljZUZldGNoVGltZW91dE1zPTUwMDA6U2tpcFByZXJlbmRlckxvY2FsQ2FuZGlkYXRlcz1FbmFibGVkEDJKIAocY2Q9MzpMb2NhbFByZWRpY3Rvcj1EaXNhYmxlZBBkUhASBDMxLiogACABKAAoASgCEosEChtQcmVyZW5kZXJMb2NhbFByZWRpY3RvclNwZWMYxJuSpQU4AUIXTG9jYWxQcmVkaWN0b3I9RGlzYWJsZWRKqgEKpQFMYWJlbD1TdGFibGUyOkxvY2FsUHJlZGljdG9yPUVuYWJsZWQ6U2lkZUVmZmVjdEZyZWVXaGl0ZWxpc3Q9RW5hYmxlZDpNYXhDb25jdXJyZW50UHJlcmVuZGVycz0zOlByZXJlbmRlclByaW9yaXR5SGFsZkxpZmVUaW1lU2Vjb25kcz0zMDpQcmVyZW5kZXJBbHdheXNDb250cm9sPUVuYWJsZWQQMkq8AQq3AUxhYmVsPVN0YWJsZTM6TG9jYWxQcmVkaWN0b3I9RW5hYmxlZDpTaWRlRWZmZWN0RnJlZVdoaXRlbGlzdD1FbmFibGVkOk1heENvbmN1cnJlbnRQcmVyZW5kZXJzPTM6UHJlcmVuZGVyUHJpb3JpdHlIYWxmTGlmZVRpbWVTZWNvbmRzPTMwOlByZXJlbmRlckFsd2F5c0NvbnRyb2w9RW5hYmxlZDpTa2lwSFRUUFM9RW5hYmxlZBAySjEKLUxhYmVsPVN0YWJsZUNvbnRyb2w1MDpMb2NhbFByZWRpY3Rvcj1EaXNhYmxlZBAyShwKF0xvY2FsUHJlZGljdG9yPURpc2FibGVkEPpMUg4SBDMwLiogAygAKAEoAhJWChdQcmVyZW5kZXJMb2NhbFByZWRpY3RvchjEtI2WBTgBQghEaXNhYmxlZEoLCgdFbmFibGVkEAFKDAoIRGlzYWJsZWQQY1IOEgQyOS4qIAMoACgBKAISVgoSTWFudWFsUmVzZXRQcm9maWxlGMSz05UFOAFCB0Rpc2FibGVKCgoGRW5hYmxlEGRKCwoHRGlzYWJsZRAAUhYSBDI5LiogACABIAIgAygAKAEoAygCEvkBCg9Qcm9ncmVzc2l2ZVNjYW4YxLSNlgU4AUIHRGVmYXVsdEoVChEzM1BlcmNlbnRfNE1pbk1heBAyShUKETUwUGVyY2VudF80TWluTWF4EABKFQoRNTBQZXJjZW50XzhNaW5NYXgQAEoWChIxMDBQZXJjZW50XzhNaW5NYXgQAEoMCghGdWxsU2NhbhAyShkKFTEwMFBlcmNlbnRfMU1pblNlZW5fQRAyShkKFTEwMFBlcmNlbnRfMU1pblNlZW5fQhAyShgKFDEwMFBlcmNlbnRfMU1pbl80TWF4EDJKDAoHRGVmYXVsdBDuBVIMEgQzMi4qIAAgASgDEqICCgRRVUlDGMT4jKUFOABCCERpc2FibGVkShUKDEh0dHBzRW5hYmxlZBAEGIeSygFKFQoMQ29udHJvbEh0dHBzEAQYiJLKAUoeCgxGbGFnRGlzYWJsZWQQACoMZGlzYWJsZS1xdWljSicKEEZsYWdIdHRwc0VuYWJsZWQQACoRZW5hYmxlLXF1aWMtaHR0cHNKKQoRRmxhZ0h0dHBzRGlzYWJsZWQQACoSZGlzYWJsZS1xdWljLWh0dHBzShwKC0ZsYWdFbmFibGVkEAAqC2VuYWJsZS1xdWljSigKDkZsYWdQYWNrZXRTaXplEAAqFHF1aWMtbWF4LXBhY2tldC1zaXplSgwKCERpc2FibGVkEFxSEBIMMzQuMC4xODQ3LjYwIAIShwMKBFFVSUMYxPiMpQU4AEIIRGlzYWJsZWRKEAoHRW5hYmxlZBAIGP+RygFKHwoWRW5hYmxlZDEzNTBCeXRlUGFja2V0cxAVGIOSygFKFQoMSHR0cHNFbmFibGVkEBUY/5HKAUoQCgdDb250cm9sEAgYgJLKAUofChZDb250cm9sMTM1MEJ5dGVQYWNrZXRzEBUYhJLKAUoVCgxDb250cm9sSHR0cHMQFRiAksoBSh4KDEZsYWdEaXNhYmxlZBAAKgxkaXNhYmxlLXF1aWNKJwoQRmxhZ0h0dHBzRW5hYmxlZBAAKhFlbmFibGUtcXVpYy1odHRwc0opChFGbGFnSHR0cHNEaXNhYmxlZBAAKhJkaXNhYmxlLXF1aWMtaHR0cHNKHAoLRmxhZ0VuYWJsZWQQACoLZW5hYmxlLXF1aWNKKAoORmxhZ1BhY2tldFNpemUQACoUcXVpYy1tYXgtcGFja2V0LXNpemVKDAoIRGlzYWJsZWQQAFIPEgszNS4wLjE4ODEuMCAAEocDCgRRVUlDGMT4jKUFOABCCERpc2FibGVkShAKB0VuYWJsZWQQABiBksoBSh8KFkVuYWJsZWQxMzUwQnl0ZVBhY2tldHMQBBiDksoBShUKDEh0dHBzRW5hYmxlZBAEGIWSygFKEAoHQ29udHJvbBAAGIKSygFKHwoWQ29udHJvbDEzNTBCeXRlUGFja2V0cxAEGISSygFKFQoMQ29udHJvbEh0dHBzEAQYhpLKAUoeCgxGbGFnRGlzYWJsZWQQACoMZGlzYWJsZS1xdWljSicKEEZsYWdIdHRwc0VuYWJsZWQQACoRZW5hYmxlLXF1aWMtaHR0cHNKKQoRRmxhZ0h0dHBzRGlzYWJsZWQQACoSZGlzYWJsZS1xdWljLWh0dHBzShwKC0ZsYWdFbmFibGVkEAAqC2VuYWJsZS1xdWljSigKDkZsYWdQYWNrZXRTaXplEAAqFHF1aWMtbWF4LXBhY2tldC1zaXplSgwKCERpc2FibGVkEFRSDxILMzUuMC4xODgzLjAgARKiAgoEUVVJQxjE+IylBTgAQghEaXNhYmxlZEoVCgxIdHRwc0VuYWJsZWQQABiJksoBShUKDENvbnRyb2xIdHRwcxAAGIqSygFKHgoMRmxhZ0Rpc2FibGVkEAAqDGRpc2FibGUtcXVpY0onChBGbGFnSHR0cHNFbmFibGVkEAAqEWVuYWJsZS1xdWljLWh0dHBzSikKEUZsYWdIdHRwc0Rpc2FibGVkEAAqEmRpc2FibGUtcXVpYy1odHRwc0ocCgtGbGFnRW5hYmxlZBAAKgtlbmFibGUtcXVpY0ooCg5GbGFnUGFja2V0U2l6ZRAAKhRxdWljLW1heC1wYWNrZXQtc2l6ZUoMCghEaXNhYmxlZBBkUhASDDM0LjAuMTg0Ny42MCADEjYKDlNCSW50ZXJzdGl0aWFsGMDSjYkFOAFCAlYxSgYKAlYxEAFKBgoCVjIQY1IIKAAoASgCKAMSWgoPU1JQU2VhcmNoQnV0dG9uGMSqsqEFOAFCCE5vQnV0dG9uSgoKBkJ1dHRvbhAKSgsKB0NvbnRyb2wQCkoMCghOb0J1dHRvbhBQUg4SBDMyLiogACABKAAoARKwAQoYU2VuZEZlZWRiYWNrTGlua0xvY2F0aW9uGMCSgpEFOAFCB2RlZmF1bHRKEAoHZGVmYXVsdBAoGKmFygFKEAoHY29udHJvbBAPGPiEygFKEQoIYWx0LXRleHQQDxj8hMoBShUKDGFsdC1sb2NhdGlvbhAPGIKFygFKHgoVYWx0LXRleHQtYW5kLWxvY2F0aW9uEA8YiIXKAVIVEgsyNy4wLjE0NTMuKiACKAAoAigBEqwBChhTZW5kRmVlZGJhY2tMaW5rTG9jYXRpb24YwJKCkQU4AUIHZGVmYXVsdEoQCgdkZWZhdWx0ECgYqYXKAUoQCgdjb250cm9sEA8Y+YTKAUoRCghhbHQtdGV4dBAPGP+EygFKFQoMYWx0LWxvY2F0aW9uEA8YhYXKAUoeChVhbHQtdGV4dC1hbmQtbG9jYXRpb24QDxiLhcoBUhESCzI3LjAuMTQ1My4qIAIoAxKyAQoYU2VuZEZlZWRiYWNrTGlua0xvY2F0aW9uGMCSgpEFOAFCB2RlZmF1bHRKEAoHZGVmYXVsdBAoGKmFygFKEAoHY29udHJvbBAPGPiEygFKEQoIYWx0LXRleHQQDxj6hMoBShUKDGFsdC1sb2NhdGlvbhAPGICFygFKHgoVYWx0LXRleHQtYW5kLWxvY2F0aW9uEA8YhoXKAVIXEgsyNy4wLjE0NTMuKiABIAAoACgCKAESrgEKGFNlbmRGZWVkYmFja0xpbmtMb2NhdGlvbhjAkoKRBTgBQgdkZWZhdWx0ShAKB2RlZmF1bHQQKBiphcoBShAKB2NvbnRyb2wQDxj5hMoBShEKCGFsdC10ZXh0EA8Y/YTKAUoVCgxhbHQtbG9jYXRpb24QDxiDhcoBSh4KFWFsdC10ZXh0LWFuZC1sb2NhdGlvbhAPGImFygFSExILMjcuMC4xNDUzLiogASAAKAMSsAEKGFNlbmRGZWVkYmFja0xpbmtMb2NhdGlvbhjAkoKRBTgBQgdkZWZhdWx0ShAKB2RlZmF1bHQQPBiphcoBShAKB2NvbnRyb2wQChj4hMoBShEKCGFsdC10ZXh0EAoY+4TKAUoVCgxhbHQtbG9jYXRpb24QChiBhcoBSh4KFWFsdC10ZXh0LWFuZC1sb2NhdGlvbhAKGIeFygFSFRILMjcuMC4xNDUzLiogAygAKAIoARKsAQoYU2VuZEZlZWRiYWNrTGlua0xvY2F0aW9uGMCSgpEFOAFCB2RlZmF1bHRKEAoHZGVmYXVsdBA8GKmFygFKEAoHY29udHJvbBAKGPmEygFKEQoIYWx0LXRleHQQChj+hMoBShUKDGFsdC1sb2NhdGlvbhAKGISFygFKHgoVYWx0LXRleHQtYW5kLWxvY2F0aW9uEAoYioXKAVIREgsyNy4wLjE0NTMuKiADKAMSfgoTU2V0dGluZ3NFbmZvcmNlbWVudBjEh62hBTgBQg5lbmZvcmNlX2Fsd2F5c0oSCg5ub19lbmZvcmNlbWVudBBkShMKD2VuZm9yY2Vfb25fbG9hZBAAShIKDmVuZm9yY2VfYWx3YXlzEABSEiAAIAEgAiADKAEoAygCKAUoBBJwChNTZXR0aW5nc0VuZm9yY2VtZW50GMSHraEFOAFCDmVuZm9yY2VfYWx3YXlzShIKDm5vX2VuZm9yY2VtZW50EGRKEwoPZW5mb3JjZV9vbl9sb2FkEABKEgoOZW5mb3JjZV9hbHdheXMQAFIEIAIoABJ/ChNTZXR0aW5nc0VuZm9yY2VtZW50GMSHraEFOAFCDmVuZm9yY2VfYWx3YXlzShIKDm5vX2VuZm9yY2VtZW50EGRKEwoPZW5mb3JjZV9vbl9sb2FkEABKEgoOZW5mb3JjZV9hbHdheXMQAFITEgszNC4wLjE4MzguMiAAIAEoABJwChNTZXR0aW5nc0VuZm9yY2VtZW50GMSHraEFOAFCDmVuZm9yY2VfYWx3YXlzShIKDm5vX2VuZm9yY2VtZW50EGRKEwoPZW5mb3JjZV9vbl9sb2FkEABKEgoOZW5mb3JjZV9hbHdheXMQAFIEIAMoABJ5ChRTaG93QXBwTGF1bmNoZXJQcm9tbzgBQgdEZWZhdWx0SiAKF1Nob3dQcm9tb1VudGlsRGlzbWlzc2VkEGQYnobKAUobChJSZXNldFNob3dQcm9tb1ByZWYQABifhsoBSgsKB0RlZmF1bHQQAFIKIAAgASACIAMoABJiChNTaG93UHJvZmlsZVN3aXRjaGVyGMDK4IoFOAFCB2RlZmF1bHRKCwoHZGVmYXVsdBBQSgsKB2NvbnRyb2wQCkoOCgpBbHdheXNTaG93EApSEBIEMjYuKiAAIAEoACgBKAISVwoPU2lkZWxvYWRXaXBlb3V0GMScnYsFOAFCCERpc2FibGVkSgwKCERpc2FibGVkEABKCwoHRW5hYmxlZBBkUhcSCzI1LjAuMTM2MC4qIAAgASACIAMoABJ3ChpTaWduSW5Ub0Nocm9tZUNvbmZpcm1hdGlvbhjAyuCKBTgBQgdkZWZhdWx0SgsKB2RlZmF1bHQQUEoUChBjb250cm9sX0RJU0FCTEVEEApKEwoPQnViYmxlX0RJU0FCTEVEEApSEBIEMjYuKiAAIAEoACgBKAIShAEKEFNpbXBsZUNhY2hlVHJpYWwYxLSNlgU4AUIMRXhwZXJpbWVudE5vShIKDkV4cGVyaW1lbnRZZXMyEAVKEQoNRXhwZXJpbWVudFllcxBVShUKEUV4cGVyaW1lbnRDb250cm9sEAVKEAoMRXhwZXJpbWVudE5vEAVSChIEMjkuKiACKAQShgEKEFNpbXBsZUNhY2hlVHJpYWwYxLSNlgU4AUIMRXhwZXJpbWVudE5vShAKDEV4cGVyaW1lbnRObxAAShEKDUV4cGVyaW1lbnRZZXMQUEoSCg5FeHBlcmltZW50WWVzMhAKShUKEUV4cGVyaW1lbnRDb250cm9sEApSDBIEMjguKiAAIAEoBBKLAQowU2ltcGxlQ2FjaGVJbmRleEZsdXNoRGVsYXlfRm9yZWdyb3VuZF9CYWNrZ3JvdW5kGMS0jZYFOAFCCTIwMDAwXzEwMEoMCgg1MDAwXzEwMBAZSgwKCDEwMDBfMTAwEBlKCwoHMTAwMF8yMBAZSg0KCTIwMDAwXzEwMBAZUgwSBDI5LiogACABKAQSYQoVU2ltcGxlQ2FjaGVNYXhUaHJlYWRzGMS0jZYFOABCAjUwSgUKATEQCkoFCgE1EBJKBgoCMTAQEkoGCgIyMBASSgYKAjMwEBJKBgoCNTAQElIOEgQyOS4qIAAgASACKAQShAEKEFNpbXBsZUNhY2hlVHJpYWwYxJuSpQU4AUIMRXhwZXJpbWVudE5vShIKDkV4cGVyaW1lbnRZZXMyEAFKFQoRRXhwZXJpbWVudENvbnRyb2wQAUoRCg1FeHBlcmltZW50WWVzEFlKEAoMRXhwZXJpbWVudE5vEAlSChIEMzAuKiADKAQSoAMKBFNQRFkYxPiMpQU4AEILU3BkeUVuYWJsZWRKCwoHQ29udHJvbBAFShAKDFNwZHlEaXNhYmxlZBAFSj4KHlNwZHlFbmFibGVkQnlXZWJTb2NrZXRPdmVyU3BkeRAAKhplbmFibGUtd2Vic29ja2V0LW92ZXItc3BkeUo0ChlTcGR5RW5hYmxlZEJ5SHR0cDJEcmFmdDA0EAAqFWVuYWJsZS1odHRwMi1kcmFmdC0wNEooChRTcGR5RW5hYmxlZEJ5U3BkeTRhMhAAKg5lbmFibGUtc3BkeTRhMkomChJTcGR5RW5hYmxlZEJ5U3BkeTMQACoOZGlzYWJsZS1zcGR5MzFKJAoUU3BkeUVuYWJsZWRCeU5wblNwZHkQACoKZW5hYmxlLW5wbkojChVTcGR5RGlzYWJsZWRCeVVzZVNwZHkQACoIdXNlLXNwZHlKKwoWU3BkeURpc2FibGVkQnlIdHRwT25seRAAKg9lbmFibGUtbnBuLWh0dHBKDwoLU3BkeUVuYWJsZWQQWlITEgszMi4wLjE2NTMuMCACKAQoBRKeAwoEU1BEWRjE+IylBTgAQgtTcGR5RW5hYmxlZEoLCgdDb250cm9sEAVKEAoMU3BkeURpc2FibGVkEAVKPgoeU3BkeUVuYWJsZWRCeVdlYlNvY2tldE92ZXJTcGR5EAAqGmVuYWJsZS13ZWJzb2NrZXQtb3Zlci1zcGR5SjQKGVNwZHlFbmFibGVkQnlIdHRwMkRyYWZ0MDQQACoVZW5hYmxlLWh0dHAyLWRyYWZ0LTA0SigKFFNwZHlFbmFibGVkQnlTcGR5NGEyEAAqDmVuYWJsZS1zcGR5NGEySiYKElNwZHlFbmFibGVkQnlTcGR5MxAAKg5kaXNhYmxlLXNwZHkzMUokChRTcGR5RW5hYmxlZEJ5TnBuU3BkeRAAKgplbmFibGUtbnBuSiMKFVNwZHlEaXNhYmxlZEJ5VXNlU3BkeRAAKgh1c2Utc3BkeUorChZTcGR5RGlzYWJsZWRCeUh0dHBPbmx5EAAqD2VuYWJsZS1ucG4taHR0cEoPCgtTcGR5RW5hYmxlZBBaUhESCzMyLjAuMTY1My4wIAAgARKVAwoeU3BlY3VsYXRpdmVSZXNvdXJjZVByZWZldGNoaW5nGICc44oFOAFCCERpc2FibGVkSgwKCERpc2FibGVkEChKEAoMTGVhcm5pbmdIb3N0EApKDwoLTGVhcm5pbmdVUkwQCkoMCghMZWFybmluZxAKShMKD1ByZWZldGNoaW5nSG9zdBAKShIKDlByZWZldGNoaW5nVVJMEApKDwoLUHJlZmV0Y2hpbmcQCkocChhQcmVmZXRjaGluZ0xvd0NvbmZpZGVuY2UQAEodChlQcmVmZXRjaGluZ0hpZ2hDb25maWRlbmNlEABKHAoYUHJlZmV0Y2hpbmdNb3JlUmVzb3VyY2VzEABKEwoPTGVhcm5pbmdTbWFsbERCEABKFgoSUHJlZmV0Y2hpbmdTbWFsbERCEABKIwofUHJlZmV0Y2hpbmdTbWFsbERCTG93Q29uZmlkZW5jZRAASiQKIFByZWZldGNoaW5nU21hbGxEQkhpZ2hDb25maWRlbmNlEABSFRILMjUuMC4xMzY0LiogAigAKAEoAhKXAwoeU3BlY3VsYXRpdmVSZXNvdXJjZVByZWZldGNoaW5nGICc44oFOAFCCERpc2FibGVkSgwKCERpc2FibGVkEChKEAoMTGVhcm5pbmdIb3N0EApKDwoLTGVhcm5pbmdVUkwQCkoMCghMZWFybmluZxAKShMKD1ByZWZldGNoaW5nSG9zdBAKShIKDlByZWZldGNoaW5nVVJMEApKDwoLUHJlZmV0Y2hpbmcQCkocChhQcmVmZXRjaGluZ0xvd0NvbmZpZGVuY2UQAEodChlQcmVmZXRjaGluZ0hpZ2hDb25maWRlbmNlEABKHAoYUHJlZmV0Y2hpbmdNb3JlUmVzb3VyY2VzEABKEwoPTGVhcm5pbmdTbWFsbERCEABKFgoSUHJlZmV0Y2hpbmdTbWFsbERCEABKIwofUHJlZmV0Y2hpbmdTbWFsbERCTG93Q29uZmlkZW5jZRAASiQKIFByZWZldGNoaW5nU21hbGxEQkhpZ2hDb25maWRlbmNlEABSFxILMjUuMC4xMzY0LiogACABKAAoASgCEl0KFVNwZWN1bGF0aXZlVGFiUmVzdG9yZRjEguiZBTgBQghEaXNhYmxlZEoLCgdFbmFibGVkEApKCwoHQ29udHJvbBAKSgwKCERpc2FibGVkEFBSChIEMzEuKiACKAQSXwoVU3BlY3VsYXRpdmVUYWJSZXN0b3JlGMSC6JkFOAFCCERpc2FibGVkSgsKB0VuYWJsZWQQMkoLCgdDb250cm9sEDJKDAoIRGlzYWJsZWQQAFIMEgQzMS4qIAAgASgEElIKClNwZWxsY2hlY2sYxNu0jQU4AUIHRGVmYXVsdEoLCgdEZWZhdWx0EGRKDwoLU3VnZ2VzdGlvbnMQAFIVEgsyNi4wLjE0MDMuMCACIAEgACgAElQKClNwZWxsY2hlY2sYxNu0jQU4AUIHRGVmYXVsdEoLCgdEZWZhdWx0EGRKDwoLU3VnZ2VzdGlvbnMQAFIXEgsyNi4wLjE0MDMuMCADIAIgASgCKAMSTgoKU3BlbGxjaGVjaxjE27SNBTgBQgdEZWZhdWx0SgsKB0RlZmF1bHQQZEoPCgtTdWdnZXN0aW9ucxAAUhESCzI2LjAuMTQwMy4wIAMoABJPCg1FbmFibGVTdGFnZTNEGICI/oYFOAFCB2VuYWJsZWRKDAoHZW5hYmxlZBC2B0oPCgtibGFja2xpc3RlZBAyUg4SBDIyLiogACABIAIoABJRCg1FbmFibGVTdGFnZTNEGICI/oYFOAFCB2VuYWJsZWRKDAoHZW5hYmxlZBDoB0oPCgtibGFja2xpc3RlZBAAUhASBDIyLioaBDIyLiogAygAEmgKDUVuYWJsZVN0YWdlM0QYgN+/iQU4AUIPZW5hYmxlZF9kZWZhdWx0ShMKD2VuYWJsZWRfZGVmYXVsdBAASgwKB2VuYWJsZWQQ6AdKDwoLYmxhY2tsaXN0ZWQQAFIKEgQyMy4qIAMoABK0AgoLVGFiRXZpY3Rpb24YgPmMpQU4AUIHRGVmYXVsdEoLCgdDb250cm9sEDJKEQoNTWVtb3J5S2VlcEFsbBAAShMKD01lbW9yeUV2aWN0SGFsZhAAShMKD01lbW9yeUtlZXBUaHJlZRAAShQKEE1lbW9yeUV2aWN0VGhyZWUQAEoYChRCYWNrZ3JvdW5kaW5nS2VlcEFsbBAAShoKFkJhY2tncm91bmRpbmdFdmljdEhhbGYQAEoYChRCYWNrZ3JvdW5kaW5nS2VlcE9uZRAAShsKF0JhY2tncm91bmRpbmdFdmljdFRocmVlEABKEgoOTm9QcmVyZW5kZXJpbmcQMkoPCgtOb1F1aWNrYmFjaxAySgwKB0RlZmF1bHQQ0gZSFBIEMjkuKhoEMzEuKiAAIAIgAygFEtoCCgtUYWJFdmljdGlvbhiAxYqYBTgBQgdEZWZhdWx0SgsKB0NvbnRyb2wQMkoRCg1NZW1vcnlLZWVwQWxsEABKEwoPTWVtb3J5RXZpY3RIYWxmEABKEwoPTWVtb3J5S2VlcFRocmVlEDJKEQoNTWVtb3J5S2VlcFR3bxAyShQKEE1lbW9yeUV2aWN0VGhyZWUQAEoYChRCYWNrZ3JvdW5kaW5nS2VlcEFsbBAAShoKFkJhY2tncm91bmRpbmdFdmljdEhhbGYQAEoYChRCYWNrZ3JvdW5kaW5nS2VlcE9uZRAAShsKF0JhY2tncm91bmRpbmdFdmljdFRocmVlEABKEgoOTm9QcmVyZW5kZXJpbmcQMkoPCgtOb1F1aWNrYmFjaxAyShEKDU1lbW9yeUNvbnRyb2wQMkoMCgdEZWZhdWx0ELwFUhQSBDMyLioaBDMyLiogACACIAMoBRKwAwoLVGFiRXZpY3Rpb24YgPmMpQU4AUIHRGVmYXVsdEoMCgdDb250cm9sEPoBShEKDU1lbW9yeUtlZXBBbGwQAEoTCg9NZW1vcnlFdmljdEhhbGYQAEoTCg9NZW1vcnlLZWVwVGhyZWUQAEoRCg1NZW1vcnlLZWVwVHdvEABKFAoQTWVtb3J5RXZpY3RUaHJlZRAAShgKFEJhY2tncm91bmRpbmdLZWVwQWxsEABKGgoWQmFja2dyb3VuZGluZ0V2aWN0SGFsZhAAShgKFEJhY2tncm91bmRpbmdLZWVwT25lEABKGwoXQmFja2dyb3VuZGluZ0V2aWN0VGhyZWUQAEoSCg5Ob1ByZXJlbmRlcmluZxAASg8KC05vUXVpY2tiYWNrEABKIQocVGFiUHJvdGVjdGlvbkFueUZvcm1BY3Rpdml0eRD6AUoiCh1UYWJQcm90ZWN0aW9uTW9zdEZvcm1BY3Rpdml0eRD6AUokCh9UYWJQcm90ZWN0aW9uTGF0ZXN0Rm9ybUFjdGl2aXR5EPoBSgsKB0RlZmF1bHQQAFIQEgQzMy4qGgQzMy4qIAIoBRLMAwoLVGFiRXZpY3Rpb24YgPmMpQU4AUIHRGVmYXVsdEoLCgdDb250cm9sEABKEQoNTWVtb3J5S2VlcEFsbBAAShMKD01lbW9yeUV2aWN0SGFsZhAAShMKD01lbW9yeUtlZXBUaHJlZRAAShEKDU1lbW9yeUtlZXBUd28QAEoUChBNZW1vcnlFdmljdFRocmVlEABKGAoUQmFja2dyb3VuZGluZ0tlZXBBbGwQAEoaChZCYWNrZ3JvdW5kaW5nRXZpY3RIYWxmEABKGAoUQmFja2dyb3VuZGluZ0tlZXBPbmUQAEobChdCYWNrZ3JvdW5kaW5nRXZpY3RUaHJlZRAAShIKDk5vUHJlcmVuZGVyaW5nEABKDwoLTm9RdWlja2JhY2sQAEodChlUYWJQcm90ZWN0aW9uTm9Qcm90ZWN0aW9uEDJKIAocVGFiUHJvdGVjdGlvbkFueUZvcm1BY3Rpdml0eRAASiEKHVRhYlByb3RlY3Rpb25Nb3N0Rm9ybUFjdGl2aXR5EDJKIwofVGFiUHJvdGVjdGlvbkxhdGVzdEZvcm1BY3Rpdml0eRAySgwKB0RlZmF1bHQQ0gZSEBIEMzMuKhoEMzMuKiADKAUSTgoTVGVzdDBQZXJjZW50RGVmYXVsdBiAnJKlBTgBQgdkZWZhdWx0SgsKB2RlZmF1bHQQAEoMCghncm91cF8wMRBkSgsKB2RvZ2Zvb2QQABJhChhUcmFuc2xhdGVJbmZvYmFyVnNCdWJibGUYxLmvmAU4AUIHRGVmYXVsdEoLCgdJbmZvYmFyEAFKCgoGQnViYmxlEAFKCwoHRGVmYXVsdBBiUg4SBDMyLiogACABKAAoAxKXAgocVU1BLUR5bmFtaWMtVW5pZm9ybWl0eS1UcmlhbBiAnJKlBTgBQgdEZWZhdWx0ShQKBkdyb3VwMRABGL+2yQEgv7bJAUoUCgZHcm91cDIQCRjAtskBIMC2yQFKFQoGR3JvdXAzEOoDGMG2yQEgwbbJAUoUCgZHcm91cDQQARjCtskBIMK2yQFKFAoGR3JvdXA1EAkYw7bJASDDtskBShUKBkdyb3VwNhDqAxjEtskBIMS2yQFKOQoGR3JvdXA3EAAYxrbJASDGtskBKiN1bWEtZHluYW1pYy11bmlmb3JtaXR5LXRyaWFsLWdyb3VwN0oVCgdEZWZhdWx0EAAYxbbJASDFtskBUgwSBDI3LiogAygAKAFgARJaChdVTUEtUG9wdWxhdGlvbi1SZXN0cmljdBiAnJKlBTgBQgdkZWZhdWx0SgsKB2RlZmF1bHQQAEoKCgZub3JtYWwQZEoLCgdkb2dmb29kEABSBhIEMzIuKmABElgKDFVNQVN0YWJpbGl0eRjEyomgBTgBQgtTZXBhcmF0ZUxvZ0oLCgdEZWZhdWx0EABKDwoLU2VwYXJhdGVMb2cQZFIVEgszNC4wLjE4MDEuMCAAIAEgAiADEmEKDFVNQVN0YWJpbGl0eRjEyomgBTgBQgtTZXBhcmF0ZUxvZ0oLCgdEZWZhdWx0EABKDwoLU2VwYXJhdGVMb2cQZFIeEgwzMy4wLjE3NTAuMTEaBDMzLiogACABIAIgAygFEr0BCh9VTUEtVW5pZm9ybWl0eS1UcmlhbC0xMC1QZXJjZW50GICckqUFOAFCB2RlZmF1bHRKCwoHZGVmYXVsdBABSgwKCGdyb3VwXzAxEAFKDAoIZ3JvdXBfMDIQAUoMCghncm91cF8wMxABSgwKCGdyb3VwXzA0EAFKDAoIZ3JvdXBfMDUQAUoMCghncm91cF8wNhABSgwKCGdyb3VwXzA3EAFKDAoIZ3JvdXBfMDgQAUoMCghncm91cF8wORABEqYPCh5VTUEtVW5pZm9ybWl0eS1UcmlhbC0xLVBlcmNlbnQYgJySpQU4AUIHZGVmYXVsdEoQCgdkZWZhdWx0EAEYoLXJAUoRCghncm91cF8wMRABGKG1yQFKEQoIZ3JvdXBfMDIQARiitckBShEKCGdyb3VwXzAzEAEYo7XJAUoRCghncm91cF8wNBABGKS1yQFKEQoIZ3JvdXBfMDUQARiltckBShEKCGdyb3VwXzA2EAEYprXJAUoRCghncm91cF8wNxABGKe1yQFKEQoIZ3JvdXBfMDgQARiotckBShEKCGdyb3VwXzA5EAEYqbXJAUoRCghncm91cF8xMBABGKq1yQFKEQoIZ3JvdXBfMTEQARirtckBShEKCGdyb3VwXzEyEAEYrLXJAUoRCghncm91cF8xMxABGK21yQFKEQoIZ3JvdXBfMTQQARiutckBShEKCGdyb3VwXzE1EAEYr7XJAUoRCghncm91cF8xNhABGLC1yQFKEQoIZ3JvdXBfMTcQARixtckBShEKCGdyb3VwXzE4EAEYsrXJAUoRCghncm91cF8xORABGLO1yQFKEQoIZ3JvdXBfMjAQARi0tckBShEKCGdyb3VwXzIxEAEYtbXJAUoRCghncm91cF8yMhABGLa1yQFKEQoIZ3JvdXBfMjMQARi3tckBShEKCGdyb3VwXzI0EAEYuLXJAUoRCghncm91cF8yNRABGLm1yQFKEQoIZ3JvdXBfMjYQARi6tckBShEKCGdyb3VwXzI3EAEYu7XJAUoRCghncm91cF8yOBABGLy1yQFKEQoIZ3JvdXBfMjkQARi9tckBShEKCGdyb3VwXzMwEAEYvrXJAUoRCghncm91cF8zMRABGL+1yQFKEQoIZ3JvdXBfMzIQARjAtckBShEKCGdyb3VwXzMzEAEYwbXJAUoRCghncm91cF8zNBABGMK1yQFKEQoIZ3JvdXBfMzUQARjDtckBShEKCGdyb3VwXzM2EAEYxLXJAUoRCghncm91cF8zNxABGMW1yQFKEQoIZ3JvdXBfMzgQARjGtckBShEKCGdyb3VwXzM5EAEYx7XJAUoRCghncm91cF80MBABGMi1yQFKEQoIZ3JvdXBfNDEQARjJtckBShEKCGdyb3VwXzQyEAEYyrXJAUoRCghncm91cF80MxABGMu1yQFKEQoIZ3JvdXBfNDQQARjMtckBShEKCGdyb3VwXzQ1EAEYzbXJAUoRCghncm91cF80NhABGM61yQFKEQoIZ3JvdXBfNDcQARjPtckBShEKCGdyb3VwXzQ4EAEY0LXJAUoRCghncm91cF80ORABGNG1yQFKEQoIZ3JvdXBfNTAQARjStckBShEKCGdyb3VwXzUxEAEY07XJAUoRCghncm91cF81MhABGNS1yQFKEQoIZ3JvdXBfNTMQARjVtckBShEKCGdyb3VwXzU0EAEY1rXJAUoRCghncm91cF81NRABGNe1yQFKEQoIZ3JvdXBfNTYQARjYtckBShEKCGdyb3VwXzU3EAEY2bXJAUoRCghncm91cF81OBABGNq1yQFKEQoIZ3JvdXBfNTkQARjbtckBShEKCGdyb3VwXzYwEAEY3LXJAUoRCghncm91cF82MRABGN21yQFKEQoIZ3JvdXBfNjIQARjetckBShEKCGdyb3VwXzYzEAEY37XJAUoRCghncm91cF82NBABGOC1yQFKEQoIZ3JvdXBfNjUQARjhtckBShEKCGdyb3VwXzY2EAEY4rXJAUoRCghncm91cF82NxABGOO1yQFKEQoIZ3JvdXBfNjgQARjktckBShEKCGdyb3VwXzY5EAEY5bXJAUoRCghncm91cF83MBABGOa1yQFKEQoIZ3JvdXBfNzEQARjntckBShEKCGdyb3VwXzcyEAEY6LXJAUoRCghncm91cF83MxABGOm1yQFKEQoIZ3JvdXBfNzQQARjqtckBShEKCGdyb3VwXzc1EAEY67XJAUoRCghncm91cF83NhABGOy1yQFKEQoIZ3JvdXBfNzcQARjttckBShEKCGdyb3VwXzc4EAEY7rXJAUoRCghncm91cF83ORABGO+1yQFKEQoIZ3JvdXBfODAQARjwtckBShEKCGdyb3VwXzgxEAEY8bXJAUoRCghncm91cF84MhABGPK1yQFKEQoIZ3JvdXBfODMQARjztckBShEKCGdyb3VwXzg0EAEY9LXJAUoRCghncm91cF84NRABGPW1yQFKEQoIZ3JvdXBfODYQARj2tckBShEKCGdyb3VwXzg3EAEY97XJAUoRCghncm91cF84OBABGPi1yQFKEQoIZ3JvdXBfODkQARj5tckBShEKCGdyb3VwXzkwEAEY+rXJAUoRCghncm91cF85MRABGPu1yQFKEQoIZ3JvdXBfOTIQARj8tckBShEKCGdyb3VwXzkzEAEY/bXJAUoRCghncm91cF85NBABGP61yQFKEQoIZ3JvdXBfOTUQARj/tckBShEKCGdyb3VwXzk2EAEYgLbJAUoRCghncm91cF85NxABGIG2yQFKEQoIZ3JvdXBfOTgQARiCtskBShEKCGdyb3VwXzk5EAEYg7bJAVIIKAAoASgCKAMSrgsKHlVNQS1Vbmlmb3JtaXR5LVRyaWFsLTEtUGVyY2VudBiAnJKlBTgBQgdkZWZhdWx0SgsKB2RlZmF1bHQQAUoMCghncm91cF8wMRABSgwKCGdyb3VwXzAyEAFKDAoIZ3JvdXBfMDMQAUoMCghncm91cF8wNBABSgwKCGdyb3VwXzA1EAFKDAoIZ3JvdXBfMDYQAUoMCghncm91cF8wNxABSgwKCGdyb3VwXzA4EAFKDAoIZ3JvdXBfMDkQAUoMCghncm91cF8xMBABSgwKCGdyb3VwXzExEAFKDAoIZ3JvdXBfMTIQAUoMCghncm91cF8xMxABSgwKCGdyb3VwXzE0EAFKDAoIZ3JvdXBfMTUQAUoMCghncm91cF8xNhABSgwKCGdyb3VwXzE3EAFKDAoIZ3JvdXBfMTgQAUoMCghncm91cF8xORABSgwKCGdyb3VwXzIwEAFKDAoIZ3JvdXBfMjEQAUoMCghncm91cF8yMhABSgwKCGdyb3VwXzIzEAFKDAoIZ3JvdXBfMjQQAUoMCghncm91cF8yNRABSgwKCGdyb3VwXzI2EAFKDAoIZ3JvdXBfMjcQAUoMCghncm91cF8yOBABSgwKCGdyb3VwXzI5EAFKDAoIZ3JvdXBfMzAQAUoMCghncm91cF8zMRABSgwKCGdyb3VwXzMyEAFKDAoIZ3JvdXBfMzMQAUoMCghncm91cF8zNBABSgwKCGdyb3VwXzM1EAFKDAoIZ3JvdXBfMzYQAUoMCghncm91cF8zNxABSgwKCGdyb3VwXzM4EAFKDAoIZ3JvdXBfMzkQAUoMCghncm91cF80MBABSgwKCGdyb3VwXzQxEAFKDAoIZ3JvdXBfNDIQAUoMCghncm91cF80MxABSgwKCGdyb3VwXzQ0EAFKDAoIZ3JvdXBfNDUQAUoMCghncm91cF80NhABSgwKCGdyb3VwXzQ3EAFKDAoIZ3JvdXBfNDgQAUoMCghncm91cF80ORABSgwKCGdyb3VwXzUwEAFKDAoIZ3JvdXBfNTEQAUoMCghncm91cF81MhABSgwKCGdyb3VwXzUzEAFKDAoIZ3JvdXBfNTQQAUoMCghncm91cF81NRABSgwKCGdyb3VwXzU2EAFKDAoIZ3JvdXBfNTcQAUoMCghncm91cF81OBABSgwKCGdyb3VwXzU5EAFKDAoIZ3JvdXBfNjAQAUoMCghncm91cF82MRABSgwKCGdyb3VwXzYyEAFKDAoIZ3JvdXBfNjMQAUoMCghncm91cF82NBABSgwKCGdyb3VwXzY1EAFKDAoIZ3JvdXBfNjYQAUoMCghncm91cF82NxABSgwKCGdyb3VwXzY4EAFKDAoIZ3JvdXBfNjkQAUoMCghncm91cF83MBABSgwKCGdyb3VwXzcxEAFKDAoIZ3JvdXBfNzIQAUoMCghncm91cF83MxABSgwKCGdyb3VwXzc0EAFKDAoIZ3JvdXBfNzUQAUoMCghncm91cF83NhABSgwKCGdyb3VwXzc3EAFKDAoIZ3JvdXBfNzgQAUoMCghncm91cF83ORABSgwKCGdyb3VwXzgwEAFKDAoIZ3JvdXBfODEQAUoMCghncm91cF84MhABSgwKCGdyb3VwXzgzEAFKDAoIZ3JvdXBfODQQAUoMCghncm91cF84NRABSgwKCGdyb3VwXzg2EAFKDAoIZ3JvdXBfODcQAUoMCghncm91cF84OBABSgwKCGdyb3VwXzg5EAFKDAoIZ3JvdXBfOTAQAUoMCghncm91cF85MRABSgwKCGdyb3VwXzkyEAFKDAoIZ3JvdXBfOTMQAUoMCghncm91cF85NBABSgwKCGdyb3VwXzk1EAFKDAoIZ3JvdXBfOTYQAUoMCghncm91cF85NxABSgwKCGdyb3VwXzk4EAFKDAoIZ3JvdXBfOTkQAVIEKAQoBRKQAQofVU1BLVVuaWZvcm1pdHktVHJpYWwtMjAtUGVyY2VudBiAnJKlBTgBQgdkZWZhdWx0ShAKB2RlZmF1bHQQARiitskBShEKCGdyb3VwXzAxEAEYo7bJAUoRCghncm91cF8wMhABGKS2yQFKEQoIZ3JvdXBfMDMQARiltskBShEKCGdyb3VwXzA0EAEYprbJARJNCh9VTUEtVW5pZm9ybWl0eS1UcmlhbC01MC1QZXJjZW50GICckqUFOAFCB2RlZmF1bHRKCwoHZGVmYXVsdBABSgwKCGdyb3VwXzAxEAEStgMKHlVNQS1Vbmlmb3JtaXR5LVRyaWFsLTUtUGVyY2VudBiAnJKlBTgBQgdkZWZhdWx0ShAKB2RlZmF1bHQQARiEtskBShEKCGdyb3VwXzAxEAEYhbbJAUoRCghncm91cF8wMhABGIa2yQFKEQoIZ3JvdXBfMDMQARiHtskBShEKCGdyb3VwXzA0EAEYiLbJAUoRCghncm91cF8wNRABGIm2yQFKEQoIZ3JvdXBfMDYQARiKtskBShEKCGdyb3VwXzA3EAEYi7bJAUoRCghncm91cF8wOBABGIy2yQFKEQoIZ3JvdXBfMDkQARiNtskBShEKCGdyb3VwXzEwEAEYjrbJAUoRCghncm91cF8xMRABGI+2yQFKEQoIZ3JvdXBfMTIQARiQtskBShEKCGdyb3VwXzEzEAEYkbbJAUoRCghncm91cF8xNBABGJK2yQFKEQoIZ3JvdXBfMTUQARiTtskBShEKCGdyb3VwXzE2EAEYlLbJAUoRCghncm91cF8xNxABGJW2yQFKEQoIZ3JvdXBfMTgQARiWtskBShEKCGdyb3VwXzE5EAEYl7bJAVIIKAAoASgCKAMSzgIKHlVNQS1Vbmlmb3JtaXR5LVRyaWFsLTUtUGVyY2VudBiAnJKlBTgBQgdkZWZhdWx0SgsKB2RlZmF1bHQQAUoMCghncm91cF8wMRABSgwKCGdyb3VwXzAyEAFKDAoIZ3JvdXBfMDMQAUoMCghncm91cF8wNBABSgwKCGdyb3VwXzA1EAFKDAoIZ3JvdXBfMDYQAUoMCghncm91cF8wNxABSgwKCGdyb3VwXzA4EAFKDAoIZ3JvdXBfMDkQAUoMCghncm91cF8xMBABSgwKCGdyb3VwXzExEAFKDAoIZ3JvdXBfMTIQAUoMCghncm91cF8xMxABSgwKCGdyb3VwXzE0EAFKDAoIZ3JvdXBfMTUQAUoMCghncm91cF8xNhABSgwKCGdyb3VwXzE3EAFKDAoIZ3JvdXBfMTgQAUoMCghncm91cF8xORABUgQoBCgFElEKDFZvaWNlVHJpZ2dlchjEvpelBTgBQghEaXNhYmxlZEoLCgdJbnN0YWxsEGRKDAoIRGlzYWJsZWQQAFIUEgQzNC4qIAAgASACKAAoASgCKAMSgAEKFldlYkdMRGVidWdSZW5kZXJlckluZm8YxILomQU4AUIHZW5hYmxlZEoQCgdjb250cm9sEAAYxpLKAUoRCghkaXNhYmxlZBAAGMeSygFKEAoHZW5hYmxlZBBkGMWSygFSHhIEMzEuKhoEMzIuKiADIAIgACABKAAoASgDKAIoBBJzChlXZWJzdG9yZURvd25sb2FkRGlyZWN0b3J5GMT8rpwFOAFCB0RlZmF1bHRKHQoZU2VwYXJhdGVEaXJlY3RvcnlVbmRlclVERBAKSgsKB0NvbnRyb2wQCkoLCgdEZWZhdWx0EFBSDCAAIAEgAigAKAEoAhJvChlXZWJzdG9yZURvd25sb2FkRGlyZWN0b3J5GMT8rpwFOAFCB0RlZmF1bHRKHQoZU2VwYXJhdGVEaXJlY3RvcnlVbmRlclVERBABSgsKB0NvbnRyb2wQAUoLCgdEZWZhdWx0EGJSCCADKAAoASgC", + "variations_seed_signature": "MEUCIQDWcDJZZ+Hi3eyyoOGUf5R5Fp0h5+28JIYT9F3s1upVxQIgdCbBPpn0KHHhZXtRQ31EIbyfyCW6mQAD779yy38VIeM=" +} \ No newline at end of file diff --git a/examples/MultiTree.nsi b/examples/MultiTree.nsi new file mode 100644 index 0000000..020944d --- /dev/null +++ b/examples/MultiTree.nsi @@ -0,0 +1,26 @@ +!include MUI2.nsh + +Name `nsJSON plug-in` +OutFile nsJSON_MultiTree.exe +RequestExecutionLevel user +ShowInstDetails show + +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_LANGUAGE English + +Section + + CreateDirectory $EXEDIR\Output + + DetailPrint `Read: $EXEDIR\Input\Example1.json into Example1` + nsJSON::Set /tree Example1 /file $EXEDIR\Input\Example1.json + DetailPrint `Read: $EXEDIR\Input\Example2.json into Example2` + nsJSON::Set /tree Example2 /file $EXEDIR\Input\Example2.json + + DetailPrint `Generate: $EXEDIR\Output\Example2.json from Example2` + nsJSON::Serialize /tree Example2 /format /file $EXEDIR\Output\Example2.json + DetailPrint `Generate: $EXEDIR\Output\Example1.json from Example1` + nsJSON::Serialize /tree Example1 /format /file $EXEDIR\Output\Example1.json + +SectionEnd \ No newline at end of file diff --git a/examples/Quote.nsi b/examples/Quote.nsi new file mode 100644 index 0000000..8ca0900 --- /dev/null +++ b/examples/Quote.nsi @@ -0,0 +1,46 @@ +!include MUI2.nsh + +Name `nsJSON plug-in` +OutFile nsJSON_Quote.exe +RequestExecutionLevel user +ShowInstDetails show + +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_LANGUAGE English + +!macro nJSON_Quote_Test Unicode Always Input + !if `${Always}` == true + nsJSON::Quote /always `${Input}` + !else if `${Unicode}` == true + nsJSON::Quote /unicode `${Input}` + !else + nsJSON::Quote `${Input}` + !endif + Pop $R0 + !if `${Always}` == true + DetailPrint `${Input} -> $R0 (/always)` + !else if `${Unicode}` == true + DetailPrint `${Input} -> $R0 (/unicode)` + !else + DetailPrint `${Input} -> $R0` + !endif +!macroend +!define nJSON_Quote_Test `!insertmacro nJSON_Quote_Test false false` +!define nJSON_Quote_Test_Unicode `!insertmacro nJSON_Quote_Test true false` +!define nJSON_Quote_Test_Always `!insertmacro nJSON_Quote_Test false true` + +Section + + ${nJSON_Quote_Test} `"` + ${nJSON_Quote_Test} `\` + ${nJSON_Quote_Test} `` + ${nJSON_Quote_Test} `` + ${nJSON_Quote_Test} `""` + ${nJSON_Quote_Test} `"some"text"` + ${nJSON_Quote_Test_Always} `"some"text"` + ${nJSON_Quote_Test_Unicode} `` + ${nJSON_Quote_Test_Unicode} `` + ${nJSON_Quote_Test_Unicode} `""` + +SectionEnd \ No newline at end of file diff --git a/examples/Sort.nsi b/examples/Sort.nsi new file mode 100644 index 0000000..4c1adf0 --- /dev/null +++ b/examples/Sort.nsi @@ -0,0 +1,36 @@ +!include MUI2.nsh + +Name `nsJSON plug-in` +OutFile nsJSON_Sort.exe +RequestExecutionLevel user +ShowInstDetails show + +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_LANGUAGE English + +!macro nJSON_Sort_Test Options + nsJSON::Sort /options ${Options} /end + nsJSON::Get /end + Pop $R0 + DetailPrint $R0 +!macroend +!define nJSON_Sort_Test `!insertmacro nJSON_Sort_Test` + +Section + + nsJSON::Set /value `{ "D": "X", "b": 3, "a": 22, "c" : 2, "d": "x", "y": { "f": 33, "a": 9, "n": [ 1, 5, -10, 11, "m" ] } }` /end + + DetailPrint `Sorted root node values only` + ${nJSON_Sort_Test} 0 + + DetailPrint `Sorted root node values only numerically` + ${nJSON_Sort_Test} 2 + + DetailPrint `Sorted root node by keys only` + ${nJSON_Sort_Test} 8 + + DetailPrint `Sorted values numerically + recursively` + ${nJSON_Sort_Test} 18 + +SectionEnd \ No newline at end of file diff --git a/examples/Syntax.nsi b/examples/Syntax.nsi new file mode 100644 index 0000000..8a35eb3 --- /dev/null +++ b/examples/Syntax.nsi @@ -0,0 +1,35 @@ +!include MUI2.nsh + +Name `nsJSON plug-in` +OutFile nsJSON_Syntax.exe +RequestExecutionLevel user +ShowInstDetails show + +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_LANGUAGE English + +!macro DoTest JSON Description + StrCpy $R0 `` + ClearErrors + nsJSON::Set /value `${JSON}` + nsJSON::Serialize + Pop $R0 + DetailPrint `${Description}:` + DetailPrint `${JSON} -> $R0` + IfErrors 0 +2 + DetailPrint `Error flag is set!` + DetailPrint `` +!macroend + +Section + + !insertmacro DoTest `{ "Input": [ { "test1": false, } ] }` `Trailing comma` + + !insertmacro DoTest `{ "Input": [ { "test1": false } .? ] }` `Junk characters` + + !insertmacro DoTest `{ "Input": [ { "test1": false } }` `Missing square bracket` + + !insertmacro DoTest `{ "Input": [ { "test1": false ] }` `Missing curly brace` + +SectionEnd \ No newline at end of file diff --git a/examples/UnicodeNSIS.nsi b/examples/UnicodeNSIS.nsi new file mode 100644 index 0000000..479da30 --- /dev/null +++ b/examples/UnicodeNSIS.nsi @@ -0,0 +1,25 @@ +!include MUI2.nsh + +Name `nsJSON plug-in` +OutFile nsJSON_UnicodeNSIS.exe +RequestExecutionLevel user +ShowInstDetails show +Unicode true + +!insertmacro MUI_PAGE_INSTFILES + +!insertmacro MUI_LANGUAGE English + +Section + + nsJSON::Set /tree testTree testNode /value testValue + + nsJSON::Get /tree testTree testNode /end + Pop $R0 + DetailPrint $R0 + + nsJSON::Serialize /tree testTree + Pop $R0 + DetailPrint $R0 + +SectionEnd \ No newline at end of file diff --git a/src/ConsoleApp/ConsoleApp.vcxproj b/src/ConsoleApp/ConsoleApp.vcxproj new file mode 100644 index 0000000..4cccade --- /dev/null +++ b/src/ConsoleApp/ConsoleApp.vcxproj @@ -0,0 +1,141 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D} + ConsoleApp + 8.1 + + + + Application + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + Application + true + v140 + MultiByte + + + Application + false + v140 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + false + + + false + + + + Level3 + Disabled + true + + + + + Debug + + + + + Level3 + Disabled + true + + + + + Debug + + + + + Level3 + MaxSpeed + true + true + false + + + true + true + No + true + libc.lib + main + + + + + Level3 + MaxSpeed + true + true + false + + + true + true + No + true + libc.lib + main + + + + + + + + + \ No newline at end of file diff --git a/src/ConsoleApp/ConsoleApp.vcxproj.filters b/src/ConsoleApp/ConsoleApp.vcxproj.filters new file mode 100644 index 0000000..6827613 --- /dev/null +++ b/src/ConsoleApp/ConsoleApp.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/src/ConsoleApp/main.c b/src/ConsoleApp/main.c new file mode 100644 index 0000000..6aaf1df --- /dev/null +++ b/src/ConsoleApp/main.c @@ -0,0 +1,30 @@ +#include + +int main() +{ + CHAR szInBuffer[32], szOutBuffer[128]; + HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE), hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); + DWORD dwBytesRead, dwBytesWritten; + int i, cchOutBuffer; + + if (ReadFile(hStdIn, szInBuffer, sizeof(szInBuffer), &dwBytesRead, NULL) && dwBytesRead) + { + for (i = lstrlenA(szInBuffer) - 1; i >= 0 && (szInBuffer[i] == '\n' || szInBuffer[i] == '\r' || szInBuffer[i] == '\t' || szInBuffer[i] == ' '); i--) + szInBuffer[i] = '\0'; + + // Pretend to do some work. + Sleep(5000); + + // Return JSON. + //cchOutBuffer = wsprintfA(szOutBuffer, "{\"Input\": \"%s\", \"Output\": \"blah!\"}", szInBuffer); + + // Or return a string. + lstrcpyA(szOutBuffer, "blah!!!!"); + cchOutBuffer = lstrlenA(szOutBuffer); + + WriteFile(hStdOut, szOutBuffer, cchOutBuffer, &dwBytesWritten, NULL); + } + + ExitProcess(0); + return 0; +} \ No newline at end of file diff --git a/src/ConsoleAppDotNet/ConsoleAppDotNet.csproj b/src/ConsoleAppDotNet/ConsoleAppDotNet.csproj new file mode 100644 index 0000000..57aa371 --- /dev/null +++ b/src/ConsoleAppDotNet/ConsoleAppDotNet.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8} + Exe + Properties + ConsoleAppDotNet + ConsoleAppDotNet + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ConsoleAppDotNet/Program.cs b/src/ConsoleAppDotNet/Program.cs new file mode 100644 index 0000000..611c9b5 --- /dev/null +++ b/src/ConsoleAppDotNet/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading; + +namespace ConsoleAppDotNet +{ + class Program + { + static void Main() + { + string input = Console.ReadLine(); + if (input == null) + return; + + input = input.Trim(); + if (input == string.Empty) + return; + + // Pretend to do some work. + Thread.Sleep(TimeSpan.FromSeconds(5)); + + // Return JSON. + Console.Write("{{\"Input\": \"{0}\", \"Output\":\"blah!\"}}", input); + } + } +} diff --git a/src/ConsoleAppDotNet/Properties/AssemblyInfo.cs b/src/ConsoleAppDotNet/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c6d08d1 --- /dev/null +++ b/src/ConsoleAppDotNet/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ConsoleAppDotNet")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ConsoleAppDotNet")] +[assembly: AssemblyCopyright("Copyright © Stuart Welch 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("dbaa5865-5ef1-4a17-bc19-b68d5e5c13f8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/JSON.c b/src/JSON.c new file mode 100644 index 0000000..d8aa257 --- /dev/null +++ b/src/JSON.c @@ -0,0 +1,1542 @@ +/* + JSON parser & writer by Stuart Welch + v1.1.1.1 - 10th October 2024 +*/ + +#include +#include "JSON.h" +#include "pluginapi.h" + +#define IsWhitespace(c) c == TEXT(' ') || c == TEXT('\t') || c == TEXT('\r') || c == TEXT('\n') +#define ntohs(n) (((((unsigned short)(n) & 0xFF)) << 8) | (((unsigned short)(n) & 0xFF00) >> 8)) +static TCHAR hex[] = TEXT("0123456789abcdef"); + +struct JSON_NODE *JSON_Create() +{ + return (struct JSON_NODE *)GlobalAlloc(GPTR, sizeof(struct JSON_NODE)); +} + +PTCHAR JSON_GetQuotedValue(struct JSON_NODE *pNode, const PTCHAR pszDefaultValue) +{ + return pNode && pNode->eType == JNT_QUOTED_VALUE ? pNode->pszValue : pszDefaultValue; +} + +BOOL JSON_IsTrue(struct JSON_NODE *pNode) +{ + if (!pNode || !pNode->pszValue || lstrcmp(pNode->pszValue, TEXT("0")) == 0 || lstrcmpi(pNode->pszValue, TEXT("false")) == 0) + return FALSE; + return TRUE; +} + +static void EatWhitespace(PTCHAR pszBuffer, int *piPos) +{ + while (IsWhitespace(pszBuffer[*piPos])) + (*piPos)++; +} + +static PTCHAR MakeCopy(PTCHAR pszCopyFrom, int iStart, int iEnd) +{ + PTCHAR pszCopy = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * (iEnd - iStart + 2)); + if (pszCopy) + { + int i = 0; + for (i = 0; iStart <= iEnd; i++, iStart++) + pszCopy[i] = pszCopyFrom[iStart]; + } + + return pszCopy; +} + +static BOOL IsChar(PTCHAR pszBuffer, int *piPos, TCHAR chExpected) +{ + EatWhitespace(pszBuffer, piPos); + + if (pszBuffer[*piPos] != chExpected) + return FALSE; + + return TRUE; +} + +/*static BOOL IsNumeric(PTCHAR pszBuffer) +{ + int cch = lstrlen(pszBuffer), i; + if (cch == 0) + return FALSE; + + if (pszBuffer[0] == TEXT('0')) + { + if (cch == 1) + return TRUE; + return FALSE; + } + + for (i = 0; i < cch; i++) + if (pszBuffer[i] < TEXT('0') || pszBuffer[i] > TEXT('9')) + return FALSE; + + return TRUE; +}*/ + +static BOOL EatChar(PTCHAR pszBuffer, int *piPos, TCHAR chExpected) +{ + if (!IsChar(pszBuffer, piPos, chExpected)) + return FALSE; + + (*piPos)++; + return TRUE; +} + +static BOOL IsEscaped(PTCHAR pszBuffer, int iPos) +{ + return iPos > 0 && pszBuffer[iPos - 1] == TEXT('\\') && !IsEscaped(pszBuffer, iPos - 1); +} + +static enum JSON_WORD_TYPE EatWord(PTCHAR pszBuffer, int *piPos, PTCHAR *ppszWord) +{ + int iStart, iEnd; + + EatWhitespace(pszBuffer, piPos); + + iStart = *piPos; + iEnd = 0; + *ppszWord = NULL; + + if (pszBuffer[*piPos] == TEXT('"')) + { + iStart++; + + while (TRUE) + { + (*piPos)++; + if (pszBuffer[*piPos] == TEXT('"') && !IsEscaped(pszBuffer, *piPos) || pszBuffer[*piPos] == 0) + { + iEnd = *piPos - 1; + if (pszBuffer[*piPos] == TEXT('"')) + (*piPos)++; + break; + } + } + + *ppszWord = MakeCopy(pszBuffer, iStart, iEnd); + return JWT_STRING; + } + + while (TRUE) + { + if (pszBuffer[*piPos] == TEXT(':') || pszBuffer[*piPos] == TEXT(',') || pszBuffer[*piPos] == TEXT('}') || pszBuffer[*piPos] == TEXT(']') || IsWhitespace(pszBuffer[*piPos]) || pszBuffer[*piPos] == 0) + { + iEnd = *piPos - 1; + break; + } + (*piPos)++; + } + + if (iStart <= iEnd) + { + *ppszWord = MakeCopy(pszBuffer, iStart, iEnd); + return JWT_OTHER; + } + + return JWT_NONE; +} + +static struct JSON_NODE *EatNode(PTCHAR pszBuffer, int *piPos, BOOL bIsValue); + +static struct JSON_NODE *EatNodeArray(PTCHAR pszBuffer, int *piPos) +{ + struct JSON_NODE *pNode = EatNode(pszBuffer, piPos, TRUE); + if (pNode && EatChar(pszBuffer, piPos, TEXT(','))) + pNode->pNext = EatNodeArray(pszBuffer, piPos); + return pNode; +} + +static struct JSON_NODE *EatNode(PTCHAR pszBuffer, int *piPos, BOOL bIsValue) +{ + struct JSON_NODE *pNode = JSON_Create(); + + if (pNode) + { + if (EatChar(pszBuffer, piPos, TEXT('{'))) + { + pNode->eType = JNT_NODE; + pNode->pValue = EatNode(pszBuffer, piPos, FALSE); + if (!pNode->pValue || !EatChar(pszBuffer, piPos, TEXT('}'))) + { + JSON_Delete(&pNode, NULL); + return NULL; + } + } + else if (EatChar(pszBuffer, piPos, TEXT('['))) + { + pNode->eType = JNT_ARRAY; + pNode->pValue = EatNodeArray(pszBuffer, piPos); + // Allow empty arrays: pValue can be NULL + if (!EatChar(pszBuffer, piPos, TEXT(']'))) + { + JSON_Delete(&pNode, NULL); + return NULL; + } + } + else + { + PTCHAR pszValue = NULL; + enum JSON_WORD_TYPE eType = EatWord(pszBuffer, piPos, &pszValue); + + if (pszValue) + { + switch (eType) + { + case JWT_STRING: + pNode->eType = JNT_QUOTED_VALUE; + break; + case JWT_OTHER: + pNode->eType = JNT_VALUE; + break; + } + + if (eType != JWT_NONE) + { + // Node is just a value. + if (bIsValue) + { + pNode->pszValue = pszValue; + } + // Node is a key: value pair. + else if (EatChar(pszBuffer, piPos, TEXT(':'))) + { + struct JSON_NODE *pChildNode = EatNode(pszBuffer, piPos, TRUE); + if (!pChildNode) + { + GlobalFree(pNode); + return NULL; + } + + pNode->eType = pChildNode->eType; + pNode->pNext = pChildNode->pNext; + pNode->pValue = pChildNode->pValue; + GlobalFree(pChildNode); + + pNode->pszKey = pszValue; + } + // No key was given; use an empty string. + else + { + pNode->pszKey = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR)); + pNode->pszValue = pszValue; + } + } + } + else + { + GlobalFree(pNode); + return NULL; + } + } + + // Commas are allowed; eat next node. + if (!bIsValue && EatChar(pszBuffer, piPos, TEXT(','))) + { + pNode->pNext = EatNode(pszBuffer, piPos, FALSE); + if (pNode->pNext == NULL) + { + GlobalFree(pNode); + return NULL; + } + } + } + + return pNode; +} + +static struct JSON_NODE *EatRoot(PTCHAR pszBuffer) +{ + int iPos = 0; + return EatNode(pszBuffer, &iPos, FALSE); +} + +static PTCHAR EscapeQuotes(PTCHAR pszStr) +{ + int nQuotes = 0, i, cchLen = lstrlen(pszStr); + PTCHAR pszStrNew; + + for (i = 0; i < cchLen; i++) + if (pszStr[i] == TEXT('"')) + nQuotes++; + + pszStrNew = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * (cchLen + nQuotes + 1)); + if (pszStrNew) + { + int j; + + for (i = 0, j = 0; i < cchLen; i++, j++) + { + if (pszStr[i] == TEXT('"') && !IsEscaped(pszStr, i)) + { + pszStrNew[j] = TEXT('\\'); + j++; + } + + pszStrNew[j] = pszStr[i]; + } + } + + return pszStrNew; +} + +void JSON_Delete(struct JSON_NODE **ppNode, struct JSON_NODE *pPrev) +{ + if (ppNode && *ppNode) + { + struct JSON_NODE *pNext = *ppNode; + + if (pPrev) + { + if (pPrev->pNext == *ppNode) + pPrev->pNext = (*ppNode)->pNext; + + if ((pPrev->eType == JNT_NODE || pPrev->eType == JNT_ARRAY) && pPrev->pValue == *ppNode) + pPrev->pValue = (*ppNode)->pNext; + } + + while (pNext) + { + struct JSON_NODE *pNextNext = pNext->pNext; + + if (pNext->pszKey) + GlobalFree(pNext->pszKey); + + switch (pNext->eType) + { + case JNT_NODE: + case JNT_ARRAY: + + if (pNext->pValue) + JSON_Delete(&pNext->pValue, NULL); + + break; + case JNT_VALUE: + case JNT_QUOTED_VALUE: + + GlobalFree(pNext->pszValue); + + break; + } + + GlobalFree(pNext); + + if (pNext == *ppNode) + *ppNode = NULL; + + if (pPrev) + break; + + pNext = pNextNext; + } + } +} + +static PCHAR HandleUTF8Input(PBYTE pbBuffer, int *pcbBuffer) +{ + if (pbBuffer[0] == 0xEF && pbBuffer[1] == 0xBB && pbBuffer[2] == 0xBF) // UTF-8 sig + { + if (pcbBuffer) + *pcbBuffer -= 3; + return (PCHAR)(pbBuffer + 3); + } + + return (PCHAR)pbBuffer; +} + +static PWCHAR HandleUTF16Input(PBYTE pbBuffer, int *pcchBuffer) +{ + if (pbBuffer[0] == 0xFE && pbBuffer[1] == 0xFF) // UTF-16BE BOM + { + int i; + PWCHAR pwszBuffer = (PWCHAR)(pbBuffer + 2); + for (i = 0; pwszBuffer[i] != L'\0'; i++) + pwszBuffer[i] = ntohs(pwszBuffer[i]); + if (pcchBuffer) + *pcchBuffer -= 2; + return pwszBuffer; + } + + if (pbBuffer[0] == 0xFF && pbBuffer[1] == 0xFE && (pbBuffer[2] != 0x00 || pbBuffer[3] != 0x00)) // UTF-16LE BOM + { + if (pcchBuffer) + *pcchBuffer -= 2; + return (PWCHAR)(pbBuffer + 2); + } + + return (PWCHAR)pbBuffer; +} + +static struct JSON_NODE *Parse(PBYTE pbSource, enum JSON_SET_FLAGS eFlags) +{ + struct JSON_NODE *pNode = NULL; + PBYTE pbBuffer = NULL; + DWORD dwSize = 0; + + if (eFlags & JSF_IS_FILE) + { + HANDLE hFile = CreateFile((PTCHAR)pbSource, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + dwSize = GetFileSize(hFile, NULL); + if (dwSize > 0) + { + pbBuffer = (PBYTE)GlobalAlloc(GPTR, dwSize + ((eFlags & JSF_IS_UNICODE) ? sizeof(WCHAR) : sizeof(CHAR))); + if (pbBuffer) + ReadFile(hFile, pbBuffer, dwSize, &dwSize, NULL); + } + CloseHandle(hFile); + } + } + else + { + pbBuffer = pbSource; + dwSize = (eFlags & JSF_IS_UNICODE) ? lstrlenW((PWCHAR)pbSource) : lstrlenA((PCHAR)pbSource); + } + + if (pbBuffer) + { +#ifdef UNICODE + if (eFlags & JSF_IS_UNICODE) + { + pNode = EatRoot(HandleUTF16Input(pbBuffer, NULL)); + } + else + { + int cbBuffer = dwSize; + PWCHAR pwszBuffer = JSON_ToUnicode(HandleUTF8Input(pbBuffer, &cbBuffer), &cbBuffer); + if (pwszBuffer) + { + pNode = EatRoot(pwszBuffer); + GlobalFree(pwszBuffer); + } + } +#else + if (eFlags & JSF_IS_UNICODE) + { + int cchBuffer = dwSize / sizeof(WCHAR); + PCHAR pszBuffer = JSON_FromUnicode(HandleUTF16Input(pbBuffer, &cchBuffer), &cchBuffer, CP_ACP); + if (pszBuffer) + { + pNode = EatRoot(pszBuffer); + GlobalFree(pszBuffer); + } + } + else + { + pNode = EatRoot(HandleUTF8Input(pbBuffer, NULL)); + } +#endif + + if (eFlags & JSF_IS_FILE) + GlobalFree(pbBuffer); + } + + return pNode; +} + +int JSON_Count(struct JSON_NODE *pNode) +{ + int i = 0; + + if (pNode) + { + pNode = pNode->pValue; + for (; pNode != NULL; i++) + pNode = pNode->pNext; + } + + return i; +} + +struct JSON_NODE *JSON_Get(struct JSON_NODE *pNode, PTCHAR pszKey, BOOL bKeyIsIndex) +{ + return JSON_Next(&pNode, pszKey, bKeyIsIndex, FALSE, NULL); +} + +struct JSON_NODE *JSON_GetEx(struct JSON_NODE *pNode, PTCHAR pszKey, BOOL bKeyIsIndex, BOOL bCreate, BOOL *pbCreated) +{ + return JSON_Next(&pNode, pszKey, bKeyIsIndex, bCreate, pbCreated); +} + +struct JSON_NODE *JSON_Next(struct JSON_NODE **ppNode, PTCHAR pszKey, BOOL bKeyIsIndex, BOOL bCreate, BOOL *pbCreated) +{ + if (pbCreated) + *pbCreated = FALSE; + + if (ppNode) + { + struct JSON_NODE *pParent = *ppNode; + struct JSON_NODE *pNext = NULL; + + pszKey = EscapeQuotes(pszKey); + if (pszKey) + { + // We can only get a child node if the parent is also a node or array. + if (pParent->eType == JNT_NODE || pParent->eType == JNT_ARRAY) + { + pNext = pParent->pValue; + + // Get the child node by index. + if (bKeyIsIndex) + { + int i, j = myatoi(pszKey); + + // Negative index? + if (j < 0) + j = JSON_Count(pParent) + j; + + for (i = 0; i < j && pNext != NULL; i++) + { + *ppNode = pNext; + pNext = pNext->pNext; + } + } + // Get the child node by key. + else if (pParent->eType == JNT_NODE) + { + while (pNext != NULL && !(!pNext->pszKey && !*pszKey || lstrcmp(pNext->pszKey, pszKey) == 0)) + { + *ppNode = pNext; + pNext = pNext->pNext; + } + } + // Seek to the end of the array if we're going to add a new node element. + else if (bCreate) + { + while (pNext != NULL) + { + *ppNode = pNext; + pNext = pNext->pNext; + } + } + // Otherwise find an array element that matches. + else + { + while (pNext != NULL) + { + if ((pNext->eType == JNT_VALUE || pNext->eType == JNT_QUOTED_VALUE) && lstrcmp(pNext->pszValue, pszKey) == 0) + break; + + *ppNode = pNext; + pNext = pNext->pNext; + } + } + + // No existing child node found and the caller wants to create one. + if (!pNext && bCreate) + { + struct JSON_NODE *pNew = (struct JSON_NODE *)GlobalAlloc(GPTR, sizeof(struct JSON_NODE)); + pNew->eType = JNT_NODE; + + if (pbCreated) + *pbCreated = TRUE; + + // Adding a new node to an array. + if (pParent->eType == JNT_ARRAY && !bKeyIsIndex) + { + pNew->pValue = (struct JSON_NODE *)GlobalAlloc(GPTR, sizeof(struct JSON_NODE)); + pNew->pValue->eType = JNT_NODE; + + if (*pszKey) + { + pNew->pValue->pszKey = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * (lstrlen(pszKey) + 1)); + if (pNew->pValue->pszKey) + lstrcpy(pNew->pValue->pszKey, pszKey); + } + + if (*ppNode != pParent) + (*ppNode)->pNext = pNew; + else + pParent->pValue = pNew; + + pNext = pNew->pValue; + } + // Change parent to an array. + else if (pParent->eType == JNT_NODE && bKeyIsIndex) + { + pParent->eType = JNT_ARRAY; + + if (*ppNode != pParent) + (*ppNode)->pNext = pNew; + else + pParent->pValue = pNew; + + pNext = pNew; + } + // Adding a new node to a node. + else + { + if (!bKeyIsIndex) + { + if (*pszKey) + { + pNew->pszKey = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * (lstrlen(pszKey) + 1)); + if (pNew->pszKey) + lstrcpy(pNew->pszKey, pszKey); + } + else + { + pNew->pszKey = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR)); + } + } + + if (*ppNode != pParent) + (*ppNode)->pNext = pNew; + else + pParent->pValue = pNew; + + pNext = pNew; + } + } + } + + GlobalFree(pszKey); + } + + return pNext; + } + + return NULL; +} + +BOOL JSON_Set(struct JSON_NODE *pNode, PBYTE pbValue, enum JSON_SET_FLAGS eFlags) +{ + if (pNode) + { + if (eFlags & JSF_IS_RAW) + { + // We are overwriting the node's value. + switch (pNode->eType) + { + case JNT_NODE: + case JNT_ARRAY: + + if (pNode->pValue) + JSON_Delete(&pNode->pValue, NULL); + + break; + case JNT_VALUE: + case JNT_QUOTED_VALUE: + + if (pNode->pszValue) + GlobalFree(pNode->pszValue); + + break; + } + + pNode->eType = JNT_QUOTED_VALUE; + +#ifdef UNICODE + if (eFlags & JSF_IS_UNICODE) + { + pNode->pszValue = JSON_Escape((PWCHAR)pbValue, JEF_NONE); + } + else + { + int cchValue = lstrlenA((PCHAR)pbValue); + PWCHAR pwszConverted = JSON_ToUnicode((PCHAR)pbValue, &cchValue); + if (pwszConverted) + { + pNode->pszValue = JSON_Escape(pwszConverted, JEF_NONE); + GlobalFree(pwszConverted); + } + else + { + pNode->pszValue = NULL; + } + } +#else + if (eFlags & JSF_IS_UNICODE) + { + int cchValue = lstrlenW((PWCHAR)pbValue); + PCHAR pszConverted = JSON_FromUnicode((PWCHAR)pbValue, &cchValue, CP_ACP); + if (pszConverted) + { + pNode->pszValue = JSON_Escape(pszConverted, JEF_NONE); + GlobalFree(pszConverted); + } + else + { + pNode->pszValue = NULL; + } + } + else + { + pNode->pszValue = JSON_Escape((PCHAR)pbValue, JEF_NONE); + } +#endif + } + else + { + struct JSON_NODE *pChildNode = Parse(pbValue, eFlags); + if (!pChildNode) + return FALSE; + + switch (pNode->eType) + { + case JNT_NODE: + + // We are overwriting the node's value. + if (pNode->pValue) + JSON_Delete(&pNode->pValue, NULL); + + switch (pChildNode->eType) + { + case JNT_NODE: + case JNT_ARRAY: + + // Child node is a key: value pair; set it as the parent node's value. + if (pChildNode->pszKey) + { + pNode->eType = JNT_NODE; + pNode->pValue = pChildNode; + } + // Child node is a node without a key; overwrite the parent node's value with the child node's value. + else + { + pNode->eType = pChildNode->eType; + pNode->pValue = pChildNode->pValue; + GlobalFree(pChildNode); + } + + break; + default: + + // Replace the node's value. + pNode->eType = pChildNode->eType; + pNode->pszValue = pChildNode->pszValue; + GlobalFree(pChildNode); + + break; + } + + break; + case JNT_ARRAY: + + if (pNode->pValue) + { + struct JSON_NODE *pArrayNode = pNode->pValue; + + // Seek to the end of the array and then append to the end. + while (TRUE) + { + if (pArrayNode->pNext == NULL) + break; + + pArrayNode = pArrayNode->pNext; + } + + pArrayNode->pNext = pChildNode; + } + // Replacing node. + else + { + pNode->eType = pChildNode->eType; + pNode->pValue = pChildNode->pValue; + GlobalFree(pChildNode); + } + + break; + case JNT_VALUE: + case JNT_QUOTED_VALUE: + + // We are overwriting the node's value. + if (pNode->pszValue) + GlobalFree(pNode->pszValue); + + switch (pChildNode->eType) + { + case JNT_NODE: + case JNT_ARRAY: + + // Child node is a key: value pair; set it as the parent node's value. + if (pChildNode->pszKey) + { + pNode->eType = pChildNode->eType; + pNode->pValue = pChildNode; + } + // Child node is a node without a key; overwrite the parent node's value with the child node's value. + else + { + pNode->eType = pChildNode->eType; + pNode->pValue = pChildNode->pValue; + GlobalFree(pChildNode); + } + + break; + default: + + // Replace the node's value. + pNode->eType = pChildNode->eType; + pNode->pszValue = pChildNode->pszValue; + GlobalFree(pChildNode); + + break; + } + + break; + } + } + } + + return TRUE; +} + +BOOL JSON_SetEx(struct JSON_NODE *pNode, PTCHAR pszKey, BOOL bKeyIsIndex, PBYTE pbValue, enum JSON_SET_FLAGS eFlags) +{ + struct JSON_NODE *pNew = JSON_Next(&pNode, pszKey, bKeyIsIndex, TRUE, NULL); + if (pNew) + return JSON_Set(pNew, pbValue, eFlags); + return FALSE; +} + +static void MyWriteFile(HANDLE hFile, PTCHAR pszText, int cchText, BOOL bAsUnicode) +{ + DWORD dwBytes; + +#ifdef UNICODE + if (bAsUnicode) + { + WriteFile(hFile, pszText, sizeof(WCHAR) * cchText, &dwBytes, NULL); + } + else + { + PCHAR pszConverted = JSON_FromUnicode(pszText, &cchText, CP_ACP); + if (pszConverted) + { + WriteFile(hFile, pszConverted, cchText, &dwBytes, NULL); + GlobalFree(pszConverted); + } + } +#else + if (bAsUnicode) + { + PWCHAR pwszConverted = JSON_ToUnicode(pszText, &cchText); + if (pwszConverted) + { + WriteFile(hFile, pwszConverted, cchText, &dwBytes, NULL); + GlobalFree(pwszConverted); + } + } + else + { + WriteFile(hFile, pszText, cchText, &dwBytes, NULL); + } +#endif +} + +static void MyWriteFileEx(HANDLE hFile, PTCHAR pszText, int cchText, BOOL bAsUnicode, int iRepeat) +{ + int i = 0; + + for (; i < iRepeat; i++) + { + MyWriteFile(hFile, pszText, cchText, bAsUnicode); + } +} + +static void SerializeToFile(HANDLE hFile, struct JSON_NODE *pNode, int iIndent, BOOL bAsUnicode) +{ + if (!pNode) + return; + + if (pNode->pszKey) + { + MyWriteFile(hFile, TEXT("\""), 1, bAsUnicode); + MyWriteFile(hFile, pNode->pszKey, lstrlen(pNode->pszKey), bAsUnicode); + MyWriteFile(hFile, TEXT("\":"), 2, bAsUnicode); + + if (iIndent) + { + MyWriteFile(hFile, TEXT(" "), 1, bAsUnicode); + } + } + + switch (pNode->eType) + { + case JNT_NODE: + case JNT_ARRAY: + + MyWriteFile(hFile, pNode->eType == JNT_ARRAY ? TEXT("[") : TEXT("{"), 1, bAsUnicode); + + if (iIndent) + { + MyWriteFile(hFile, TEXT("\r\n"), 2, bAsUnicode); + MyWriteFileEx(hFile, TEXT(JSON_INDENT_CHAR), 1, bAsUnicode, iIndent); + } + + SerializeToFile(hFile, pNode->pValue, iIndent ? iIndent + JSON_INDENT : 0, bAsUnicode); + + if (iIndent) + { + MyWriteFile(hFile, TEXT("\r\n"), 2, bAsUnicode); + MyWriteFileEx(hFile, TEXT(JSON_INDENT_CHAR), 1, bAsUnicode, iIndent - 1); + } + + MyWriteFile(hFile, pNode->eType == JNT_ARRAY ? TEXT("]") : TEXT("}"), 1, bAsUnicode); + + break; + case JNT_VALUE: + + MyWriteFile(hFile, pNode->pszValue, lstrlen(pNode->pszValue), bAsUnicode); + + break; + case JNT_QUOTED_VALUE: + + MyWriteFile(hFile, TEXT("\""), 1, bAsUnicode); + MyWriteFile(hFile, pNode->pszValue, lstrlen(pNode->pszValue), bAsUnicode); + MyWriteFile(hFile, TEXT("\""), 1, bAsUnicode); + + break; + } + + if (pNode->pNext) + { + pNode = pNode->pNext; + + MyWriteFile(hFile, TEXT(","), 1, bAsUnicode); + + if (iIndent) + { + MyWriteFile(hFile, TEXT("\r\n"), 2, bAsUnicode); + MyWriteFileEx(hFile, TEXT(JSON_INDENT_CHAR), 1, bAsUnicode, iIndent - 1); + } + + SerializeToFile(hFile, pNode, iIndent, bAsUnicode); + } +} + +static void MyStrCpy(PTCHAR *ppszBuffer, int *pcchBuffer, int *piPos, PTCHAR pszCopy, int cchCopyLen, BOOL bResize, DWORD *pdwLastError) +{ + if (*piPos + cchCopyLen <= *pcchBuffer) + { + if (ppszBuffer && *ppszBuffer) + lstrcpy(*ppszBuffer + *piPos, pszCopy); + *piPos += cchCopyLen; + } + else if (bResize) + { + *pcchBuffer *= 2; + + if (ppszBuffer && *ppszBuffer) + { + PTCHAR pszNewBuffer = (PTCHAR)GlobalReAlloc(*ppszBuffer, *pcchBuffer, GMEM_ZEROINIT | GMEM_MOVEABLE); + if (!pszNewBuffer) + { + *pdwLastError = GetLastError(); + GlobalFree(*ppszBuffer); + *ppszBuffer = NULL; + } + else + { + *ppszBuffer = pszNewBuffer; + } + } + + MyStrCpy(ppszBuffer, pcchBuffer, piPos, pszCopy, cchCopyLen, TRUE, pdwLastError); + } + else + { + int i = 0; + for (; i < cchCopyLen && *piPos < *pcchBuffer; i++, (*piPos)++) + { + if (ppszBuffer && *ppszBuffer) + (*ppszBuffer)[*piPos] = pszCopy[i]; + } + } +} + +static void MyStrCpyEx(PTCHAR *ppszBuffer, int *pcchBuffer, int *piPos, PTCHAR pszCopy, int cchCopyLen, int iRepeat, BOOL bResize, DWORD *pdwLastError) +{ + int i = 0; + for (; i < iRepeat; i++) + { + MyStrCpy(ppszBuffer, pcchBuffer, piPos, pszCopy, cchCopyLen, bResize, pdwLastError); + } +} + +static void SerializeToBuffer(PTCHAR *ppszBuffer, int *pcchBuffer, struct JSON_NODE *pNode, int *piPos, int iIndent, BOOL bResize, DWORD *pdwLastError) +{ + if (!pNode) + { + if (ppszBuffer && *ppszBuffer) + lstrcpy(*ppszBuffer + *piPos, TEXT("")); + return; + } + + if (pNode->pszKey) + { + MyStrCpy(ppszBuffer, pcchBuffer, piPos, TEXT("\""), 1, bResize, pdwLastError); + MyStrCpy(ppszBuffer, pcchBuffer, piPos, pNode->pszKey, lstrlen(pNode->pszKey), bResize, pdwLastError); + MyStrCpy(ppszBuffer, pcchBuffer, piPos, TEXT("\":"), 2, bResize, pdwLastError); + + if (iIndent) + { + MyStrCpy(ppszBuffer, pcchBuffer, piPos, TEXT(" "), 1, bResize, pdwLastError); + } + } + + switch (pNode->eType) + { + case JNT_NODE: + case JNT_ARRAY: + + MyStrCpy(ppszBuffer, pcchBuffer, piPos, pNode->eType == JNT_ARRAY ? TEXT("[") : TEXT("{"), 1, bResize, pdwLastError); + + if (iIndent) + { + MyStrCpy(ppszBuffer, pcchBuffer, piPos, TEXT("\r\n"), 2, bResize, pdwLastError); + MyStrCpyEx(ppszBuffer, pcchBuffer, piPos, TEXT(JSON_INDENT_CHAR), 1, iIndent, bResize, pdwLastError); + } + + SerializeToBuffer(ppszBuffer, pcchBuffer, pNode->pValue, piPos, iIndent ? iIndent + JSON_INDENT : 0, bResize, pdwLastError); + + if (iIndent) + { + MyStrCpy(ppszBuffer, pcchBuffer, piPos, TEXT("\r\n"), 2, bResize, pdwLastError); + MyStrCpyEx(ppszBuffer, pcchBuffer, piPos, TEXT(JSON_INDENT_CHAR), 1, iIndent - 1, bResize, pdwLastError); + } + + MyStrCpy(ppszBuffer, pcchBuffer, piPos, pNode->eType == JNT_ARRAY ? TEXT("]") : TEXT("}"), 1, bResize, pdwLastError); + + break; + case JNT_VALUE: + + MyStrCpy(ppszBuffer, pcchBuffer, piPos, pNode->pszValue, lstrlen(pNode->pszValue), bResize, pdwLastError); + + break; + case JNT_QUOTED_VALUE: + + MyStrCpy(ppszBuffer, pcchBuffer, piPos, TEXT("\""), 1, bResize, pdwLastError); + MyStrCpy(ppszBuffer, pcchBuffer, piPos, pNode->pszValue, lstrlen(pNode->pszValue), bResize, pdwLastError); + MyStrCpy(ppszBuffer, pcchBuffer, piPos, TEXT("\""), 1, bResize, pdwLastError); + + break; + } + + if (pNode->pNext) + { + pNode = pNode->pNext; + + MyStrCpy(ppszBuffer, pcchBuffer, piPos, TEXT(","), 1, bResize, pdwLastError); + + if (iIndent) + { + MyStrCpy(ppszBuffer, pcchBuffer, piPos, TEXT("\r\n"), 2, bResize, pdwLastError); + MyStrCpyEx(ppszBuffer, pcchBuffer, piPos, TEXT(JSON_INDENT_CHAR), 1, iIndent - 1, bResize, pdwLastError); + } + + SerializeToBuffer(ppszBuffer, pcchBuffer, pNode, piPos, iIndent, bResize, pdwLastError); + } +} + +static BOOL AddRoot(struct JSON_NODE **ppNode) +{ + struct JSON_NODE *pRoot = JSON_Create(); + if (pRoot) + { + pRoot->eType = JNT_NODE; + pRoot->pValue = *ppNode; + *ppNode = pRoot; + return TRUE; + } + + return FALSE; +} + +static TCHAR i2a(TCHAR code) +{ + return hex[code & 15]; +} + +static int EscapePostData(PTCHAR *ppszPostData, int *pcchPostData, DWORD *pdwLastError) +{ + int cchLength = 0; + if (ppszPostData && *ppszPostData) + { + PTCHAR pszBuffer = (PTCHAR)GlobalAlloc(GPTR, (lstrlen(*ppszPostData) * 3 + 1) * sizeof(TCHAR)); + + if (pszBuffer) + { + PTCHAR pszBufferPtr = pszBuffer, pszPostDataPtr = *ppszPostData; + int iPos = 0; + + while (*pszPostDataPtr) + { + if (IsCharAlphaNumeric(*pszPostDataPtr) || *pszPostDataPtr == TEXT('-') || *pszPostDataPtr == TEXT('_') || *pszPostDataPtr == TEXT('.') || *pszPostDataPtr == TEXT('~')) + { + *pszBufferPtr++ = *pszPostDataPtr; + } + else if (*pszPostDataPtr == TEXT(' ')) + { + *pszBufferPtr++ = TEXT('+'); + } + else + { + *pszBufferPtr++ = TEXT('%'); + *pszBufferPtr++ = i2a(*pszPostDataPtr >> 4); + *pszBufferPtr++ = i2a(*pszPostDataPtr & 15); + } + + pszPostDataPtr++; + } + + if ((cchLength = lstrlen(pszBuffer)) > 0) + MyStrCpy(ppszPostData, pcchPostData, &iPos, pszBuffer, cchLength, TRUE, pdwLastError); + + GlobalFree(pszBuffer); + } + } + + return cchLength; +} + +BOOL JSON_Serialize(struct JSON_NODE *pNode, PTCHAR pszBuffer, int cchBuffer, BOOL bIsFile, BOOL bAsUnicode, BOOL bFormat) +{ + DWORD dwLastError = 0; + + BOOL bAddRoot = pNode && pNode->pszKey && *pNode->pszKey; + if (bAddRoot && !AddRoot(&pNode)) + bAddRoot = FALSE; + + if (bIsFile) + { + HANDLE hFile = CreateFile(pszBuffer, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + SerializeToFile(hFile, pNode, bFormat ? JSON_INDENT : 0, bAsUnicode); + CloseHandle(hFile); + } + else + { + dwLastError = GetLastError(); + } + } + else + { + int iPos = 0; + SerializeToBuffer(&pszBuffer, &cchBuffer, pNode, &iPos, bFormat ? JSON_INDENT : 0, FALSE, &dwLastError); + } + + if (bAddRoot) + GlobalFree(pNode); + + SetLastError(dwLastError); + return dwLastError == 0; +} + +PTCHAR JSON_SerializeAlloc(struct JSON_NODE *pNode, BOOL bFormat, BOOL bAsPostData) +{ + DWORD dwLastError = 0; + int cchBuffer = 2048; + PTCHAR pszBuffer = (PTCHAR)GlobalAlloc(GPTR, cchBuffer * sizeof(TCHAR)); + + if (pszBuffer) + { + BOOL bAddRoot = pNode && pNode->pszKey && *pNode->pszKey; + if (bAddRoot && !AddRoot(&pNode)) + bAddRoot = FALSE; + + if (bAsPostData) + { + struct JSON_NODE *pNext = pNode->pValue; + int iPos = 0; + int cchValueBuffer = 2048; + PTCHAR pszValueBuffer = (PTCHAR)GlobalAlloc(GPTR, cchValueBuffer * sizeof(TCHAR)); + if (pszValueBuffer) + { + while (pNext) + { + switch (pNext->eType) + { + case JNT_ARRAY: + { + struct JSON_NODE *pArrayNext = pNext->pValue; + while (pArrayNext) + { + int iValuePos = 0; + MyStrCpy(&pszBuffer, &cchBuffer, &iPos, pNext->pszKey, lstrlen(pNext->pszKey), TRUE, &dwLastError); + MyStrCpy(&pszBuffer, &cchBuffer, &iPos, TEXT("[]="), 3, TRUE, &dwLastError); + + switch (pArrayNext->eType) + { + case JNT_NODE: + case JNT_ARRAY: + SerializeToBuffer(&pszValueBuffer, &cchValueBuffer, pArrayNext, &iValuePos, 0, TRUE, &dwLastError); + break; + default: + MyStrCpy(&pszValueBuffer, &cchValueBuffer, &iValuePos, pArrayNext->pszValue, lstrlen(pArrayNext->pszValue), TRUE, &dwLastError); + break; + } + + iValuePos = EscapePostData(&pszValueBuffer, &cchValueBuffer, &dwLastError); + if (iValuePos > 0) + MyStrCpy(&pszBuffer, &cchBuffer, &iPos, pszValueBuffer, iValuePos, TRUE, &dwLastError); + + pArrayNext = pArrayNext->pNext; + if (pArrayNext) + MyStrCpy(&pszBuffer, &cchBuffer, &iPos, TEXT("&"), 1, TRUE, &dwLastError); + } + } + break; + + default: + { + int iValuePos = 0; + MyStrCpy(&pszBuffer, &cchBuffer, &iPos, pNext->pszKey, lstrlen(pNext->pszKey), TRUE, &dwLastError); + + if (pNext->eType != JNT_VALUE || pNext->eType == JNT_VALUE && lstrcmpi(pNext->pszValue, TEXT("true")) != 0) + { + MyStrCpy(&pszBuffer, &cchBuffer, &iPos, TEXT("="), 1, TRUE, &dwLastError); + + if (pNext->eType == JNT_NODE) + SerializeToBuffer(&pszValueBuffer, &cchValueBuffer, pNext->pValue, &iValuePos, bFormat ? JSON_INDENT : 0, TRUE, &dwLastError); + else + MyStrCpy(&pszValueBuffer, &cchValueBuffer, &iValuePos, pNext->pszValue, lstrlen(pNext->pszValue), TRUE, &dwLastError); + + iValuePos = EscapePostData(&pszValueBuffer, &cchValueBuffer, &dwLastError); + if (iValuePos > 0) + MyStrCpy(&pszBuffer, &cchBuffer, &iPos, pszValueBuffer, iValuePos, TRUE, &dwLastError); + } + } + break; + } + + pNext = pNext->pNext; + if (pNext) + MyStrCpy(&pszBuffer, &cchBuffer, &iPos, TEXT("&"), 1, TRUE, &dwLastError); + } + + GlobalFree(pszValueBuffer); + } + } + else + { + int iPos = 0; + SerializeToBuffer(&pszBuffer, &cchBuffer, pNode, &iPos, bFormat ? JSON_INDENT : 0, TRUE, &dwLastError); + } + + if (bAddRoot) + GlobalFree(pNode); + } + + SetLastError(dwLastError); + return pszBuffer; +} + +PTCHAR JSON_Expand(struct JSON_NODE *pNode) +{ + PTCHAR pszExpanded = NULL; + int i, j, cch = lstrlen(pNode->pszValue); + TCHAR szUnicode[7]; + + pszExpanded = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * (cch + 1)); + if (pszExpanded) + { + for (i = 0, j = 0; i < cch; i++, j++) + { + if (pNode->pszValue[i] == TEXT('\\')) + { + switch (pNode->pszValue[i + 1]) + { + case TEXT('"'): + case TEXT('\\'): + case TEXT('/'): + pszExpanded[j] = pNode->pszValue[i + 1]; + i++; + break; + case TEXT('b'): + pszExpanded[j] = TEXT('\b'); + i++; + break; + case TEXT('f'): + pszExpanded[j] = TEXT('\f'); + i++; + break; + case TEXT('n'): + pszExpanded[j] = TEXT('\n'); + i++; + break; + case TEXT('r'): + pszExpanded[j] = TEXT('\r'); + i++; + break; + case TEXT('t'): + pszExpanded[j] = TEXT('\t'); + i++; + break; + case TEXT('u'): + wsprintf(szUnicode, TEXT("0x%c%c%c%c"), pNode->pszValue[i + 2], pNode->pszValue[i + 3], pNode->pszValue[i + 4], pNode->pszValue[i + 5]); +#ifdef UNICODE + pszExpanded[j] = (WCHAR)myatoi(szUnicode); +#else + wsprintfW((PWCHAR)szUnicode, L"%c", myatoi(szUnicode)); + if (WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, (PWCHAR)szUnicode, 1, NULL, 0, NULL, NULL) == 1) + WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, (PWCHAR)szUnicode, 1, pszExpanded + j, 1, NULL, NULL); + else + lstrcpyA(pszExpanded + j, "?"); +#endif + i += 5; + break; + } + } + else + { + pszExpanded[j] = pNode->pszValue[i]; + } + } + } + + return pszExpanded; +} + +PTCHAR JSON_Escape(PTCHAR pszValue, enum JSON_ESCAPE_FLAGS eFlags) +{ + PTCHAR pszEscaped; + int i, cch = lstrlen(pszValue), cchNew = cch; + BOOL bAlreadyQuoted = cch > 1 && pszValue[0] == TEXT('"') && pszValue[cch - 1] == TEXT('"'); + BOOL bQuote = (eFlags & JEF_QUOTE) && ((eFlags & JEF_ALWAYS_QUOTE) || !bAlreadyQuoted); + if (bQuote) + cchNew += 2; + + for (i = 0; i < cch; i++) + { + switch (pszValue[i]) + { + case TEXT('\b'): + case TEXT('\f'): + case TEXT('\n'): + case TEXT('\r'): + case TEXT('\t'): + case TEXT('\\'): + case TEXT('"'): + cchNew++; + break; + default: + if ((eFlags & JEF_ESCAPE_UNICODE) && (pszValue[i] < 32 || pszValue[i] > 126)) + cchNew += 6; + break; + } + } + + pszEscaped = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * (cchNew + 1)); + + if (pszEscaped && cch < cchNew) + { + int j; + + if (bQuote) + { + pszEscaped[0] = TEXT('"'); + i = 0; + j = 1; + } + else if (bAlreadyQuoted) + { + pszEscaped[0] = TEXT('"'); + i = 1; + j = 1; + cch--; + } + else + { + i = 0; + j = 0; + } + + for (; i < cch; i++, j++) + { + switch (pszValue[i]) + { + case TEXT('\b'): + pszEscaped[j++] = TEXT('\\'); + pszEscaped[j] = TEXT('b'); + break; + case TEXT('\f'): + pszEscaped[j++] = TEXT('\\'); + pszEscaped[j] = TEXT('f'); + break; + case TEXT('\n'): + pszEscaped[j++] = TEXT('\\'); + pszEscaped[j] = TEXT('n'); + break; + case TEXT('\r'): + pszEscaped[j++] = TEXT('\\'); + pszEscaped[j] = TEXT('r'); + break; + case TEXT('\t'): + pszEscaped[j++] = TEXT('\\'); + pszEscaped[j] = TEXT('t'); + break; + case TEXT('\\'): + case TEXT('"'): + pszEscaped[j++] = TEXT('\\'); + pszEscaped[j] = pszValue[i]; + break; + default: + if ((eFlags & JEF_ESCAPE_UNICODE) && (pszValue[i] < 32 || pszValue[i] > 126)) + { + j += wsprintf(pszEscaped + j, TEXT("\\u%04x"), (unsigned char)pszValue[i]) - 1; + } + else + { + pszEscaped[j] = pszValue[i]; + } + break; + } + } + + if (bQuote || bAlreadyQuoted) + { + pszEscaped[j] = TEXT('"'); + } + } + else + { + lstrcpy(pszEscaped, pszValue); + } + + return pszEscaped; +} + +PCHAR JSON_FromUnicode(PWCHAR pwszText, int *pcchText, UINT nCodePage) +{ + int cbConverted = WideCharToMultiByte(nCodePage, WC_COMPOSITECHECK, pwszText, *pcchText, NULL, 0, NULL, NULL); + if (cbConverted > 0) + { + PCHAR pszBuffer = (PCHAR)GlobalAlloc(GPTR, sizeof(CHAR) * (cbConverted + 1)); + if (pszBuffer) + { + if (WideCharToMultiByte(nCodePage, WC_COMPOSITECHECK, pwszText, *pcchText, pszBuffer, cbConverted, NULL, NULL) > 0) + { + *pcchText = cbConverted; + return pszBuffer; + } + + GlobalFree(pszBuffer); + } + } + + return NULL; +} + +PWCHAR JSON_ToUnicode(PCHAR pszText, int *pcbText) +{ + int cchConverted = MultiByteToWideChar(CP_ACP, MB_COMPOSITE, pszText, *pcbText, NULL, 0); + if (cchConverted > 0) + { + PWCHAR pwszBuffer = (PWCHAR)GlobalAlloc(GPTR, sizeof(WCHAR) * (cchConverted + 1)); + if (pwszBuffer) + { + if (MultiByteToWideChar(CP_ACP, MB_COMPOSITE, pszText, *pcbText, pwszBuffer, cchConverted) > 0) + { + *pcbText = cchConverted; + return pwszBuffer; + } + + GlobalFree(pwszBuffer); + } + } + + return NULL; +} + +static BOOL _Sort(enum JSON_SORT_FLAGS eFlags, struct JSON_NODE *pCurrent, struct JSON_NODE *pNext) +{ + BOOL bMove = FALSE; + + if (eFlags & JSF_BY_KEYS) + { + if (pNext->eType == JNT_NODE) + { + if (eFlags & JSF_RECURSIVE) + JSON_Sort(pNext, eFlags); + } + + if (eFlags & JSF_NUMERIC) + { + if (myatoi(pCurrent->pszKey) > myatoi(pNext->pszKey)) + bMove = TRUE; + } + else if (eFlags & JSF_CASE_SENSITIVE) + { + if (lstrcmp(pCurrent->pszKey, pNext->pszKey) > 0) + bMove = TRUE; + } + else + { + if (lstrcmpi(pCurrent->pszKey, pNext->pszKey) > 0) + bMove = TRUE; + } + } + else + { + if (pNext->eType == JNT_ARRAY || pNext->eType == JNT_NODE) + { + if (eFlags & JSF_RECURSIVE) + JSON_Sort(pNext, eFlags); + return FALSE; + } + + if (pCurrent->eType == JNT_ARRAY || pCurrent->eType == JNT_NODE) + { + return FALSE; + } + + if (eFlags & JSF_NUMERIC) + { + if (myatoi(pCurrent->pszValue) > myatoi(pNext->pszValue)) + bMove = TRUE; + } + else if (eFlags & JSF_CASE_SENSITIVE) + { + if (lstrcmp(pCurrent->pszValue, pNext->pszValue) > 0) + bMove = TRUE; + } + else + { + if (lstrcmpi(pCurrent->pszValue, pNext->pszValue) > 0) + bMove = TRUE; + } + } + + if (eFlags & JSF_DESCENDING) + bMove = !bMove; + + return bMove; +} + +void JSON_Sort(struct JSON_NODE *pNode, enum JSON_SORT_FLAGS eFlags) +{ + if (pNode->eType != JNT_NODE && pNode->eType != JNT_ARRAY) + return; + + while (TRUE) + { + BOOL bSwapped = FALSE; + struct JSON_NODE **ppLink = &pNode->pValue; + struct JSON_NODE *pCurrent = pNode->pValue; + struct JSON_NODE *pNext; + + while (pNext = pCurrent->pNext) + { + if (_Sort(eFlags, pCurrent, pNext)) + { + pCurrent->pNext = pNext->pNext; + pNext->pNext = pCurrent; + *ppLink = pCurrent = pNext; + bSwapped |= TRUE; + } + + ppLink = &pCurrent->pNext; + pCurrent = pNext; + } + + if (!bSwapped) + break; + } +} \ No newline at end of file diff --git a/src/JSON.h b/src/JSON.h new file mode 100644 index 0000000..68ffd9d --- /dev/null +++ b/src/JSON.h @@ -0,0 +1,86 @@ +#ifndef __JSON_H__ +#define __JSON_H__ + +#define JSON_INDENT 1 +#define JSON_INDENT_CHAR "\t" + +enum JSON_WORD_TYPE +{ + JWT_STRING, + JWT_OTHER, + JWT_NONE +}; + +enum JSON_NODE_TYPE +{ + // node = key: [node] + JNT_NODE, + // node = key: [ array ] + JNT_ARRAY, + // node = key: value + JNT_VALUE, + // node = key: "value" + JNT_QUOTED_VALUE +}; + +enum JSON_SET_FLAGS +{ + JSF_NONE = 0, + JSF_IS_FILE = 1, + JSF_IS_UNICODE = 2, + JSF_IS_RAW = 4 +}; + +enum JSON_ESCAPE_FLAGS +{ + JEF_NONE = 0, + JEF_ESCAPE_UNICODE = 1, + JEF_QUOTE = 2, + JEF_ALWAYS_QUOTE = 4 +}; + +struct JSON_NODE +{ + enum JSON_NODE_TYPE eType; + struct JSON_NODE* pNext; + PTCHAR pszKey; + union + { + PTCHAR pszValue; + struct JSON_NODE* pValue; + }; +}; + +enum JSON_SORT_FLAGS +{ + // Sort descending instead of ascending. + JSF_DESCENDING = 1, + // Numeric comparison rather than string. + JSF_NUMERIC = 2, + // Sort case sensitive. + JSF_CASE_SENSITIVE = 4, + // Sort by keys rather than by values. + JSF_BY_KEYS = 8, + // Sort recursively. + JSF_RECURSIVE = 16 +}; + +struct JSON_NODE* JSON_Create(); +BOOL JSON_IsTrue(struct JSON_NODE* pNode); +PTCHAR JSON_GetQuotedValue(struct JSON_NODE* pNode, const PTCHAR pszDefaultValue); +void JSON_Delete(struct JSON_NODE** ppNode, struct JSON_NODE* pPrev); +int JSON_Count(struct JSON_NODE* pNode); +struct JSON_NODE* JSON_Get(struct JSON_NODE* pNode, PTCHAR pszKey, BOOL bKeyIsIndex); +struct JSON_NODE* JSON_GetEx(struct JSON_NODE* pNode, PTCHAR pszKey, BOOL bKeyIsIndex, BOOL bCreate, BOOL* pbCreated); +struct JSON_NODE* JSON_Next(struct JSON_NODE** ppNode, PTCHAR pszKey, BOOL bKeyIsIndex, BOOL bCreate, BOOL* pbCreated); +BOOL JSON_Set(struct JSON_NODE* pNode, PBYTE pbValue, enum JSON_SET_FLAGS eFlags); +BOOL JSON_SetEx(struct JSON_NODE* pNode, PTCHAR pszKey, BOOL bKeyIsIndex, PBYTE pbValue, enum JSON_SET_FLAGS eFlags); +BOOL JSON_Serialize(struct JSON_NODE* pNode, PTCHAR pszBuffer, int cchBuffer, BOOL bIsFile, BOOL bAsUnicode, BOOL bFormat); +PTCHAR JSON_SerializeAlloc(struct JSON_NODE* pNode, BOOL bFormat, BOOL bAsPostData); +PTCHAR JSON_Expand(struct JSON_NODE* pNode); +PTCHAR JSON_Escape(PTCHAR pszValue, enum JSON_ESCAPE_FLAGS eFlags); +PCHAR JSON_FromUnicode(PWCHAR pwszText, int* pcchText, UINT nCodePage); +PWCHAR JSON_ToUnicode(PCHAR pszText, int* pcbText); +void JSON_Sort(struct JSON_NODE* pNode, enum JSON_SORT_FLAGS eFlags); + +#endif \ No newline at end of file diff --git a/src/LinkedList.c b/src/LinkedList.c new file mode 100644 index 0000000..29cd1d8 --- /dev/null +++ b/src/LinkedList.c @@ -0,0 +1,90 @@ +#include +#include "LinkedList.h" + +struct LinkedList* LinkedListCreate() +{ + return (struct LinkedList*)GlobalAlloc(GPTR, sizeof(struct LinkedList)); +} + +void LinkedListDestroy(struct LinkedList** ppList, LinkedListDestroyCallback callback) +{ + if (ppList && *ppList) + { + struct LinkedListNode* pNode = (*ppList)->First; + + while (pNode) + { + struct LinkedListNode* pNext = pNode->Next; + if (callback) + callback(pNode); + GlobalFree(pNode->Key); + GlobalFree(pNode); + pNode = pNext; + } + + GlobalFree(*ppList); + *ppList = NULL; + } +} + +void LinkedListDelete(struct LinkedList** ppList, const PTCHAR szKey) +{ + if (ppList && *ppList) + { + struct LinkedListNode* pNode = (*ppList)->First; + struct LinkedListNode* pPrev = NULL; + PTCHAR pszKey = szKey ? szKey : TEXT(""); + + while (pNode) + { + struct LinkedListNode* pNext = pNode->Next; + + if (lstrcmpi(pNode->Key, pszKey) == 0) + { + if (pPrev) + pPrev->Next = pNext; + + if ((*ppList)->First == pNode) + { + GlobalFree(*ppList); + *ppList = NULL; + } + + GlobalFree(pNode->Key); + GlobalFree(pNode); + break; + } + + pPrev = pNode; + pNode = pNext; + } + } +} + +struct LinkedListNode* LinkedListGet(struct LinkedList* pList, const PTCHAR szKey, const BOOL bCreate) +{ + if (pList) + { + struct LinkedListNode* pNode = pList->First; + PTCHAR pszKey = szKey ? szKey : TEXT(""); + + while (pNode) + { + if (lstrcmpi(pNode->Key, pszKey) == 0) + return pNode; + pNode = pNode->Next; + } + + if (bCreate) + { + pNode = (struct LinkedListNode*)GlobalAlloc(GPTR, sizeof(struct LinkedListNode)); + pNode->Key = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * (lstrlen(pszKey) + 1)); + lstrcpy(pNode->Key, pszKey); + pNode->Next = pList->First; + pList->First = pNode; + return pNode; + } + } + + return NULL; +} \ No newline at end of file diff --git a/src/LinkedList.h b/src/LinkedList.h new file mode 100644 index 0000000..06a1ece --- /dev/null +++ b/src/LinkedList.h @@ -0,0 +1,26 @@ +#ifndef __LinkedList_H__ +#define __LinkedList_H__ + +struct LinkedListNode +{ + PTCHAR Key; + PVOID Value; + struct LinkedListNode* Next; +}; + +struct LinkedList +{ + struct LinkedListNode* First; +}; + +struct LinkedList* LinkedListCreate(); + +typedef void (*LinkedListDestroyCallback)(struct LinkedListNode* pListNode); + +void LinkedListDestroy(struct LinkedList** ppList, LinkedListDestroyCallback callback); + +void LinkedListDelete(struct LinkedList** ppList, const PTCHAR szKey); + +struct LinkedListNode* LinkedListGet(struct LinkedList* pList, const PTCHAR szKey, const BOOL bCreate); + +#endif \ No newline at end of file diff --git a/src/api.h b/src/api.h new file mode 100644 index 0000000..85d41c4 --- /dev/null +++ b/src/api.h @@ -0,0 +1,83 @@ +/* + * apih + * + * This file is a part of NSIS. + * + * Copyright (C) 1999-2013 Nullsoft and Contributors + * + * Licensed under the zlib/libpng license (the "License"); + * you may not use this file except in compliance with the License. + * + * Licence details can be found in the file COPYING. + * + * This software is provided 'as-is', without any express or implied + * warranty. + */ + +#ifndef _NSIS_EXEHEAD_API_H_ +#define _NSIS_EXEHEAD_API_H_ + +// Starting with NSIS 2.42, you can check the version of the plugin API in exec_flags->plugin_api_version +// The format is 0xXXXXYYYY where X is the major version and Y is the minor version (MAKELONG(y,x)) +// When doing version checks, always remember to use >=, ex: if (pX->exec_flags->plugin_api_version >= NSISPIAPIVER_1_0) {} + +#define NSISPIAPIVER_1_0 0x00010000 +#define NSISPIAPIVER_CURR NSISPIAPIVER_1_0 + +// NSIS Plug-In Callback Messages +enum NSPIM +{ + NSPIM_UNLOAD, // This is the last message a plugin gets, do final cleanup + NSPIM_GUIUNLOAD, // Called after .onGUIEnd +}; + +// Prototype for callbacks registered with extra_parameters->RegisterPluginCallback() +// Return NULL for unknown messages +// Should always be __cdecl for future expansion possibilities +typedef UINT_PTR (*NSISPLUGINCALLBACK)(enum NSPIM); + +// extra_parameters data structures containing other interesting stuff +// but the stack, variables and HWND passed on to plug-ins. +typedef struct +{ + int autoclose; + int all_user_var; + int exec_error; + int abort; + int exec_reboot; // NSIS_SUPPORT_REBOOT + int reboot_called; // NSIS_SUPPORT_REBOOT + int XXX_cur_insttype; // depreacted + int plugin_api_version; // see NSISPIAPIVER_CURR + // used to be XXX_insttype_changed + int silent; // NSIS_CONFIG_SILENT_SUPPORT + int instdir_error; + int rtl; + int errlvl; + int alter_reg_view; + int status_update; +} exec_flags_t; + +#ifndef NSISCALL +# define NSISCALL __stdcall +#endif + +typedef struct { + exec_flags_t *exec_flags; + int (NSISCALL *ExecuteCodeSegment)(int, HWND); + void (NSISCALL *validate_filename)(TCHAR *); + int (NSISCALL *RegisterPluginCallback)(HMODULE, NSISPLUGINCALLBACK); // returns 0 on success, 1 if already registered and < 0 on errors +} extra_parameters; + +// Definitions for page showing plug-ins +// See Ui.c to understand better how they're used + +// sent to the outer window to tell it to go to the next inner window +#define WM_NOTIFY_OUTER_NEXT (WM_USER+0x8) + +// custom pages should send this message to let NSIS know they're ready +#define WM_NOTIFY_CUSTOM_READY (WM_USER+0xd) + +// sent as wParam with WM_NOTIFY_OUTER_NEXT when user cancels - heed its warning +#define NOTIFY_BYE_BYE 'x' + +#endif /* _PLUGIN_H_ */ diff --git a/src/nsJSON.c b/src/nsJSON.c new file mode 100644 index 0000000..f3d2721 --- /dev/null +++ b/src/nsJSON.c @@ -0,0 +1,1474 @@ +/* + nsJSON NSIS plug-in by Stuart Welch + v1.1.1.1 - 10th October 2024 +*/ + +#include +#include +#include "LinkedList.h" +#include "nsJSON.h" +#include "JSON.h" +#include "pluginapi.h" + +HANDLE g_hInstance; +struct LinkedList* g_pList = NULL; + +struct THREAD_PARAM +{ + struct JSON_NODE* pNode; + struct JSON_NODE* pRootNode; +}; + +static void NodeDelete(struct LinkedListNode* pListNode) +{ + JSON_Delete(&((struct JSON_NODE*)pListNode->Value), NULL); + pListNode->Value = NULL; +} + +static UINT_PTR PluginCallback(enum NSPIM msg) +{ + if (msg == NSPIM_UNLOAD) + { + LinkedListDestroy(&g_pList, NodeDelete); + } + return 0; +} + +BOOL WINAPI DllMain(HANDLE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) +{ + g_hInstance = hInst; + return TRUE; +} + +static struct JSON_NODE* GetTreeNode(PTCHAR pszTree, BOOL bCreate) +{ + struct LinkedListNode* pListNode; + + if (!g_pList) + g_pList = LinkedListCreate(); + + pListNode = LinkedListGet(g_pList, pszTree, bCreate); + + if (bCreate && pListNode && !pListNode->Value) + pListNode->Value = JSON_Create(); + + return pListNode ? (struct JSON_NODE*)pListNode->Value : NULL; +} + +static struct JSON_NODE* PopTreeNode(PTCHAR pszArg, BOOL bCreate, BOOL* pbCreated) +{ + struct LinkedListNode* pListNode = NULL; + + if (!g_pList) + g_pList = LinkedListCreate(); + + if (pbCreated) + *pbCreated = FALSE; + + if (popstring(pszArg) == 0) + { + if (lstrcmpi(pszArg, TEXT("/tree")) == 0) + { + if (popstring(pszArg) == 0) + pListNode = LinkedListGet(g_pList, pszArg, bCreate); + } + else + { + pushstring(pszArg); + lstrcpy(pszArg, TEXT("")); + } + } + + if (!pListNode) + pListNode = LinkedListGet(g_pList, NULL, bCreate); + + if (bCreate && pListNode && !pListNode->Value) + { + pListNode->Value = JSON_Create(); + if (pbCreated) + *pbCreated = TRUE; + } + + return pListNode ? (struct JSON_NODE*)pListNode->Value : NULL; +} + +enum HttpWebRequestDataType +{ + WRDT_Default = 0, + WRDT_JSON = 1, + WRDT_Raw = 2, +}; + +static enum HttpWebRequestDataType GetHttpWebRequestDataType(struct JSON_NODE* pRootNode) +{ + PTCHAR pszDataType = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("DataType"), FALSE), TEXT("")); + if (lstrcmpi(pszDataType, TEXT("JSON")) == 0) + return WRDT_JSON; + if (lstrcmpi(pszDataType, TEXT("Raw")) == 0) + return WRDT_Raw; + return WRDT_Default; +} + +#define _trim(c) (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == '.') + +static void GetLastErrorMessage(PTCHAR pszErrorMessage, const PTCHAR szWin32Func, DWORD dwLastError) +{ + PTCHAR pszError; + int cchLastError; + + if (dwLastError >= 12001 && dwLastError <= 12156) + cchLastError = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandleA("wininet.dll"), dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&pszError, 0, NULL); + else + cchLastError = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&pszError, 0, NULL); + + if (cchLastError > 0) + { + int cchWin32Func = lstrlen(szWin32Func); + lstrcpy(pszErrorMessage, szWin32Func); + lstrcpy(pszErrorMessage + cchWin32Func, TEXT(": ")); + + for (cchLastError--; cchLastError >= 0 && _trim(pszError[cchLastError]); cchLastError--) + pszError[cchLastError] = '\0'; + lstrcpy(pszErrorMessage + cchWin32Func + 2, pszError); + + wsprintf(pszErrorMessage + cchWin32Func + 2 + cchLastError, TEXT(" (%lu)"), dwLastError); + } + + if (pszError) + LocalFree(pszError); +} + +static void SetLastErrorNode(struct JSON_NODE* pNode, const PTCHAR szWin32Func, DWORD dwLastError) +{ + PTCHAR pszError = (PTCHAR)GlobalAlloc(GPTR, 16 * sizeof(TCHAR)); + int cchLastError; + + if (pszError) + { + wsprintf(pszError, TEXT("%lu"), dwLastError); + JSON_SetEx(pNode, TEXT("ErrorCode"), FALSE, (PBYTE)pszError, JSF_NONE); + GlobalFree(pszError); + } + + if (dwLastError >= 12001 && dwLastError <= 12156) + cchLastError = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, GetModuleHandleA("wininet.dll"), dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&pszError, 0, NULL); + else + cchLastError = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&pszError, 0, NULL); + + if (cchLastError > 0) + { + int cchWin32Func = lstrlen(szWin32Func); + PTCHAR pszErrorMessage = (PTCHAR)GlobalAlloc(GPTR, (cchLastError + cchWin32Func + 3) * sizeof(TCHAR)); + if (pszErrorMessage) + { + lstrcpy(pszErrorMessage, szWin32Func); + lstrcpy(pszErrorMessage + cchWin32Func, TEXT(": ")); + + for (cchLastError--; cchLastError >= 0 && _trim(pszError[cchLastError]); cchLastError--) + pszError[cchLastError] = '\0'; + lstrcpy(pszErrorMessage + cchWin32Func + 2, pszError); +#ifdef UNICODE + JSON_SetEx(pNode, TEXT("ErrorMessage"), FALSE, (PBYTE)pszErrorMessage, JSF_IS_RAW | JSF_IS_UNICODE); +#else + JSON_SetEx(pNode, TEXT("ErrorMessage"), FALSE, (PBYTE)pszErrorMessage, JSF_IS_RAW); +#endif + GlobalFree(pszErrorMessage); + } + } + + if (pszError) + LocalFree(pszError); +} + +static void DoHttpWebRequest(struct JSON_NODE* pNode, struct JSON_NODE* pRootNode) +{ + static PTCHAR accept[2] = { TEXT("*/*"), NULL }; + + PTCHAR pszUrl = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("Url"), FALSE), NULL); + if (pszUrl) + { + PTCHAR pszAgentNode = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("Agent"), FALSE), TEXT("nsJSON NSIS plug-in/1.0.x.x")), + pszAccessType = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("AccessType"), FALSE), NULL), + pszProxyName = NULL, + pszProxyBypass = NULL; + struct JSON_NODE* pProxyNode = JSON_Get(pRootNode, TEXT("Proxy"), FALSE); + BOOL bSuccess = FALSE; + + DWORD dwAccessType = INTERNET_OPEN_TYPE_DIRECT; + if (pszAccessType) + { + if (lstrcmpi(pszAccessType, TEXT("PreConfig")) == 0) + { + dwAccessType = INTERNET_OPEN_TYPE_PRECONFIG; + } + else if (lstrcmpi(pszAccessType, TEXT("Proxy")) == 0) + { + if (pProxyNode) + { + pszProxyName = JSON_GetQuotedValue(JSON_Get(pProxyNode, TEXT("Server"), FALSE), NULL); + if (pszProxyName) + { + dwAccessType = INTERNET_OPEN_TYPE_PROXY; + pszProxyBypass = JSON_GetQuotedValue(JSON_Get(pProxyNode, TEXT("Bypass"), FALSE), NULL); + } + } + } + } + + HINTERNET hSession = InternetOpen(pszAgentNode, INTERNET_OPEN_TYPE_DIRECT, pszProxyName, pszProxyBypass, 0); + if (hSession != NULL) + { + LPURL_COMPONENTS pstUrlComp = (LPURL_COMPONENTS)GlobalAlloc(GPTR, sizeof(URL_COMPONENTS)); + if (pstUrlComp != NULL) + { + struct JSON_NODE* pParamsNode = JSON_Get(pRootNode, TEXT("Params"), FALSE); + BOOL bRawParams = pParamsNode && lstrcmpi(JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("ParamsType"), FALSE), TEXT("")), TEXT("Raw")) == 0; + PTCHAR pszParams = pParamsNode ? (bRawParams ? JSON_GetQuotedValue(pParamsNode, TEXT("")) : JSON_SerializeAlloc(pParamsNode->pValue, FALSE, TRUE)) : NULL; + + if (pParamsNode && !pszParams) + { + SetLastErrorNode(pNode, TEXT("JSON_SerializeAlloc"), GetLastError()); + } + else + { + pstUrlComp->dwStructSize = sizeof(URL_COMPONENTS); + pstUrlComp->dwUrlPathLength = pstUrlComp->dwHostNameLength = lstrlen(pszUrl) + 1; + + if (pParamsNode && pszParams && *pszParams) + pstUrlComp->dwUrlPathLength += lstrlen(pszParams) + 1; + + pstUrlComp->lpszUrlPath = (PTCHAR)GlobalAlloc(GPTR, pstUrlComp->dwUrlPathLength * sizeof(TCHAR)); + if (pstUrlComp->lpszUrlPath) + { + pstUrlComp->lpszHostName = (PTCHAR)GlobalAlloc(GPTR, pstUrlComp->dwHostNameLength * sizeof(TCHAR)); + if (pstUrlComp->lpszHostName) + { + if (InternetCrackUrl(pszUrl, 0, 0, pstUrlComp)) + { + HINTERNET hConnect = InternetConnect(hSession, pstUrlComp->lpszHostName, pstUrlComp->nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); + if (hConnect) + { + PTCHAR pszVerb = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("Verb"), FALSE), TEXT("GET")); + PTCHAR pszValue; + HINTERNET hRequest; + BOOL bEncoding; + + if (pParamsNode && pszParams && *pszParams) + { + lstrcpy(pstUrlComp->lpszUrlPath + pstUrlComp->dwUrlPathLength, TEXT("?")); + lstrcpy(pstUrlComp->lpszUrlPath + pstUrlComp->dwUrlPathLength + 1, pszParams); + } + + if (pProxyNode) + { + pszValue = JSON_GetQuotedValue(JSON_Get(pProxyNode, TEXT("Username"), FALSE), NULL); + if (pszValue) + InternetSetOption(hConnect, INTERNET_OPTION_PROXY_USERNAME, pszValue, lstrlen(pszValue)); + + pszValue = JSON_GetQuotedValue(JSON_Get(pProxyNode, TEXT("Password"), FALSE), NULL); + if (pszValue) + InternetSetOption(hConnect, INTERNET_OPTION_PROXY_PASSWORD, pszValue, lstrlen(pszValue)); + } + + if (bEncoding = JSON_IsTrue(JSON_Get(pRootNode, TEXT("Decoding"), FALSE))) + { + InternetSetOption(hConnect, INTERNET_OPTION_HTTP_DECODING, &bEncoding, sizeof(bEncoding)); + } + + pszValue = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("Username"), FALSE), NULL); + if (pszValue) + { + InternetSetOption(hConnect, INTERNET_OPTION_USERNAME, pszValue, lstrlen(pszValue)); + } + + pszValue = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("Password"), FALSE), NULL); + if (pszValue) + { + InternetSetOption(hConnect, INTERNET_OPTION_PASSWORD, pszValue, lstrlen(pszValue)); + } + + pszValue = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("ConnectTimeout"), FALSE), NULL); + if (pszValue) + { + ULONG ulValue = myatou(pszValue); + InternetSetOption(hConnect, INTERNET_OPTION_CONNECT_TIMEOUT, &ulValue, sizeof(ulValue)); + } + + pszValue = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("SendTimeout"), FALSE), NULL); + if (pszValue) + { + ULONG ulValue = myatou(pszValue); + InternetSetOption(hConnect, INTERNET_OPTION_SEND_TIMEOUT, &ulValue, sizeof(ulValue)); + } + + pszValue = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("ReceiveTimeout"), FALSE), NULL); + if (pszValue) + { + ULONG ulValue = myatou(pszValue); + InternetSetOption(hConnect, INTERNET_OPTION_RECEIVE_TIMEOUT, &ulValue, sizeof(ulValue)); + } + + hRequest = HttpOpenRequest(hConnect, pszVerb, pstUrlComp->lpszUrlPath, NULL, NULL, accept, + INTERNET_FLAG_NO_CACHE_WRITE | + INTERNET_FLAG_NO_COOKIES | + INTERNET_FLAG_NO_UI | + INTERNET_FLAG_RELOAD | + INTERNET_FLAG_KEEP_CONNECTION | + (pstUrlComp->nScheme == INTERNET_SCHEME_HTTPS ? + INTERNET_FLAG_SECURE | + INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | + INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS : 0), 0); + if (hRequest) + { + struct JSON_NODE* pDataNode = JSON_Get(pRootNode, TEXT("Data"), FALSE); + PTCHAR pszDataEncoding = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("DataEncoding"), FALSE), NULL); + enum HttpWebRequestDataType eDataType = GetHttpWebRequestDataType(pRootNode); + struct JSON_NODE* pHeadersNode = JSON_Get(pRootNode, TEXT("Headers"), FALSE); + BOOL bRequestSent = FALSE; + + if (pHeadersNode) + { + if (pHeadersNode->eType == JNT_QUOTED_VALUE) + { + HttpAddRequestHeaders(hRequest, pHeadersNode->pszValue, -1L, 0); + } + + if (bEncoding) + { + HttpAddRequestHeaders(hRequest, TEXT("Accept-Encoding: gzip,deflate\r\n"), -1L, HTTP_ADDREQ_FLAG_ADD); + } + + if (lstrcmpi(pszVerb, TEXT("POST")) == 0) + { + if (eDataType == WRDT_JSON) + { + HttpAddRequestHeaders(hRequest, TEXT("Content-Type: application/json\r\n"), -1L, HTTP_ADDREQ_FLAG_ADD); + } + else + { + HttpAddRequestHeaders(hRequest, TEXT("Content-Type: application/x-www-form-urlencoded\r\n"), -1L, HTTP_ADDREQ_FLAG_ADD); + } + } + + if (pHeadersNode->eType == JNT_NODE) + { + pHeadersNode = pHeadersNode->pValue; + while (pHeadersNode) + { + int cchHeader = lstrlen(pHeadersNode->pszKey) + lstrlen(pHeadersNode->pszValue) + 4; + PTCHAR pszHeader = (PTCHAR)GlobalAlloc(GPTR, (cchHeader + 1) * sizeof(TCHAR)); + if (pszHeader) + { + lstrcpy(pszHeader, pHeadersNode->pszKey); + lstrcat(pszHeader, TEXT(": ")); + lstrcat(pszHeader, pHeadersNode->pszValue); + lstrcat(pszHeader, TEXT("\r\n")); + HttpAddRequestHeaders(hRequest, pszHeader, cchHeader, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE); + GlobalFree(pszHeader); + } + + pHeadersNode = pHeadersNode->pNext; + } + } + } + + if (pDataNode) + { + PTCHAR pszBuffer; + + if (eDataType == WRDT_JSON || eDataType == WRDT_Default) + { + pszBuffer = JSON_SerializeAlloc(pDataNode->pValue, FALSE, eDataType == WRDT_Default); + if (!pszBuffer) + { + SetLastErrorNode(pNode, TEXT("JSON_SerializeAlloc"), GetLastError()); + } + } + else + { + pszBuffer = JSON_GetQuotedValue(pDataNode, TEXT("")); + } + + if (pszBuffer) + { +#ifdef UNICODE + if (pszDataEncoding && lstrcmpi(pszDataEncoding, TEXT("Unicode")) == 0) + { + bRequestSent = HttpSendRequest(hRequest, NULL, 0, pszBuffer, lstrlen(pszBuffer) * sizeof(TCHAR)); + if (!bRequestSent) + SetLastErrorNode(pNode, TEXT("HttpSendRequest"), GetLastError()); + } + else + { + int cchConverted = lstrlen(pszBuffer); + PCHAR pszConverted = JSON_FromUnicode(pszBuffer, &cchConverted, CP_ACP); + if (pszConverted) + { + bRequestSent = HttpSendRequest(hRequest, NULL, 0, pszConverted, cchConverted); + if (!bRequestSent) + SetLastErrorNode(pNode, TEXT("HttpSendRequest"), GetLastError()); + GlobalFree(pszConverted); + } + } +#else + if (pszDataEncoding && lstrcmpi(pszDataEncoding, TEXT("Unicode")) == 0) + { + int cbUnicode = lstrlen(pszBuffer); + PWCHAR pszUnicode = JSON_ToUnicode(pszBuffer, &cbUnicode); + if (pszUnicode) + { + bRequestSent = HttpSendRequest(hRequest, NULL, 0, pszUnicode, cbUnicode); + if (!bRequestSent) + SetLastErrorNode(pNode, TEXT("HttpSendRequest"), GetLastError()); + GlobalFree(pszUnicode); + } + } + else + { + bRequestSent = HttpSendRequest(hRequest, NULL, 0, pszBuffer, lstrlen(pszBuffer) * sizeof(TCHAR)); + if (!bRequestSent) + SetLastErrorNode(pNode, TEXT("HttpSendRequest"), GetLastError()); + } +#endif + + GlobalFree(pszBuffer); + } + } + else + { + bRequestSent = HttpSendRequest(hRequest, NULL, 0, NULL, 0); + if (!bRequestSent) + SetLastErrorNode(pNode, TEXT("HttpSendRequest"), GetLastError()); + } + + if (bRequestSent) + { + DWORD dwSize; + PTCHAR pszStatusCode = NULL; + + if (InternetQueryDataAvailable(hRequest, &dwSize, 0, 0)) + { + PBYTE pbResponseBuffer = (PBYTE)GlobalAlloc(GPTR, dwSize + 1); + if (pbResponseBuffer) + { + DWORD dwBytesRead = 0; + if (InternetReadFile(hRequest, pbResponseBuffer, dwSize, &dwBytesRead)) + { + JSON_SetEx(pNode, TEXT("Output"), FALSE, pbResponseBuffer, + (JSON_IsTrue(JSON_Get(pRootNode, TEXT("UnicodeOutput"), FALSE)) ? JSF_IS_UNICODE : 0) | + (JSON_IsTrue(JSON_Get(pRootNode, TEXT("RawOutput"), FALSE)) ? JSF_IS_RAW : 0)); + } + else + { + SetLastErrorNode(pNode, TEXT("InternetReadFile"), GetLastError()); + } + + GlobalFree(pbResponseBuffer); + } + } + else + { + SetLastErrorNode(pNode, TEXT("InternetQueryDataAvailable"), GetLastError()); + } + + dwSize = 0; + if (!HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, pszStatusCode, &dwSize, NULL) && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + pszStatusCode = GlobalAlloc(GPTR, (dwSize + 1) * sizeof(TCHAR)); + if (pszStatusCode) + { + if (HttpQueryInfo(hRequest, HTTP_QUERY_STATUS_CODE, pszStatusCode, &dwSize, NULL)) + { +#ifdef UNICODE + JSON_SetEx(pNode, TEXT("StatusCode"), FALSE, (PBYTE)pszStatusCode, JSF_IS_UNICODE); +#else + JSON_SetEx(pNode, TEXT("StatusCode"), FALSE, (PBYTE)pszStatusCode, JSF_NONE); +#endif + } + + GlobalFree(pszStatusCode); + } + } + } + + InternetCloseHandle(hRequest); + } + else + { + SetLastErrorNode(pNode, TEXT("HttpOpenRequest"), GetLastError()); + } + + InternetCloseHandle(hConnect); + } + else + { + SetLastErrorNode(pNode, TEXT("InternetConnect"), GetLastError()); + } + } + else + { + SetLastErrorNode(pNode, TEXT("InternetCrackUrl"), GetLastError()); + } + + GlobalFree(pstUrlComp->lpszHostName); + } + + GlobalFree(pstUrlComp->lpszUrlPath); + } + + if (pParamsNode && pszParams && !bRawParams) + GlobalFree(pszParams); + } + + GlobalFree(pstUrlComp); + } + + InternetCloseHandle(hSession); + } + else + { + SetLastErrorNode(pNode, TEXT("InternetOpen"), GetLastError()); + } + } +} + +static DWORD WINAPI DoHttpWebRequestThreadProc(LPVOID lpParameter) +{ + struct THREAD_PARAM* ptp = (struct THREAD_PARAM*)lpParameter; + DoHttpWebRequest(ptp->pNode, ptp->pRootNode); + GlobalFree(ptp); + return 0; +} + +static void WriteInput(HANDLE hStdIn, struct JSON_NODE* pNode, BOOL bIsUnicode) +{ + DWORD dwBytesWritten; +#ifdef UNICODE + if (bIsUnicode) + { + WriteFile(hStdIn, pNode->pszValue, lstrlen(pNode->pszValue) * sizeof(TCHAR), &dwBytesWritten, NULL); + } + else + { + int cchValue = lstrlen(pNode->pszValue); + PCHAR pszConverted = JSON_FromUnicode(pNode->pszValue, &cchValue, CP_ACP); + if (pszConverted) + { + WriteFile(hStdIn, pszConverted, cchValue * sizeof(TCHAR), &dwBytesWritten, NULL); + GlobalFree(pszConverted); + } + } +#else + if (bIsUnicode) + { + int cchValue = lstrlenA(pNode->pszValue); + PWCHAR pwszConverted = JSON_ToUnicode(pNode->pszValue, &cchValue); + if (pwszConverted) + { + WriteFile(hStdIn, pNode->pszValue, lstrlen(pNode->pszValue) * sizeof(TCHAR), &dwBytesWritten, NULL); + GlobalFree(pwszConverted); + } + } + else + { + WriteFile(hStdIn, pNode->pszValue, lstrlen(pNode->pszValue) * sizeof(TCHAR), &dwBytesWritten, NULL); + } +#endif +} + +static void DoCreateProcess(struct JSON_NODE* pNode, struct JSON_NODE* pRootNode) +{ +#ifdef UNICODE + JSON_SetEx(pNode, TEXT("Output"), FALSE, (PBYTE)TEXT(""), JSF_IS_UNICODE); + JSON_SetEx(pNode, TEXT("ExitCode"), FALSE, (PBYTE)TEXT(""), JSF_IS_UNICODE); +#else + JSON_SetEx(pNode, TEXT("Output"), FALSE, (PBYTE)TEXT(""), JSF_NONE); + JSON_SetEx(pNode, TEXT("ExitCode"), FALSE, (PBYTE)TEXT(""), JSF_NONE); +#endif + + PTCHAR pszPath = JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("Path"), FALSE), NULL); + if (pszPath) + { + HANDLE hStdInRead = NULL, hStdInWrite = NULL, hStdOutRead = NULL, hStdOutWrite = NULL; + PBYTE pbOutputBuffer = (PBYTE)GlobalAlloc(GPTR, g_stringsize); + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if (pbOutputBuffer) + { + if (CreatePipe(&hStdOutRead, &hStdOutWrite, &saAttr, 0) && + SetHandleInformation(hStdOutRead, HANDLE_FLAG_INHERIT, 0) && + CreatePipe(&hStdInRead, &hStdInWrite, &saAttr, 0) && + SetHandleInformation(hStdInWrite, HANDLE_FLAG_INHERIT, 0)) + { + PPROCESS_INFORMATION ppiProcInfo = (PPROCESS_INFORMATION)GlobalAlloc(GPTR, sizeof(PROCESS_INFORMATION)); + if (ppiProcInfo) + { + LPSTARTUPINFO psiStartInfo = (LPSTARTUPINFO)GlobalAlloc(GPTR, sizeof(STARTUPINFO)); + if (psiStartInfo) + { + struct JSON_NODE* pArgumentsNode = JSON_Get(pRootNode, TEXT("Arguments"), FALSE); + PTCHAR pszArguments = NULL; + + if (pArgumentsNode) + { + if (pArgumentsNode->eType == JNT_VALUE || pArgumentsNode->eType == JNT_QUOTED_VALUE) + { + pszArguments = pArgumentsNode->pszValue; + } + else if (pArgumentsNode->eType == JNT_ARRAY) + { + DWORD cchAlloc = 0; + struct JSON_NODE* pArrayNext = pArgumentsNode->pValue; + while (pArrayNext) + { + if (pArrayNext->eType == JNT_VALUE || pArrayNext->eType == JNT_QUOTED_VALUE) + cchAlloc += lstrlen(pArrayNext->pszValue) + 1; + pArrayNext = pArrayNext->pNext; + } + + if (cchAlloc > 0) + { + pszArguments = GlobalAlloc(GPTR, sizeof(TCHAR) * cchAlloc); + if (pszArguments) + { + DWORD dwPos = 0; + + pArrayNext = pArgumentsNode->pValue; + while (pArrayNext) + { + if (pArrayNext->eType == JNT_VALUE || pArrayNext->eType == JNT_QUOTED_VALUE) + { + lstrcpy(pszArguments + dwPos, pArrayNext->pszValue); + dwPos += lstrlen(pArrayNext->pszValue); + lstrcpy(pszArguments + dwPos, TEXT(" ")); + dwPos++; + } + pArrayNext = pArrayNext->pNext; + } + } + } + } + } + + psiStartInfo->cb = sizeof(STARTUPINFO); + psiStartInfo->hStdError = hStdOutWrite; + psiStartInfo->hStdOutput = hStdOutWrite; + psiStartInfo->hStdInput = hStdInRead; + psiStartInfo->dwFlags = STARTF_USESTDHANDLES; + + if (CreateProcess(pszPath, pszArguments, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, JSON_GetQuotedValue(JSON_Get(pRootNode, TEXT("WorkingDir"), FALSE), NULL), psiStartInfo, ppiProcInfo)) + { + struct JSON_NODE* pInputNode = JSON_Get(pRootNode, TEXT("Input"), FALSE); + DWORD dwBytesRead, dwTotalBytesRead = 0, dwValueBufferPos = 0; + PBYTE pbValueBuffer = NULL; + + CloseHandle(ppiProcInfo->hThread); + + if (pInputNode) + { + BOOL bIsUnicode = JSON_IsTrue(JSON_Get(pRootNode, TEXT("UnicodeInput"), FALSE)); + + if (pInputNode->eType == JNT_VALUE || pInputNode->eType == JNT_QUOTED_VALUE) + { + WriteInput(hStdInWrite, pInputNode, bIsUnicode); + } + else if (pInputNode->eType == JNT_ARRAY) + { + struct JSON_NODE* pArrayNext = pArgumentsNode->pValue; + while (pArrayNext) + { + if (pArrayNext->eType == JNT_VALUE || pArrayNext->eType == JNT_QUOTED_VALUE) + WriteInput(hStdInWrite, pArrayNext, bIsUnicode); + pArrayNext = pArrayNext->pNext; + } + } + } + + CloseHandle(hStdInWrite); + hStdInWrite = NULL; + CloseHandle(hStdOutWrite); + hStdOutWrite = NULL; + + while (ReadFile(hStdOutRead, pbOutputBuffer, g_stringsize, &dwBytesRead, NULL) && dwBytesRead != 0) + { + DWORD i; + + dwTotalBytesRead += dwBytesRead; + + if (pbValueBuffer) + { + pbValueBuffer = (PBYTE)GlobalReAlloc(pbValueBuffer, dwTotalBytesRead + sizeof(TCHAR), GMEM_ZEROINIT | GMEM_MOVEABLE); + } + else + { + pbValueBuffer = (PBYTE)GlobalAlloc(GPTR, dwBytesRead + sizeof(TCHAR)); + } + + if (!pbValueBuffer) + { + SetLastErrorNode(pNode, TEXT("DoCreateProcess"), GetLastError()); + break; + } + + for (i = 0; i < dwBytesRead; i++, dwValueBufferPos++) + { + pbValueBuffer[dwValueBufferPos] = pbOutputBuffer[i]; + } + } + + if (pbValueBuffer) + { + JSON_SetEx(pNode, TEXT("Output"), FALSE, pbValueBuffer, + (JSON_IsTrue(JSON_Get(pRootNode, TEXT("UnicodeOutput"), FALSE)) ? JSF_IS_UNICODE : 0) | + (JSON_IsTrue(JSON_Get(pRootNode, TEXT("RawOutput"), FALSE)) ? JSF_IS_RAW : 0)); + } + + if (GetExitCodeProcess(ppiProcInfo->hProcess, &dwBytesRead)) + { + PTCHAR pszExitCode = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * 11); + if (pszExitCode) + { + wsprintf(pszExitCode, TEXT("%lu"), dwBytesRead); +#ifdef UNICODE + JSON_SetEx(pNode, TEXT("ExitCode"), FALSE, (PBYTE)pszExitCode, JSF_IS_UNICODE); +#else + JSON_SetEx(pNode, TEXT("ExitCode"), FALSE, (PBYTE)pszExitCode, JSF_NONE); +#endif + GlobalFree(pszExitCode); + } + } + + CloseHandle(ppiProcInfo->hProcess); + } + + if (pArgumentsNode && pArgumentsNode->eType == JNT_ARRAY && pszArguments) + GlobalFree(pszArguments); + + GlobalFree(psiStartInfo); + } + + GlobalFree(ppiProcInfo); + } + } + + if (hStdInWrite != NULL) + CloseHandle(hStdInWrite); + if (hStdOutWrite != NULL) + CloseHandle(hStdOutWrite); + if (hStdInRead != NULL) + CloseHandle(hStdInRead); + if (hStdOutRead != NULL) + CloseHandle(hStdOutRead); + + GlobalFree(pbOutputBuffer); + } + } +} + +static DWORD WINAPI DoCreateProcessThreadProc(LPVOID lpParameter) +{ + struct THREAD_PARAM* ptp = (struct THREAD_PARAM*)lpParameter; + DoCreateProcess(ptp->pNode, ptp->pRootNode); + GlobalFree(ptp); + return 0; +} + +static BOOL CallFunc(LPTHREAD_START_ROUTINE func, struct JSON_NODE* pNode, PTCHAR pszTree) +{ + struct JSON_NODE* pRootNode = GetTreeNode(pszTree, FALSE); + BOOL bSuccess = FALSE; + + if (pRootNode) + { + struct THREAD_PARAM* ptp = (struct THREAD_PARAM*)GlobalAlloc(GPTR, sizeof(struct THREAD_PARAM)); + if (ptp) + { + ptp->pNode = pNode; + ptp->pRootNode = pRootNode; + + if (JSON_IsTrue(JSON_Get(pRootNode, TEXT("Async"), FALSE))) + { + HANDLE hThread = CreateThread(NULL, 0, func, ptp, 0, NULL); + if (hThread) + { + PTCHAR pszHandle = (PTCHAR)GlobalAlloc(GPTR, g_stringsize * sizeof(TCHAR)); + if (pszHandle) + { +#ifdef _WIN64 + wsprintf(pszHandle, TEXT("%Id"), hThread); +#else + wsprintf(pszHandle, TEXT("%d"), hThread); +#endif + +#ifdef UNICODE + JSON_SetEx(pRootNode, TEXT("Handle"), FALSE, (PBYTE)pszHandle, JSF_IS_UNICODE); +#else + JSON_SetEx(pRootNode, TEXT("Handle"), FALSE, (PBYTE)pszHandle, JSF_NONE); +#endif + GlobalFree(pszHandle); + bSuccess = TRUE; + } + } + else + { + GlobalFree(ptp); + } + } + else if (JSON_IsTrue(JSON_Get(pRootNode, TEXT("UIAsync"), FALSE))) + { + HANDLE hThread = CreateThread(NULL, 0, func, ptp, 0, NULL); + if (hThread) + { + BOOL bLoop = TRUE; + while (bLoop) + { + MSG msg; + if (MsgWaitForMultipleObjectsEx(1, &hThread, INFINITE, QS_ALLINPUT | QS_ALLPOSTMESSAGE, 0) != WAIT_OBJECT_0 + 1) + break; + + while (bLoop && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + if (msg.message == WM_QUIT) + { + PostMessage(msg.hwnd, msg.message, msg.wParam, msg.lParam); + bLoop = FALSE; + } + else + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + } + + CloseHandle(hThread); + bSuccess = TRUE; + } + } + else + { + func(ptp); + bSuccess = TRUE; + } + } + } + + return bSuccess; +} + +NSISFUNC(Serialize) +{ + DLL_INIT(); + { + BOOL bOK = FALSE; + + PTCHAR pszArg = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * string_size); + if (pszArg) + { + BOOL bIsFile = FALSE, bAsUnicode = FALSE, bFormat = FALSE; + struct JSON_NODE* pNode = PopTreeNode(pszArg, FALSE, NULL); + + while (popstring(pszArg) == 0) + { + if (lstrcmpi(pszArg, TEXT("/file")) == 0) + { + bIsFile = TRUE; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/unicode")) == 0) + { + bAsUnicode = TRUE; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/format")) == 0) + { + bFormat = TRUE; + continue; + } + + if (!bIsFile) + pushstring(pszArg); + break; + } + + if (pNode) + { +#ifdef UNICODE + if (!bIsFile) + bAsUnicode = TRUE; +#endif + if (JSON_Serialize(pNode, pszArg, string_size - 1, bIsFile, bAsUnicode, bFormat)) + { + if (!bIsFile) + pushstring(pszArg); + bOK = TRUE; + } + else + { + GetLastErrorMessage(pszArg, TEXT("JSON_Serialize"), GetLastError()); + pushstring(pszArg); + } + } + + GlobalFree(pszArg); + } + + if (!bOK) + extra->exec_flags->exec_error = 1; + } +} + +static void PushKeys(struct JSON_NODE* pNode) +{ + if (pNode->pNext) + PushKeys(pNode->pNext); + + if (pNode->pszKey) + pushstring(pNode->pszKey); + else + pushstring(pNode->pszValue); +} + +#define GET_ACTION_NOEXPAND 1 +#define GET_ACTION_KEY 2 +#define GET_ACTION_KEYS 3 +#define GET_ACTION_TYPE 4 +#define GET_ACTION_EXISTS 5 +#define GET_ACTION_COUNT 6 +#define GET_ACTION_ISEMPTY 7 + +NSISFUNC(Get) +{ + DLL_INIT(); + { + BOOL bOK = FALSE; + + PTCHAR pszArg = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * string_size); + if (pszArg) + { + struct JSON_NODE* pNode = PopTreeNode(pszArg, FALSE, NULL); + struct JSON_NODE* pPrev; + int nAction = 0; + BOOL bKeyIsIndex = FALSE; + + while (popstring(pszArg) == 0) + { + if (lstrcmpi(pszArg, TEXT("/noexpand")) == 0) + { + nAction = GET_ACTION_NOEXPAND; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/key")) == 0) + { + nAction = GET_ACTION_KEY; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/keys")) == 0) + { + nAction = GET_ACTION_KEYS; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/type")) == 0) + { + nAction = GET_ACTION_TYPE; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/exists")) == 0) + { + nAction = GET_ACTION_EXISTS; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/count")) == 0) + { + nAction = GET_ACTION_COUNT; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/isempty")) == 0) + { + nAction = GET_ACTION_ISEMPTY; + continue; + } + + pushstring(pszArg); + break; + } + + while (popstring(pszArg) == 0) + { + if (lstrcmpi(pszArg, TEXT("/end")) == 0) + break; + + if (lstrcmpi(pszArg, TEXT("/index")) == 0) + { + bKeyIsIndex = TRUE; + continue; + } + + if (pNode) + { + pPrev = pNode; + pNode = JSON_Next(&pPrev, pszArg, bKeyIsIndex, FALSE, NULL); + bKeyIsIndex = FALSE; + } + } + + if (pNode) + { + if (nAction == GET_ACTION_ISEMPTY) + { + pushstring(pNode->pValue ? TEXT("no") : TEXT("yes")); + } + else if (nAction == GET_ACTION_COUNT) + { + wsprintf(pszArg, TEXT("%d"), JSON_Count(pNode)); + pushstring(pszArg); + } + else if (nAction == GET_ACTION_EXISTS) + { + pushstring(TEXT("yes")); + } + else if (nAction == GET_ACTION_TYPE) + { + switch (pNode->eType) + { + case JNT_NODE: + pushstring(TEXT("node")); + break; + case JNT_ARRAY: + pushstring(TEXT("array")); + break; + case JNT_VALUE: + pushstring(TEXT("value")); + break; + case JNT_QUOTED_VALUE: + pushstring(TEXT("string")); + break; + } + } + else if (nAction == GET_ACTION_KEY) + { + if (pNode->pszKey) + pushstring(pNode->pszKey); + else + pushstring(pNode->pszValue); + } + else if (nAction == GET_ACTION_KEYS) + { + if (pNode->eType == JNT_NODE) + { + PushKeys(pNode->pValue); + wsprintf(pszArg, TEXT("%d"), JSON_Count(pNode)); + pushstring(pszArg); + } + else + { + pushstring(TEXT("0")); + } + } + else if (pNode->eType == JNT_VALUE) + { + pushstring(pNode->pszValue); + } + else if (pNode->eType == JNT_QUOTED_VALUE) + { + if (nAction == GET_ACTION_NOEXPAND) + { + pushstring(pNode->pszValue); + } + else + { + PTCHAR pszExpanded = JSON_Expand(pNode); + if (pszExpanded) + { + pushstring(pszExpanded); + GlobalFree(pszExpanded); + } + } + } + else if (pNode->pValue) + { + if (JSON_Serialize(pNode->pValue, pszArg, string_size - 1, FALSE, FALSE, FALSE)) + { + pushstring(pszArg); + bOK = TRUE; + } + else + { + GetLastErrorMessage(pszArg, TEXT("JSON_Serialize"), GetLastError()); + pushstring(pszArg); + } + } + else + { + pushstring(TEXT("")); + } + + bOK = TRUE; + } + else if (nAction == GET_ACTION_EXISTS) + { + pushstring(TEXT("no")); + bOK = TRUE; + } + else if (nAction == GET_ACTION_TYPE) + { + pushstring(TEXT("")); + bOK = TRUE; + } + else if (nAction == GET_ACTION_KEYS) + { + pushstring(TEXT("0")); + bOK = TRUE; + } + + GlobalFree(pszArg); + } + + if (!bOK) + extra->exec_flags->exec_error = 1; + } +} + +NSISFUNC(Set) +{ + DLL_INIT(); + { + BOOL bOK = FALSE; + + PTCHAR pszArg = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * string_size); + if (pszArg) + { + PTCHAR pszTree = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * string_size); + if (pszTree) + { + BOOL bCreated; + struct JSON_NODE* pTreeNode = PopTreeNode(pszTree, TRUE, &bCreated); + struct JSON_NODE* pNode = pTreeNode; + struct JSON_NODE* pPrev = NULL; + BOOL bKeyIsIndex = FALSE; + + while (popstring(pszArg) == 0) + { + if (lstrcmpi(pszArg, TEXT("/index")) == 0) + { + bKeyIsIndex = TRUE; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/value")) == 0) + { + if (popstring(pszArg) == 0 && pNode) + { +#ifdef UNICODE + if (JSON_Set(pNode, (PBYTE)pszArg, JSF_IS_UNICODE)) +#else + if (JSON_Set(pNode, (PBYTE)pszArg, JSF_NONE)) +#endif + bOK = TRUE; + } + break; + } + + if (lstrcmpi(pszArg, TEXT("/file")) == 0) + { + if (popstring(pszArg) == 0) + { + if (lstrcmpi(pszArg, TEXT("/unicode")) == 0) + { + if (popstring(pszArg) == 0 && pNode) + { + if (JSON_Set(pNode, (PBYTE)pszArg, JSF_IS_FILE | JSF_IS_UNICODE)) + bOK = TRUE; + } + } + else + { + if (JSON_Set(pNode, (PBYTE)pszArg, JSF_IS_FILE)) + bOK = TRUE; + } + } + break; + } + + if (lstrcmpi(pszArg, TEXT("/http")) == 0) + { + if (popstring(pszArg) == 0 && pNode) + { + if (CallFunc(DoHttpWebRequestThreadProc, pNode, pszArg)) + bOK = TRUE; + } + break; + } + + if (lstrcmpi(pszArg, TEXT("/exec")) == 0) + { + if (popstring(pszArg) == 0 && pNode) + { + if (CallFunc(DoCreateProcessThreadProc, pNode, pszArg)) + bOK = TRUE; + } + break; + } + + if (pNode) + { + pPrev = pNode; + pNode = JSON_Next(&pPrev, pszArg, bKeyIsIndex, TRUE, &bCreated); + bKeyIsIndex = FALSE; + } + } + + if (!bOK && pNode && bCreated) + { + if (pNode == pTreeNode) + LinkedListDelete(&g_pList, pszTree); + JSON_Delete(&pNode, pPrev); + } + + GlobalFree(pszTree); + } + + GlobalFree(pszArg); + } + + if (!bOK) + extra->exec_flags->exec_error = 1; + } +} + +NSISFUNC(Delete) +{ + DLL_INIT(); + { + BOOL bOK = FALSE; + + PTCHAR pszArg = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * string_size); + if (pszArg) + { + PTCHAR pszTree = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * string_size); + if (pszTree) + { + struct JSON_NODE* pTreeNode = PopTreeNode(pszTree, FALSE, NULL); + struct JSON_NODE* pNode = pTreeNode; + struct JSON_NODE* pPrev = NULL; + BOOL bKeyIsIndex = FALSE; + + while (popstring(pszArg) == 0) + { + if (lstrcmpi(pszArg, TEXT("/end")) == 0) + break; + + if (lstrcmpi(pszArg, TEXT("/index")) == 0) + { + bKeyIsIndex = TRUE; + continue; + } + + if (pNode) + { + pPrev = pNode; + pNode = JSON_Next(&pPrev, pszArg, bKeyIsIndex, FALSE, NULL); + bKeyIsIndex = FALSE; + } + } + + if (pNode) + { + if (pNode == pTreeNode) + LinkedListDelete(&g_pList, pszTree); + JSON_Delete(&pNode, pPrev); + bOK = TRUE; + } + + GlobalFree(pszTree); + } + + GlobalFree(pszArg); + } + + if (!bOK) + extra->exec_flags->exec_error = 1; + } +} + +NSISFUNC(Quote) +{ + DLL_INIT(); + { + BOOL bOK = FALSE; + + PTCHAR pszArg = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * string_size); + if (pszArg) + { + enum JSON_ESCAPE_FLAGS eFlags = JEF_QUOTE; + + while (popstring(pszArg) == 0) + { + if (lstrcmpi(pszArg, TEXT("/unicode")) == 0) + { + eFlags |= JEF_ESCAPE_UNICODE; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/always")) == 0) + { + eFlags |= JEF_ALWAYS_QUOTE; + continue; + } + + pushstring(pszArg); + break; + } + + if (popstring(pszArg) == 0) + { + PTCHAR pszQuoted = JSON_Escape(pszArg, eFlags); + if (pszQuoted) + { + pushstring(pszQuoted); + GlobalFree(pszQuoted); + bOK = TRUE; + } + } + + GlobalFree(pszArg); + } + + if (!bOK) + extra->exec_flags->exec_error = 1; + } +} + +NSISFUNC(Wait) +{ + DLL_INIT(); + { + BOOL bOK = FALSE; + + PTCHAR pszArg = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * string_size); + if (pszArg) + { + if (popstring(pszArg) == 0) + { + struct JSON_NODE* pNode = GetTreeNode(pszArg, FALSE); + if (pNode) + { + struct JSON_NODE* pPrev = pNode; + struct JSON_NODE* pHandleNode = JSON_Next(&pPrev, TEXT("Handle"), FALSE, FALSE, NULL); + if (pHandleNode && pHandleNode->eType == JNT_VALUE) + { + HANDLE hThread = (HANDLE)nsishelper_str_to_ptr(pHandleNode->pszValue); + if (hThread) + { + int nTimeout = -1; + + while (popstring(pszArg) == 0) + { + if (lstrcmpi(pszArg, TEXT("/timeout")) == 0) + { + if (popstring(pszArg) == 0) + nTimeout = myatoi(pszArg); + continue; + } + + pushstring(pszArg); + break; + } + + if (nTimeout >= 0) + { + if (WaitForSingleObject(hThread, nTimeout) == WAIT_TIMEOUT) + { + pushstring(TEXT("wait")); + } + else + { + pushstring(TEXT("")); + CloseHandle(hThread); + JSON_Delete(&pHandleNode, pPrev); + } + } + else + { + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + JSON_Delete(&pHandleNode, pPrev); + } + + bOK = TRUE; + } + } + } + } + + GlobalFree(pszArg); + } + + if (!bOK) + extra->exec_flags->exec_error = 1; + } +} + +NSISFUNC(Sort) +{ + DLL_INIT(); + { + BOOL bOK = FALSE; + + PTCHAR pszArg = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR) * string_size); + if (pszArg) + { + struct JSON_NODE* pNode = PopTreeNode(pszArg, FALSE, NULL); + struct JSON_NODE* pPrev = NULL; + BOOL bKeyIsIndex = FALSE; + enum JSON_SORT_FLAGS eFlags = 0; + + while (popstring(pszArg) == 0) + { + if (lstrcmpi(pszArg, TEXT("/end")) == 0) + break; + + if (lstrcmpi(pszArg, TEXT("/index")) == 0) + { + bKeyIsIndex = TRUE; + continue; + } + + if (lstrcmpi(pszArg, TEXT("/options")) == 0) + { + eFlags = (enum JSON_SORT_FLAGS)popint(); + continue; + } + + if (pNode) + { + pPrev = pNode; + pNode = JSON_Next(&pPrev, pszArg, bKeyIsIndex, FALSE, NULL); + bKeyIsIndex = FALSE; + } + } + + if (pNode) + { + JSON_Sort(pNode, eFlags); + bOK = TRUE; + } + + GlobalFree(pszArg); + } + + if (!bOK) + extra->exec_flags->exec_error = 1; + } +} \ No newline at end of file diff --git a/src/nsJSON.h b/src/nsJSON.h new file mode 100644 index 0000000..a521d3d --- /dev/null +++ b/src/nsJSON.h @@ -0,0 +1,7 @@ +#ifndef __NSJSON_H__ +#define __NSJSON_H__ + +#define NSISFUNC(name) void __declspec(dllexport) name(HWND hWndParent, int string_size, TCHAR* variables, stack_t** stacktop, extra_parameters* extra) +#define DLL_INIT() EXDLL_INIT(); extra->RegisterPluginCallback((HMODULE)g_hInstance, PluginCallback) + +#endif \ No newline at end of file diff --git a/src/nsJSON.rc b/src/nsJSON.rc new file mode 100644 index 0000000..57233df Binary files /dev/null and b/src/nsJSON.rc differ diff --git a/src/nsJSON.sln b/src/nsJSON.sln new file mode 100644 index 0000000..9a2466a --- /dev/null +++ b/src/nsJSON.sln @@ -0,0 +1,97 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsJSON", "nsJSON.vcxproj", "{EC3375F9-B492-470B-8EA6-8F5F4A804BB7}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ConsoleApp", "ConsoleApp\ConsoleApp.vcxproj", "{6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleAppDotNet", "ConsoleAppDotNet\ConsoleAppDotNet.csproj", "{DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug Unicode|Any CPU = Debug Unicode|Any CPU + Debug Unicode|Win32 = Debug Unicode|Win32 + Debug Unicode|x64 = Debug Unicode|x64 + Debug|Any CPU = Debug|Any CPU + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release Unicode|Any CPU = Release Unicode|Any CPU + Release Unicode|Win32 = Release Unicode|Win32 + Release Unicode|x64 = Release Unicode|x64 + Release|Any CPU = Release|Any CPU + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Debug Unicode|Any CPU.ActiveCfg = Debug Unicode|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Debug Unicode|Win32.ActiveCfg = Debug Unicode|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Debug Unicode|Win32.Build.0 = Debug Unicode|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Debug Unicode|x64.ActiveCfg = Debug Unicode|x64 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Debug Unicode|x64.Build.0 = Debug Unicode|x64 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Debug|Win32.ActiveCfg = Debug|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Debug|Win32.Build.0 = Debug|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Debug|x64.ActiveCfg = Debug|x64 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Debug|x64.Build.0 = Debug|x64 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Release Unicode|Any CPU.ActiveCfg = Release Unicode|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Release Unicode|Win32.ActiveCfg = Release Unicode|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Release Unicode|Win32.Build.0 = Release Unicode|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Release Unicode|x64.ActiveCfg = Release Unicode|x64 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Release Unicode|x64.Build.0 = Release Unicode|x64 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Release|Any CPU.ActiveCfg = Release|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Release|Win32.ActiveCfg = Release|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Release|Win32.Build.0 = Release|Win32 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Release|x64.ActiveCfg = Release|x64 + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7}.Release|x64.Build.0 = Release|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug Unicode|Any CPU.ActiveCfg = Release|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug Unicode|Any CPU.Build.0 = Release|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug Unicode|Win32.ActiveCfg = Debug|Win32 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug Unicode|Win32.Build.0 = Debug|Win32 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug Unicode|x64.ActiveCfg = Debug|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug Unicode|x64.Build.0 = Debug|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug|Win32.ActiveCfg = Debug|Win32 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug|Win32.Build.0 = Debug|Win32 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug|x64.ActiveCfg = Debug|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Debug|x64.Build.0 = Debug|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release Unicode|Any CPU.ActiveCfg = Release|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release Unicode|Any CPU.Build.0 = Release|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release Unicode|Win32.ActiveCfg = Release|Win32 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release Unicode|Win32.Build.0 = Release|Win32 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release Unicode|x64.ActiveCfg = Release|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release Unicode|x64.Build.0 = Release|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release|Any CPU.ActiveCfg = Release|Win32 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release|Win32.ActiveCfg = Release|Win32 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release|Win32.Build.0 = Release|Win32 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release|x64.ActiveCfg = Release|x64 + {6ECB05AC-F1A5-41F9-8880-431EEB8E2D7D}.Release|x64.Build.0 = Release|x64 + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug Unicode|Any CPU.ActiveCfg = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug Unicode|Any CPU.Build.0 = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug Unicode|Win32.ActiveCfg = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug Unicode|Win32.Build.0 = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug Unicode|x64.ActiveCfg = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug Unicode|x64.Build.0 = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug|Win32.ActiveCfg = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug|Win32.Build.0 = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug|x64.ActiveCfg = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Debug|x64.Build.0 = Debug|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release Unicode|Any CPU.ActiveCfg = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release Unicode|Any CPU.Build.0 = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release Unicode|Win32.ActiveCfg = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release Unicode|Win32.Build.0 = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release Unicode|x64.ActiveCfg = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release Unicode|x64.Build.0 = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release|Any CPU.Build.0 = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release|Win32.ActiveCfg = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release|Win32.Build.0 = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release|x64.ActiveCfg = Release|Any CPU + {DBAA5865-5EF1-4A17-BC19-B68D5E5C13F8}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/nsJSON.vcxproj b/src/nsJSON.vcxproj new file mode 100644 index 0000000..854b2fe --- /dev/null +++ b/src/nsJSON.vcxproj @@ -0,0 +1,449 @@ + + + + + Debug Unicode + Win32 + + + Debug Unicode + x64 + + + Debug + Win32 + + + Debug + x64 + + + Release Unicode + Win32 + + + Release Unicode + x64 + + + Release + Win32 + + + Release + x64 + + + + {EC3375F9-B492-470B-8EA6-8F5F4A804BB7} + 10.0.15063.0 + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + MultiByte + v140 + + + DynamicLibrary + MultiByte + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + MultiByte + v140 + + + DynamicLibrary + MultiByte + v140 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)..\..\Plugins\x86-ansi\ + false + false + $(SolutionDir)..\..\Plugins\x86-ansi\ + $(SolutionDir)..\..\Plugins\x86-unicode\ + false + false + false + false + $(SolutionDir)..\..\Plugins\x86-unicode\ + false + false + + + $(SolutionDir)..\..\Plugins\amd64-unicode\ + + + $(SolutionDir)..\..\Plugins\amd64-unicode\ + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + + + + + MultiThreadedDLL + false + + + Level3 + true + MinSpace + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + DllMain + + + MachineX86 + false + wininet.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + No + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + + + + + MultiThreadedDLL + false + + + Level3 + true + MinSpace + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + DllMain + + + false + wininet.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + No + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + + + + + WIN32;_DEBUG;_WINDOWS;_USRDLL;nsJSON_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + + + Level3 + true + EditAndContinue + Disabled + MultiThreadedDebug + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + MachineX86 + + + false + wininet.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + + + + + WIN32;_DEBUG;_WINDOWS;_USRDLL;nsJSON_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + + + Level3 + true + ProgramDatabase + Disabled + MultiThreadedDebug + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + false + wininet.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + + + + + WIN32;_DEBUG;_WINDOWS;_USRDLL;nsJSON_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + + + Level3 + true + EditAndContinue + Disabled + MultiThreadedDebug + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + MachineX86 + + + wininet.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + + + + + WIN32;_DEBUG;_WINDOWS;_USRDLL;nsJSON_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + + + Level3 + true + ProgramDatabase + Disabled + MultiThreadedDebug + + + _DEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + + + + + wininet.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + + + + + MultiThreadedDLL + false + + + MinSpace + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + DllMain + + + MachineX86 + false + wininet.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + No + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + + + + + MultiThreadedDLL + false + + + MinSpace + + + NDEBUG;%(PreprocessorDefinitions) + 0x0409 + + + true + DllMain + + + false + wininet.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + No + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nsJSON.vcxproj.filters b/src/nsJSON.vcxproj.filters new file mode 100644 index 0000000..77298fe --- /dev/null +++ b/src/nsJSON.vcxproj.filters @@ -0,0 +1,56 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/nsis_tchar.h b/src/nsis_tchar.h new file mode 100644 index 0000000..210bab1 --- /dev/null +++ b/src/nsis_tchar.h @@ -0,0 +1,229 @@ +/* + * nsis_tchar.h + * + * This file is a part of NSIS. + * + * Copyright (C) 1999-2013 Nullsoft and Contributors + * + * This software is provided 'as-is', without any express or implied + * warranty. + * + * For Unicode support by Jim Park -- 08/30/2007 + */ + +// Jim Park: Only those we use are listed here. + +#pragma once + +#ifdef _UNICODE + +#ifndef _T +#define __T(x) L ## x +#define _T(x) __T(x) +#define _TEXT(x) __T(x) +#endif + +#ifndef _TCHAR_DEFINED +#define _TCHAR_DEFINED +#if !defined(_NATIVE_WCHAR_T_DEFINED) && !defined(_WCHAR_T_DEFINED) +typedef unsigned short TCHAR; +#else +typedef wchar_t TCHAR; +#endif +#endif + + +// program +#define _tenviron _wenviron +#define __targv __wargv + +// printfs +#define _ftprintf fwprintf +#define _sntprintf _snwprintf +#if (defined(_MSC_VER) && (_MSC_VER<=1310)) || defined(__MINGW32__) +# define _stprintf swprintf +#else +# define _stprintf _swprintf +#endif +#define _tprintf wprintf +#define _vftprintf vfwprintf +#define _vsntprintf _vsnwprintf +#if defined(_MSC_VER) && (_MSC_VER<=1310) +# define _vstprintf vswprintf +#else +# define _vstprintf _vswprintf +#endif + +// scanfs +#define _tscanf wscanf +#define _stscanf swscanf + +// string manipulations +#define _tcscat wcscat +#define _tcschr wcschr +#define _tcsclen wcslen +#define _tcscpy wcscpy +#define _tcsdup _wcsdup +#define _tcslen wcslen +#define _tcsnccpy wcsncpy +#define _tcsncpy wcsncpy +#define _tcsrchr wcsrchr +#define _tcsstr wcsstr +#define _tcstok wcstok + +// string comparisons +#define _tcscmp wcscmp +#define _tcsicmp _wcsicmp +#define _tcsncicmp _wcsnicmp +#define _tcsncmp wcsncmp +#define _tcsnicmp _wcsnicmp + +// upper / lower +#define _tcslwr _wcslwr +#define _tcsupr _wcsupr +#define _totlower towlower +#define _totupper towupper + +// conversions to numbers +#define _tcstoi64 _wcstoi64 +#define _tcstol wcstol +#define _tcstoul wcstoul +#define _tstof _wtof +#define _tstoi _wtoi +#define _tstoi64 _wtoi64 +#define _ttoi _wtoi +#define _ttoi64 _wtoi64 +#define _ttol _wtol + +// conversion from numbers to strings +#define _itot _itow +#define _ltot _ltow +#define _i64tot _i64tow +#define _ui64tot _ui64tow + +// file manipulations +#define _tfopen _wfopen +#define _topen _wopen +#define _tremove _wremove +#define _tunlink _wunlink + +// reading and writing to i/o +#define _fgettc fgetwc +#define _fgetts fgetws +#define _fputts fputws +#define _gettchar getwchar + +// directory +#define _tchdir _wchdir + +// environment +#define _tgetenv _wgetenv +#define _tsystem _wsystem + +// time +#define _tcsftime wcsftime + +#else // ANSI + +#ifndef _T +#define _T(x) x +#define _TEXT(x) x +#endif + +#ifndef _TCHAR_DEFINED +#define _TCHAR_DEFINED +typedef char TCHAR; +#endif + +// program +#define _tenviron environ +#define __targv __argv + +// printfs +#define _ftprintf fprintf +#define _sntprintf _snprintf +#define _stprintf sprintf +#define _tprintf printf +#define _vftprintf vfprintf +#define _vsntprintf _vsnprintf +#define _vstprintf vsprintf + +// scanfs +#define _tscanf scanf +#define _stscanf sscanf + +// string manipulations +#define _tcscat strcat +#define _tcschr strchr +#define _tcsclen strlen +#define _tcscnlen strnlen +#define _tcscpy strcpy +#define _tcsdup _strdup +#define _tcslen strlen +#define _tcsnccpy strncpy +#define _tcsrchr strrchr +#define _tcsstr strstr +#define _tcstok strtok + +// string comparisons +#define _tcscmp strcmp +#define _tcsicmp _stricmp +#define _tcsncmp strncmp +#define _tcsncicmp _strnicmp +#define _tcsnicmp _strnicmp + +// upper / lower +#define _tcslwr _strlwr +#define _tcsupr _strupr + +#define _totupper toupper +#define _totlower tolower + +// conversions to numbers +#define _tcstol strtol +#define _tcstoul strtoul +#define _tstof atof +#define _tstoi atoi +#define _tstoi64 _atoi64 +#define _tstoi64 _atoi64 +#define _ttoi atoi +#define _ttoi64 _atoi64 +#define _ttol atol + +// conversion from numbers to strings +#define _i64tot _i64toa +#define _itot _itoa +#define _ltot _ltoa +#define _ui64tot _ui64toa + +// file manipulations +#define _tfopen fopen +#define _topen _open +#define _tremove remove +#define _tunlink _unlink + +// reading and writing to i/o +#define _fgettc fgetc +#define _fgetts fgets +#define _fputts fputs +#define _gettchar getchar + +// directory +#define _tchdir _chdir + +// environment +#define _tgetenv getenv +#define _tsystem system + +// time +#define _tcsftime strftime + +#endif + +// is functions (the same in Unicode / ANSI) +#define _istgraph isgraph +#define _istascii __isascii + +#define __TFILE__ _T(__FILE__) +#define __TDATE__ _T(__DATE__) +#define __TTIME__ _T(__TIME__) diff --git a/src/pluginapi.c b/src/pluginapi.c new file mode 100644 index 0000000..c507e31 --- /dev/null +++ b/src/pluginapi.c @@ -0,0 +1,294 @@ +#include + +#include "pluginapi.h" + +#ifdef _countof +#define COUNTOF _countof +#else +#define COUNTOF(a) (sizeof(a)/sizeof(a[0])) +#endif + +unsigned int g_stringsize; +stack_t **g_stacktop; +TCHAR *g_variables; + +// utility functions (not required but often useful) + +int NSISCALL popstring(TCHAR *str) +{ + stack_t *th; + if (!g_stacktop || !*g_stacktop) return 1; + th=(*g_stacktop); + if (str) lstrcpy(str,th->text); + *g_stacktop = th->next; + GlobalFree((HGLOBAL)th); + return 0; +} + +int NSISCALL popstringn(TCHAR *str, int maxlen) +{ + stack_t *th; + if (!g_stacktop || !*g_stacktop) return 1; + th=(*g_stacktop); + if (str) lstrcpyn(str,th->text,maxlen?maxlen:g_stringsize); + *g_stacktop = th->next; + GlobalFree((HGLOBAL)th); + return 0; +} + +void NSISCALL 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; +} + +TCHAR* NSISCALL getuservariable(const int varnum) +{ + if (varnum < 0 || varnum >= __INST_LAST) return NULL; + return g_variables+varnum*g_stringsize; +} + +void NSISCALL setuservariable(const int varnum, const TCHAR *var) +{ + if (var != NULL && varnum >= 0 && varnum < __INST_LAST) + lstrcpy(g_variables + varnum*g_stringsize, var); +} + +#ifdef _UNICODE +int NSISCALL PopStringA(char* ansiStr) +{ + wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, g_stringsize*sizeof(wchar_t)); + int rval = popstring(wideStr); + WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL); + GlobalFree((HGLOBAL)wideStr); + return rval; +} + +int NSISCALL PopStringNA(char* ansiStr, int maxlen) +{ + int realLen = maxlen ? maxlen : g_stringsize; + wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, realLen*sizeof(wchar_t)); + int rval = popstringn(wideStr, realLen); + WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, realLen, NULL, NULL); + GlobalFree((HGLOBAL)wideStr); + return rval; +} + +void NSISCALL PushStringA(const char* ansiStr) +{ + wchar_t* wideStr = (wchar_t*) GlobalAlloc(GPTR, g_stringsize*sizeof(wchar_t)); + MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize); + pushstring(wideStr); + GlobalFree((HGLOBAL)wideStr); + return; +} + +void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr) +{ + lstrcpyW(wideStr, getuservariable(varnum)); +} + +void NSISCALL GetUserVariableA(const int varnum, char* ansiStr) +{ + wchar_t* wideStr = getuservariable(varnum); + WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL); +} + +void NSISCALL SetUserVariableA(const int varnum, const char* ansiStr) +{ + if (ansiStr != NULL && varnum >= 0 && varnum < __INST_LAST) + { + wchar_t* wideStr = g_variables + varnum * g_stringsize; + MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize); + } +} + +#else +// ANSI defs +int NSISCALL PopStringW(wchar_t* wideStr) +{ + char* ansiStr = (char*) GlobalAlloc(GPTR, g_stringsize); + int rval = popstring(ansiStr); + MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize); + GlobalFree((HGLOBAL)ansiStr); + return rval; +} + +int NSISCALL PopStringNW(wchar_t* wideStr, int maxlen) +{ + int realLen = maxlen ? maxlen : g_stringsize; + char* ansiStr = (char*) GlobalAlloc(GPTR, realLen); + int rval = popstringn(ansiStr, realLen); + MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, realLen); + GlobalFree((HGLOBAL)ansiStr); + return rval; +} + +void NSISCALL PushStringW(wchar_t* wideStr) +{ + char* ansiStr = (char*) GlobalAlloc(GPTR, g_stringsize); + WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL); + pushstring(ansiStr); + GlobalFree((HGLOBAL)ansiStr); +} + +void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr) +{ + char* ansiStr = getuservariable(varnum); + MultiByteToWideChar(CP_ACP, 0, ansiStr, -1, wideStr, g_stringsize); +} + +void NSISCALL GetUserVariableA(const int varnum, char* ansiStr) +{ + lstrcpyA(ansiStr, getuservariable(varnum)); +} + +void NSISCALL SetUserVariableW(const int varnum, const wchar_t* wideStr) +{ + if (wideStr != NULL && varnum >= 0 && varnum < __INST_LAST) + { + char* ansiStr = g_variables + varnum * g_stringsize; + WideCharToMultiByte(CP_ACP, 0, wideStr, -1, ansiStr, g_stringsize, NULL, NULL); + } +} +#endif + +// playing with integers + +INT_PTR NSISCALL nsishelper_str_to_ptr(const TCHAR *s) +{ + INT_PTR v=0; + if (*s == _T('0') && (s[1] == _T('x') || s[1] == _T('X'))) + { + s++; + for (;;) + { + int c=*(++s); + if (c >= _T('0') && c <= _T('9')) c-=_T('0'); + else if (c >= _T('a') && c <= _T('f')) c-=_T('a')-10; + else if (c >= _T('A') && c <= _T('F')) c-=_T('A')-10; + else break; + v<<=4; + v+=c; + } + } + else if (*s == _T('0') && s[1] <= _T('7') && s[1] >= _T('0')) + { + for (;;) + { + int c=*(++s); + if (c >= _T('0') && c <= _T('7')) c-=_T('0'); + else break; + v<<=3; + v+=c; + } + } + else + { + int sign=0; + if (*s == _T('-')) sign++; else s--; + for (;;) + { + int c=*(++s) - _T('0'); + if (c < 0 || c > 9) break; + v*=10; + v+=c; + } + if (sign) v = -v; + } + + return v; +} + +unsigned int NSISCALL myatou(const TCHAR *s) +{ + unsigned int v=0; + + for (;;) + { + unsigned int c=*s++; + if (c >= _T('0') && c <= _T('9')) c-=_T('0'); + else break; + v*=10; + v+=c; + } + return v; +} + +int NSISCALL myatoi_or(const TCHAR *s) +{ + int v=0; + if (*s == _T('0') && (s[1] == _T('x') || s[1] == _T('X'))) + { + s++; + for (;;) + { + int c=*(++s); + if (c >= _T('0') && c <= _T('9')) c-=_T('0'); + else if (c >= _T('a') && c <= _T('f')) c-=_T('a')-10; + else if (c >= _T('A') && c <= _T('F')) c-=_T('A')-10; + else break; + v<<=4; + v+=c; + } + } + else if (*s == _T('0') && s[1] <= _T('7') && s[1] >= _T('0')) + { + for (;;) + { + int c=*(++s); + if (c >= _T('0') && c <= _T('7')) c-=_T('0'); + else break; + v<<=3; + v+=c; + } + } + else + { + int sign=0; + if (*s == _T('-')) sign++; else s--; + for (;;) + { + int c=*(++s) - _T('0'); + if (c < 0 || c > 9) break; + v*=10; + v+=c; + } + if (sign) v = -v; + } + + // Support for simple ORed expressions + if (*s == _T('|')) + { + v |= myatoi_or(s+1); + } + + return v; +} + +INT_PTR NSISCALL popintptr() +{ + TCHAR buf[128]; + if (popstringn(buf,COUNTOF(buf))) + return 0; + return nsishelper_str_to_ptr(buf); +} + +int NSISCALL popint_or() +{ + TCHAR buf[128]; + if (popstringn(buf,COUNTOF(buf))) + return 0; + return myatoi_or(buf); +} + +void NSISCALL pushintptr(INT_PTR value) +{ + TCHAR buffer[30]; + wsprintf(buffer, sizeof(void*) > 4 ? _T("%Id") : _T("%d"), value); + pushstring(buffer); +} diff --git a/src/pluginapi.h b/src/pluginapi.h new file mode 100644 index 0000000..ca671a8 --- /dev/null +++ b/src/pluginapi.h @@ -0,0 +1,104 @@ +#ifndef ___NSIS_PLUGIN__H___ +#define ___NSIS_PLUGIN__H___ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "api.h" +#include "nsis_tchar.h" + +#ifndef NSISCALL +# define NSISCALL __stdcall +#endif + +#define EXDLL_INIT() { \ + g_stringsize=string_size; \ + g_stacktop=stacktop; \ + g_variables=variables; } + +typedef struct _stack_t { + struct _stack_t *next; + TCHAR text[1]; // this should be the length of string_size +} stack_t; + +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 +}; + +extern unsigned int g_stringsize; +extern stack_t **g_stacktop; +extern TCHAR *g_variables; + +void NSISCALL pushstring(const TCHAR *str); +void NSISCALL pushintptr(INT_PTR value); +#define pushint(v) pushintptr((INT_PTR)(v)) +int NSISCALL popstring(TCHAR *str); // 0 on success, 1 on empty stack +int NSISCALL popstringn(TCHAR *str, int maxlen); // with length limit, pass 0 for g_stringsize +INT_PTR NSISCALL popintptr(); +#define popint() ( (int) popintptr() ) +int NSISCALL popint_or(); // with support for or'ing (2|4|8) +INT_PTR NSISCALL nsishelper_str_to_ptr(const TCHAR *s); +#define myatoi(s) ( (int) nsishelper_str_to_ptr(s) ) // converts a string to an integer +unsigned int NSISCALL myatou(const TCHAR *s); // converts a string to an unsigned integer, decimal only +int NSISCALL myatoi_or(const TCHAR *s); // with support for or'ing (2|4|8) +TCHAR* NSISCALL getuservariable(const int varnum); +void NSISCALL setuservariable(const int varnum, const TCHAR *var); + +#ifdef _UNICODE +#define PopStringW(x) popstring(x) +#define PushStringW(x) pushstring(x) +#define SetUserVariableW(x,y) setuservariable(x,y) + +int NSISCALL PopStringA(char* ansiStr); +void NSISCALL PushStringA(const char* ansiStr); +void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr); +void NSISCALL GetUserVariableA(const int varnum, char* ansiStr); +void NSISCALL SetUserVariableA(const int varnum, const char* ansiStr); + +#else +// ANSI defs + +#define PopStringA(x) popstring(x) +#define PushStringA(x) pushstring(x) +#define SetUserVariableA(x,y) setuservariable(x,y) + +int NSISCALL PopStringW(wchar_t* wideStr); +void NSISCALL PushStringW(wchar_t* wideStr); +void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr); +void NSISCALL GetUserVariableA(const int varnum, char* ansiStr); +void NSISCALL SetUserVariableW(const int varnum, const wchar_t* wideStr); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif//!___NSIS_PLUGIN__H___ diff --git a/src/resource.h b/src/resource.h new file mode 100644 index 0000000..97a43f8 Binary files /dev/null and b/src/resource.h differ