diff --git a/.gitignore b/.gitignore index c4515c8..cb1bc06 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ build/ .cache compile_commands.json .ccls + +# local helper script +copy.sh diff --git a/.gitmodules b/.gitmodules index ca68aaa..a908805 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "third_party/subhook"] path = third_party/subhook - url = https://github.com/Zeex/subhook.git + url = https://github.com/Dasharo/subhook.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ddeff9..f004499 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.10) project (DarkTanos) +set(API_VERSION 9) + set(CMAKE_CXX_STANDARD 17) set(CMAKE_BUILD_TYPE Debug) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -11,6 +13,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(SUBHOOK_STATIC ON) set(SUBHOOK_TESTS OFF) +include_directories(${CMAKE_SOURCE_DIR}/tools) add_subdirectory(client/) add_subdirectory(do_lib/) diff --git a/README.md b/README.md new file mode 100644 index 0000000..93a5c3f --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +# Tanos API lib build and test guide + +## Goal +Build all required artifacts with one command: + +```bash +./build.sh -b +``` + +Expected output files: + +- `browser/dist/darkbot_browser_linux.AppImage` +- `build/client/DarkTanos.so` +- `build/do_lib/libdo_lib.so` + +## Prerequisites (Linux) + +- `cmake` and a C++ toolchain (`gcc/g++`, `make`) +- `strip` (usually from `binutils`) +- `nvm` installed (`~/.nvm/nvm.sh` must exist) +- Node.js version from `browser/.nvmrc` (the script runs `nvm use || nvm install`) +- `npm` + +## Build libs + browser + +From repo root: + +```bash +./build.sh -b +``` + +What this does: + +1. Builds browser AppImage in `browser/dist/` +2. Configures/builds CMake project in `build/` +3. Renames `build/client/libDarkTanos.so` to `build/client/DarkTanos.so` +4. Strips symbols from `DarkTanos.so` and `libdo_lib.so` +5. Runs `./copy.sh` automatically if that file exists and is executable + +## Set executable permission for AppImage + +```bash +chmod +x browser/dist/darkbot_browser_linux.AppImage +``` + +## Quick verification / smoke test + +Check artifacts exist: + +```bash +ls -lh \ + browser/dist/darkbot_browser_linux.AppImage \ + build/client/DarkTanos.so \ + build/do_lib/libdo_lib.so +``` + +Check library dependencies resolve: + +```bash +ldd build/client/DarkTanos.so +ldd build/do_lib/libdo_lib.so +``` + +Optional AppImage smoke check: + +```bash +./browser/dist/darkbot_browser_linux.AppImage --appimage-version +``` + +## DarkBot build requirements + +Your DarkBot base must be: + +- Latest changes from: +- Including PR: +- Including PR: +- Including PR: + +After building, copy these files into your DarkBot `lib` directory: + +- `browser/dist/darkbot_browser_linux.AppImage` +- `build/client/DarkTanos.so` +- `build/do_lib/libdo_lib.so` + +You can copy them manually, or use `copy.sh` as described below. + +### Important: prevent lib overwrite in DarkBot + +In DarkBot `LibSetup`, disable/skip logic that overwrites these files in the bot `lib` directory: + +- `DarkTanos.so` +- `libdo_lib.so` + +This is required so your locally built versions remain in use. + +## Optional copy helper + +If you want automatic copy to your DarkBot `lib` folder after each build: + +1. Copy `copy.sh.example` to `copy.sh` +2. Set destination path (`DEST`) in `copy.sh` +3. Make it executable: + +```bash +cp copy.sh.example copy.sh +chmod +x copy.sh +``` + +Then `./build.sh -b` will copy artifacts automatically. diff --git a/browser/.gitignore b/browser/.gitignore new file mode 100644 index 0000000..78fdd87 --- /dev/null +++ b/browser/.gitignore @@ -0,0 +1,3 @@ + +/node_modules +/dist diff --git a/browser/.nvmrc b/browser/.nvmrc new file mode 100644 index 0000000..3c03207 --- /dev/null +++ b/browser/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/browser/package-lock.json b/browser/package-lock.json new file mode 100644 index 0000000..1174708 --- /dev/null +++ b/browser/package-lock.json @@ -0,0 +1,4193 @@ +{ + "name": "darkbot_browser", + "version": "1.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "darkbot_browser", + "version": "1.0.1", + "dependencies": { + "@trodi/electron-splashscreen": "^1.0.2" + }, + "devDependencies": { + "electron": "11.5.0", + "electron-builder": "^24.4.0" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/get": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.14.1.tgz", + "integrity": "sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^9.6.0", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=8.6" + }, + "optionalDependencies": { + "global-agent": "^3.0.0", + "global-tunnel-ng": "^2.7.1" + } + }, + "node_modules/@electron/notarize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.2.1.tgz", + "integrity": "sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.0.5.tgz", + "integrity": "sha512-k9ZzUQtamSoweGQDV2jILiRIHUu7lYlJ3c6IEmjv1hC17rclE+eb9U+f6UFlOOETo0JzY1HNlXy4YOlCvl+Lww==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/universal": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz", + "integrity": "sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.1", + "@malept/cross-spawn-promise": "^1.1.0", + "debug": "^4.3.1", + "dir-compare": "^3.0.0", + "fs-extra": "^9.0.1", + "minimatch": "^3.0.4", + "plist": "^3.0.4" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@trodi/electron-splashscreen": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@trodi/electron-splashscreen/-/electron-splashscreen-1.0.2.tgz", + "integrity": "sha512-Lb36omHRFRAN4nkgPacmxXYgGKXDXxrNlDFXoZp6+ceAIC4kExOvoxj/WyCOtZXLd48MygR3QpjDvflpX67+4A==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz", + "integrity": "sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.13.3.tgz", + "integrity": "sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.2.1", + "@electron/osx-sign": "1.0.5", + "@electron/universal": "1.5.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chromium-pickle-js": "^0.2.0", + "debug": "^4.3.4", + "ejs": "^3.1.8", + "electron-publish": "24.13.1", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "minimatch": "^5.1.1", + "read-config-file": "6.3.2", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "24.13.3", + "electron-builder-squirrel-windows": "24.13.3" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.13.1.tgz", + "integrity": "sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "4.0.0", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz", + "integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-file-ts": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.6.tgz", + "integrity": "sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.10", + "typescript": "^5.3.3" + } + }, + "node_modules/config-file-ts/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz", + "integrity": "sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal": "^1.0.0", + "minimatch": "^3.0.4" + } + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz", + "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", + "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "11.5.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-11.5.0.tgz", + "integrity": "sha512-WjNDd6lGpxyiNjE3LhnFCAk/D9GIj1rU3GSDealVShhkkkPR3Vh4q8ErXGDl1OAO/faomVa10KoFPUN/pLbNxg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^1.0.1", + "@types/node": "^12.0.12", + "extract-zip": "^1.0.3" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 8.6" + } + }, + "node_modules/electron-builder": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.13.3.tgz", + "integrity": "sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "dmg-builder": "24.13.3", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "read-config-file": "6.3.2", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-24.13.3.tgz", + "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "24.13.3", + "archiver": "^5.3.1", + "builder-util": "24.13.1", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-publish": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz", + "integrity": "sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + } + }, + "node_modules/extract-zip/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/extract-zip/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/global-tunnel-ng": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz", + "integrity": "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "encodeurl": "^1.0.2", + "lodash": "^4.17.10", + "npm-conf": "^1.1.3", + "tunnel": "^0.0.6" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "config-chain": "^1.1.11", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/read-config-file": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", + "integrity": "sha512-M80lpCjnE6Wt6zb98DoW8WHR09nzMSpu8XHtPkiTHrJ5Az9CybfeQhTJ8D7saeBHpGhLPIVyA8lcL6ZmdKwY6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-file-ts": "^0.2.4", + "dotenv": "^9.0.2", + "dotenv-expand": "^5.1.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.0", + "lazy-val": "^1.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + } + } +} diff --git a/browser/package.json b/browser/package.json new file mode 100644 index 0000000..0f1da57 --- /dev/null +++ b/browser/package.json @@ -0,0 +1,38 @@ +{ + "name": "darkbot_browser", + "version": "1.0.1", + "description": "DarkBot Browser", + "main": "src/main.js", + "scripts": { + "start": "electron .", + "dist": "electron-builder", + "build": "electron-builder --publish always" + }, + "author": "DarkBot Team", + "build": { + "appId": "eu.darkbot.browser", + "productName": "darkbot_browser", + "artifactName": "darkbot_browser_linux.AppImage", + "asar": true, + "npmRebuild": true, + "buildDependenciesFromSource": false, + "directories": { + "buildResources": "res" + }, + "linux": { + "target": "AppImage", + "extraResources": [ + "./res/icon.png", + "./res/linux/libpepflashplayer.so" + ], + "category": "Game" + } + }, + "dependencies": { + "@trodi/electron-splashscreen": "^1.0.2" + }, + "devDependencies": { + "electron": "11.5.0", + "electron-builder": "^24.4.0" + } +} diff --git a/browser/res/icon.png b/browser/res/icon.png new file mode 100644 index 0000000..4ec7dcc Binary files /dev/null and b/browser/res/icon.png differ diff --git a/browser/res/linux/libpepflashplayer.so b/browser/res/linux/libpepflashplayer.so new file mode 100644 index 0000000..e152de6 Binary files /dev/null and b/browser/res/linux/libpepflashplayer.so differ diff --git a/browser/src/index.html b/browser/src/index.html new file mode 100644 index 0000000..ed00139 --- /dev/null +++ b/browser/src/index.html @@ -0,0 +1,12 @@ + + + + + + DarkBot Browser + + + + + \ No newline at end of file diff --git a/browser/src/key_handler.js b/browser/src/key_handler.js new file mode 100644 index 0000000..3b50d5c --- /dev/null +++ b/browser/src/key_handler.js @@ -0,0 +1,152 @@ +// utilities for translating numeric codes into Electron input events +// this module lives alongside main.js and encapsulates all key-related logic. + +// Windows VK / DOM numeric keyCode → Electron key string +const specialKeys = { + // Control keys + 8: 'Backspace', + 9: 'Tab', + 13: 'Enter', + 16: 'Shift', + 17: 'Control', + 18: 'Alt', + 19: 'Pause', + 20: 'CapsLock', + 27: 'Escape', + 32: 'Space', + + // Navigation + 33: 'PageUp', + 34: 'PageDown', + 35: 'End', + 36: 'Home', + 37: 'ArrowLeft', + 38: 'ArrowUp', + 39: 'ArrowRight', + 40: 'ArrowDown', + 45: 'Insert', + 46: 'Delete', + + // Meta keys + 91: 'Meta', // Left Windows / Command + 92: 'Meta', // Right Windows / Command + 93: 'ContextMenu', + + // Numpad digits + 96: 'Numpad0', + 97: 'Numpad1', + 98: 'Numpad2', + 99: 'Numpad3', + 100: 'Numpad4', + 101: 'Numpad5', + 102: 'Numpad6', + 103: 'Numpad7', + 104: 'Numpad8', + 105: 'Numpad9', + + // Numpad operations + 106: 'NumpadMultiply', + 107: 'NumpadAdd', + 109: 'NumpadSubtract', + 110: 'NumpadDecimal', + 111: 'NumpadDivide', + 108: 'NumpadEnter', + + // Function keys + 112: 'F1', + 113: 'F2', + 114: 'F3', + 115: 'F4', + 116: 'F5', + 117: 'F6', + 118: 'F7', + 119: 'F8', + 120: 'F9', + 121: 'F10', + 122: 'F11', + 123: 'F12', + + // Lock keys + 144: 'NumLock', + 145: 'ScrollLock' +}; + +const oemKeys = { + 186: ';', + 187: '=', + 188: ',', + 189: '-', + 190: '.', + 191: '/', + 192: '`', + 219: '[', + 220: '\\', + 221: ']', + 222: "'" +}; + +/** + * Resolves a numeric key code to an Electron key string. + */ +function resolveKey(code) { + if (specialKeys[code]) return specialKeys[code]; + if (oemKeys[code]) return oemKeys[code]; + + return String.fromCharCode(code); +} + +/** + * Dispatches key events based on the provided code and action (press/release). + */ +function dispatchKeyEvent(webContents, code, press, release) { + if (!webContents.isFocused()) { + webContents.focus(); + } + + const keyCode = resolveKey(code); + + if (press) { + webContents.sendInputEvent({ type: 'keyDown', keyCode }); + } + if (release) { + webContents.sendInputEvent({ type: 'keyUp', keyCode }); + } +} + +/** + * Simulates a key click (press and release) for the given key code. + */ +function handleKeyClick(webContents, code) { + dispatchKeyEvent(webContents, code, true, true); +} + +/** + * Simulates a key press event for the given key code. + */ +function handleKeyDown(webContents, code) { + dispatchKeyEvent(webContents, code, true, false); +} + +/** + * Simulates a key release event for the given key code. + */ +function handleKeyUp(webContents, code) { + dispatchKeyEvent(webContents, code, false, true); +} + +/** + * Simulates typing a string of text by sending individual character events with a small delay between them. + */ +function handleText(webContents, text) { + webContents.focus(); + + let delay = 0; + for (const ch of text) { + setTimeout((char) => { + webContents.sendInputEvent({ type: 'char', keyCode: char }); + }, delay, ch); + delay += 10; // small delay + } +} + +module.exports = { handleKeyClick, handleKeyDown, handleKeyUp, handleText }; diff --git a/browser/src/main.js b/browser/src/main.js new file mode 100644 index 0000000..d83714e --- /dev/null +++ b/browser/src/main.js @@ -0,0 +1,185 @@ +const {app, BrowserWindow} = require('electron') +const path = require('path') +const net = require('net') +const {initSplashScreen} = require("@trodi/electron-splashscreen") + +let mainWindow; + +function log(...args) { console.log('[browser]', ...args); } + +app.commandLine.appendSwitch('ppapi-flash-path', getFlashPath()) + +const { handleKeyClick, handleKeyDown, handleKeyUp, handleText } = require('./key_handler'); + +var server = net.createServer(function (sock) { + sock.setEncoding('utf8'); + + sock.on('data', (data) => { + if (!mainWindow) { + log("Received command but mainWindow is not initialized, ignoring"); + return; + } + + try { + const obj = JSON.parse(data); + switch (obj.cmd) { + case "refresh": + log("Received refresh command, reloading..."); + mainWindow.reload(); + break; + case "setSize": + // resize the browser window to the given width and height + mainWindow.setSize(obj.w, obj.h); + break; + case "keyClick": + handleKeyClick(mainWindow.webContents, obj.key); + break; + case "keyDown": + handleKeyDown(mainWindow.webContents, obj.key); + break; + case "keyUp": + handleKeyUp(mainWindow.webContents, obj.key); + break; + case "text": + handleText(mainWindow.webContents, obj.text); + break; + } + } catch (e) { + log("Failed to parse command:", data, e); + return; + } + + // Send acknowledgment back to the sender. + sock.write(data + "|ok"); + }); + + sock.on('error', (err) => { + log("Socket error", err); + }); + + sock.on('close', (hadError) => { + log("Socket closed" + (hadError ? " (error)" : "")); + // client may reconnect later; the server stays listening and will emit + // a new connection event when that happens. nothing to do here other + // than logging/debugging. + }); +}); +server.listen("/tmp/darkbot_ipc_" + process.pid); + +function createWindow(url, sid, apiVersion, launchGame = false) { + let icon = path.join(process.resourcesPath, "res", "icon.png") + + let window = initSplashScreen({ + windowOpts: { + width: 1400, + height: 900, + icon: icon, + show: false, + darkTheme: true, + autoHideMenuBar: true, + title: "DarkBot Browser" + (apiVersion ? ` [Tanos v${apiVersion}]` : ""), + webPreferences: { + plugins: true, + sandbox: false, + contextIsolation: true, + nodeIntegration: false, + enableRemoteModule: false, + preload: path.join(__dirname, 'preload.js') + } + }, + templateUrl: `${__dirname}/splash.html`, + splashScreenOpts: { + width: 300, + height: 300, + frame: true, + alwaysOnTop: true, + webPreferences: { + contextIsolation: true, + nodeIntegration: false, + enableRemoteModule: false + } + }, + minVisible: 0, + delay: 0 + }) + + window.webContents.userAgent = 'BigpointClient/1.6.7' + window.webContents.on('new-window', (event, url) => { + event.preventDefault() + window.loadURL(url) + }) + + window.on('page-title-updated', (evt) => { + evt.preventDefault(); + }); + + window.webContents.on('before-input-event', (event, input) => { + let focus = () => BrowserWindow.getFocusedWindow(); + + if (!focus() || input.type != "keyUp") { + return; + } + }); + + log(url, sid, launchGame); + if (url && sid) { + window.webContents.session.cookies.set({url: url, name: 'dosid', value: sid}) + .then(() => window.loadURL(url + '/indexInternal.es?action=' + ((launchGame) ? 'internalMapRevolution ': 'internalStart'))) + } else { + window.loadURL('https://darkorbit.com') + //window.loadFile(path.join(__dirname, 'index.html')) + } + return window; +} + +function createMainWindow() { + const {url, sid, apiVersion, launchGame} = parseArgv(); + mainWindow = createWindow(url, sid, apiVersion, launchGame); +} + +app.whenReady().then(() => { + createMainWindow(); + + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) { + createMainWindow(); + } + }) +}) + +app.on('window-all-closed', function () { + app.quit() +}) + +function parseArgv() { + let url, sid, apiVersion, launchGame = false + + for (let i = 1; i < process.argv.length; i++) { + const arg = process.argv[i] + + if (arg === '--launch') { + launchGame = true + continue + } + + const [key, value] = arg.split('=', 2) + switch (key) { + case '--url': + url = value + break + case '--sid': + sid = value + break + case '--api-version': + apiVersion = value + break + } + } + + return {url, sid, apiVersion, launchGame}; +} + +function getFlashPath() { + app.commandLine.appendSwitch("--no-sandbox") + return path.join(process.resourcesPath.split("/")[1] === "tmp" ? process.resourcesPath : app.getAppPath(), './res/linux/libpepflashplayer.so'); +} diff --git a/browser/src/preload.js b/browser/src/preload.js new file mode 100644 index 0000000..efef892 --- /dev/null +++ b/browser/src/preload.js @@ -0,0 +1,3 @@ +window.addEventListener('DOMContentLoaded', () => { + +}) \ No newline at end of file diff --git a/browser/src/renderer.js b/browser/src/renderer.js new file mode 100644 index 0000000..e69de29 diff --git a/browser/src/spinner.svg b/browser/src/spinner.svg new file mode 100644 index 0000000..a7c3131 --- /dev/null +++ b/browser/src/spinner.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/browser/src/splash.html b/browser/src/splash.html new file mode 100644 index 0000000..410f3e5 --- /dev/null +++ b/browser/src/splash.html @@ -0,0 +1,19 @@ + + + + + + + + + + \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..858f663 --- /dev/null +++ b/build.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPTSRC=`readlink -f "$0" || echo "$0"` +RUN_PATH=`dirname "${SCRIPTSRC}" || echo .` + +cd ${RUN_PATH} + +BROWSER_DIR="./browser" +BUILD_DIR="./build" +CLIENT_LIB_DIR="$BUILD_DIR/client" +DO_LIB_DIR="$BUILD_DIR/do_lib" + +# Command line flags +CLEAN=false +BUILD_BROWSER=false + +# parse arguments (allows -c and -b in any order) +while [[ $# -gt 0 ]]; do + case "$1" in + -c) + CLEAN=true + shift + ;; + -b) + BUILD_BROWSER=true + shift + ;; + *) + echo "Usage: $0 [-c] [-b]" + echo " -c: Clean build directory (and browser/dist) before building" + echo " -b: Build browser component first by invoking browser/build.sh" + exit 1 + ;; + esac +done + +# Perform clean if requested +if [[ "$CLEAN" == "true" ]]; then + echo "Cleaning $BUILD_DIR directory..." + rm -rf "$BUILD_DIR" + # also clean browser output if building browser + if [[ "$BUILD_BROWSER" == "true" ]]; then + echo "Cleaning $BROWSER_DIR/dist directory..." + rm -rf "$BROWSER_DIR/dist" + fi +fi + +# If requested, build the browser component first +if [[ "$BUILD_BROWSER" == "true" ]]; then + cd ${BROWSER_DIR} + echo "Building browser component..." + + # Load nvm (required) + export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" + # This loads nvm (if installed via standard install script) + if [ -s "$NVM_DIR/nvm.sh" ]; then + # shellcheck disable=SC1090 + . "$NVM_DIR/nvm.sh" + else + echo "❌ nvm not found. Please install NVM first: https://github.com/nvm-sh/nvm" + return 1 2>/dev/null || exit 1 + fi + + # Use or install Node.js version from .nvmrc + nvm use || nvm install + + echo "Installing dependencies." + npm install + + echo "Building browser app." + npm run dist -- --linux + + echo "Browser build completed." + cd ${RUN_PATH} +fi + +# Configure and build the main project with optimizations +cmake -S . -B "$BUILD_DIR" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON \ + -DCMAKE_CXX_FLAGS_RELEASE="-Os -ffunction-sections -fdata-sections -fvisibility=hidden" \ + -DCMAKE_SHARED_LINKER_FLAGS_RELEASE="-Wl,--gc-sections" +cmake --build "$BUILD_DIR" + +# Rename the client library to match what darkbot expects +if [[ -f "$CLIENT_LIB_DIR/libDarkTanos.so" ]]; then + mv "$CLIENT_LIB_DIR/libDarkTanos.so" "$CLIENT_LIB_DIR/DarkTanos.so" +fi + +# Strip unneeded symbols from the shared libraries to reduce size +if command -v strip >/dev/null 2>&1; then + strip --strip-unneeded "$CLIENT_LIB_DIR/DarkTanos.so" + strip --strip-unneeded "$DO_LIB_DIR/libdo_lib.so" +else + echo "strip not found; skipping symbol stripping." >&2 +fi + +# if a copy script exists, execute it to move artifacts into darkbot/lib +if [[ -x "./copy.sh" ]]; then + echo "Running copy.sh to transfer build artifacts..." + ./copy.sh +elif [[ -f "./copy.sh" ]]; then + echo "copy.sh exists but is not executable; please make it executable to run" +fi + diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 650524e..d9405f7 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -4,6 +4,29 @@ find_package(JNI REQUIRED) include_directories(${PROJECT_NAME} PRIVATE ${JNI_INCLUDE_DIRS}) +add_definitions(-DAPI_VERSION=${API_VERSION}) + +set(BROWSER_APPIMAGE_PATH "${CMAKE_SOURCE_DIR}/browser/dist/darkbot_browser_linux.AppImage") +if(NOT EXISTS "${BROWSER_APPIMAGE_PATH}") + message(FATAL_ERROR "Required browser AppImage not found: ${BROWSER_APPIMAGE_PATH}") +endif() + +execute_process( + COMMAND sh -c "sha256sum '${BROWSER_APPIMAGE_PATH}' | awk '{print \$1}'" + OUTPUT_VARIABLE BROWSER_APPIMAGE_SHA256 + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_VARIABLE _browser_appimage_sha256_err + RESULT_VARIABLE _browser_appimage_sha256_result +) +if(NOT _browser_appimage_sha256_result EQUAL 0) + message(FATAL_ERROR "Failed to compute SHA256 for ${BROWSER_APPIMAGE_PATH}: ${_browser_appimage_sha256_err}") +endif() +string(STRIP "${BROWSER_APPIMAGE_SHA256}" BROWSER_APPIMAGE_SHA256) +string(LENGTH "${BROWSER_APPIMAGE_SHA256}" _browser_appimage_sha256_len) +if(NOT _browser_appimage_sha256_len EQUAL 64) + message(FATAL_ERROR "SHA256 output for ${BROWSER_APPIMAGE_PATH} is invalid: ${BROWSER_APPIMAGE_SHA256}") +endif() + add_library(${PROJECT_NAME} SHARED eu_darkbot_api_DarkTanos.cpp bot_client.cpp @@ -11,4 +34,10 @@ add_library(${PROJECT_NAME} SHARED sock_ipc.cpp ) +target_compile_definitions(${PROJECT_NAME} PRIVATE BROWSER_APPIMAGE_SHA256="${BROWSER_APPIMAGE_SHA256}") + +find_package(X11 REQUIRED) +target_include_directories(${PROJECT_NAME} PRIVATE ${X11_INCLUDE_DIR}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${X11_LIBRARIES}) + target_compile_options(${PROJECT_NAME} PRIVATE -std=c++17) diff --git a/client/bot_client.cpp b/client/bot_client.cpp index d10c8fd..a187692 100644 --- a/client/bot_client.cpp +++ b/client/bot_client.cpp @@ -1,9 +1,20 @@ #include "bot_client.h" #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include +#include +#include +#include +#include +#include #include "utils.h" #include "proc_util.h" @@ -16,9 +27,622 @@ #include #include +#include +#include +#include +#include + #define MEM_SIZE 1024 +namespace window +{ + Window browser_window = 0; + + struct Property + { + Atom actual_type = None; + int actual_format = 0; + unsigned long nitems = 0; + unsigned long bytes_after = 0; + unsigned char *prop = nullptr; + }; + + /** + * Helper function to free the memory allocated by XGetWindowProperty and reset the WindowProperty structure. + */ + void free_property(Property &property) + { + if (property.prop) + { + XFree(property.prop); + } + property.actual_type = None; + property.actual_format = 0; + property.nitems = 0; + property.bytes_after = 0; + property.prop = nullptr; + } + + /** + * Helper function to get a window property with proper error handling and type checking. + */ + bool get_property(Display *display, + Window window, + Atom property, + Atom type, + long length, + Property &out) + { + if (!display || property == None) + { + return false; + } + + int status = XGetWindowProperty(display, window, property, 0, length, False, type, + &out.actual_type, + &out.actual_format, + &out.nitems, + &out.bytes_after, + &out.prop); + + return status == Success; + } + + /** + * Helper function to get the PID of the process owning a window, using the _NET_WM_PID property. + */ + bool get_pid(Display *display, Window window, pid_t &pid) + { + Atom atom_pid = XInternAtom(display, "_NET_WM_PID", True); + if (atom_pid == None) + { + return false; + } + + Property property; + bool read_ok = get_property(display, window, atom_pid, XA_CARDINAL, 1, property); + + if (!read_ok || !property.prop || property.nitems == 0) + { + free_property(property); + return false; + } + + pid = static_cast(*reinterpret_cast(property.prop)); + free_property(property); + return true; + } + + /** + * Helper function to check if a given PID is the browser process or a child of it. + */ + bool is_browser_pid(pid_t owner_pid, pid_t browser_pid) + { + return owner_pid == browser_pid || ProcUtil::IsChildOf(owner_pid, browser_pid); + } + + /** + * Checks if X11 window control is available by verifying the DISPLAY environment variable. + */ + bool x11_control_available() + { + const char *display = std::getenv("DISPLAY"); + return display && *display; + } + + /** + * Helper function to attempt to get window attributes, handling potential X11 errors gracefully. + */ + bool try_get_attrs(Display *display, Window window) + { + XWindowAttributes attrs; + if (XGetWindowAttributes(display, window, &attrs) != 0) + { + return true; + } + + XSync(display, False); + return XGetWindowAttributes(display, window, &attrs) != 0; + } + + /** + * Helper function to find the top-level root child of a given window, + * which is likely the actual browser window we want to control. + */ + Window find_toplevel_root_child(Display *display, Window root, Window window) + { + if (!window) + { + return 0; + } + + Window current = window; + while (current) + { + Window root_return = 0; + Window parent_return = 0; + Window *children = nullptr; + unsigned int nchildren = 0; + + if (!XQueryTree(display, current, &root_return, &parent_return, &children, &nchildren)) + { + return current; + } + + if (children) + { + XFree(children); + } + + if (parent_return == 0 || parent_return == root) + { + return current; + } + + current = parent_return; + } + + return window; + } + + /** + * Recursive helper function to find any descendant window owned by the browser process, + * in case the top-level window doesn't have a PID or isn't directly owned by the browser. + */ + Window find_browser_owned_descendant_recursive(Display *display, Window root, pid_t browser_pid) + { + if (!root) + { + return 0; + } + + pid_t owner_pid = -1; + if (get_pid(display, root, owner_pid) && is_browser_pid(owner_pid, browser_pid)) + { + return root; + } + + Window root_return = 0; + Window parent_return = 0; + Window *children = nullptr; + unsigned int nchildren = 0; + + if (!XQueryTree(display, root, &root_return, &parent_return, &children, &nchildren)) + { + return 0; + } + + Window found = 0; + for (unsigned int i = 0; i < nchildren && !found; i++) + { + found = find_browser_owned_descendant_recursive(display, children[i], browser_pid); + } + + if (children) + { + XFree(children); + } + return found; + } + + /** + * Main function to find the browser window by first checking the _NET_CLIENT_LIST for windows owned by the browser PID. + */ + Window find_browser_client(Display *display, pid_t browser_pid) + { + Window root = DefaultRootWindow(display); + Atom atom_client_list = XInternAtom(display, "_NET_CLIENT_LIST", True); + if (atom_client_list != None) + { + Property property; + bool read_ok = get_property(display, root, atom_client_list, XA_WINDOW, 4096, property); + + if (read_ok && property.prop && property.actual_type == XA_WINDOW) + { + Window *windows = reinterpret_cast(property.prop); + Window child_fallback = 0; + for (unsigned long i = 0; i < property.nitems; i++) + { + pid_t owner_pid = -1; + if (!get_pid(display, windows[i], owner_pid)) + { + continue; + } + + if (owner_pid == browser_pid) + { + free_property(property); + return windows[i]; + } + + if (!child_fallback && ProcUtil::IsChildOf(owner_pid, browser_pid)) + { + child_fallback = windows[i]; + } + } + + if (child_fallback) + { + free_property(property); + return child_fallback; + } + } + + free_property(property); + } + + Window any_owned = find_browser_owned_descendant_recursive(display, root, browser_pid); + if (!any_owned) + { + return 0; + } + + return find_toplevel_root_child(display, root, any_owned); + } + + /** + * Helper function to check if a window has the WM_STATE property, which is a strong indicator + * that it's a top-level application window rather than a transient or child window. + */ + bool has_wm_state(Display *display, Window window) + { + Atom wm_state = XInternAtom(display, "WM_STATE", True); + if (wm_state == None) + { + return false; + } + + Property property; + bool read_ok = get_property(display, window, wm_state, wm_state, 2, property); + bool has_state = read_ok && property.actual_type == wm_state && property.nitems > 0; + + free_property(property); + return has_state; + } + + /** + * Main function to resolve the actual client window of the browser, + * which may involve checking the top-level window and its children + */ + Window resolve_client(Display *display, pid_t browser_pid) + { + if (!display) + { + return 0; + } + + Window window = find_browser_client(display, browser_pid); + if (!window) + { + return 0; + } + + if (has_wm_state(display, window)) + { + return window; + } + + Window root_return = 0; + Window parent_return = 0; + Window *children = nullptr; + unsigned int nchildren = 0; + + if (!XQueryTree(display, window, &root_return, &parent_return, &children, &nchildren)) + { + return window; + } + + Window client = 0; + for (unsigned int i = 0; i < nchildren; i++) + { + if (has_wm_state(display, children[i])) + { + client = children[i]; + break; + } + } + + if (children) + { + XFree(children); + } + + return client ? client : window; + } + + /** + * Helper function to execute an action in the context of the browser window. + */ + template + bool with_browser(int flash_pid, int browser_pid, Func&& action) + { + if (flash_pid == -1 || !x11_control_available()) + return false; + + Display *display = XOpenDisplay(nullptr); + if (!display) + return false; + + if (!browser_window || !try_get_attrs(display, browser_window)) + browser_window = resolve_client(display, browser_pid); + + bool result = false; + if (browser_window) + result = action(display, browser_window); + + XFlush(display); + XCloseDisplay(display); + return result; + } +} + +namespace mouse +{ + struct EventContext + { + Display *display; + Window window; + Window root; + int local_x, local_y; + int root_x, root_y; + }; + + /** + * Prepares the context for a mouse event by translating local coordinates to root coordinates. + */ + bool prepare_event(Display *display, Window window, int32_t x, int32_t y, EventContext &ctx) + { + if (!display || !window) + { + return false; + } + + XWindowAttributes attrs; + if (XGetWindowAttributes(display, window, &attrs) == 0) + { + return false; + } + + ctx.display = display; + ctx.window = window; + ctx.local_x = x; + ctx.local_y = y; + + // Clamp coordinates to the window bounds to avoid unexpected behavior + if (ctx.local_x < 0) + ctx.local_x = 0; + if (ctx.local_y < 0) + ctx.local_y = 0; + if (attrs.width > 0 && ctx.local_x >= attrs.width) + ctx.local_x = attrs.width - 1; + if (attrs.height > 0 && ctx.local_y >= attrs.height) + ctx.local_y = attrs.height - 1; + + ctx.root = DefaultRootWindow(display); + Window child = 0; + XTranslateCoordinates(display, window, ctx.root, 0, 0, &ctx.root_x, &ctx.root_y, &child); + + return true; + } + + /** + * Fills the common fields of an XEvent structure for mouse events, based on the provided context. + */ + void fill_event_common(XEvent &event, const EventContext &ctx) + { + std::memset(&event, 0, sizeof(event)); + event.xany.display = ctx.display; + event.xany.window = ctx.window; + event.xbutton.root = ctx.root; + event.xbutton.subwindow = None; + event.xbutton.time = CurrentTime; + event.xbutton.x = ctx.local_x; + event.xbutton.y = ctx.local_y; + event.xbutton.x_root = ctx.root_x + ctx.local_x; + event.xbutton.y_root = ctx.root_y + ctx.local_y; + event.xbutton.send_event = True; + event.xbutton.same_screen = True; + } + + /** + * Sends a mouse move event. + */ + bool send_move(Display *display, Window window, int32_t x, int32_t y) + { + EventContext ctx; + if (!prepare_event(display, window, x, y, ctx)) + return false; + + XEvent event; + fill_event_common(event, ctx); + event.type = MotionNotify; + + XSendEvent(ctx.display, ctx.window, True, PointerMotionMask, &event); + return true; + } + + /** + * Sends mouse button press/release events. + */ + bool send_button(Display *display, Window window, int32_t x, int32_t y, int button, bool press, bool release) + { + EventContext ctx; + if (!prepare_event(display, window, x, y, ctx)) + return false; + + XEvent event; + fill_event_common(event, ctx); + event.xbutton.button = button; + + if (press) + { + event.type = ButtonPress; + XSendEvent(ctx.display, ctx.window, True, ButtonPressMask, &event); + } + + if (release) + { + event.type = ButtonRelease; + XSendEvent(ctx.display, ctx.window, True, ButtonReleaseMask, &event); + } + return true; + } + + /** + * Sends a mouse wheel event. + */ + bool send_wheel(Display *display, Window window, int32_t x, int32_t y, int button) + { + return send_button(display, window, x, y, button, true, true); + } +} + +namespace cursor_marker +{ + static constexpr int dot_size = 6; // 6x6 marker size + static constexpr const char *dot_color = "red"; + + // State for the cursor marker, including whether it's enabled. + struct State + { + bool enabled = false; + Display *display = nullptr; + Window window = 0; + Window parent = 0; + std::chrono::steady_clock::time_point last_time; + std::mutex mutex; + }; + + static State state; + + /** + * Destroys the cursor marker window and closes the display connection. + */ + void destroy() + { + if (state.window && state.display) + { + XDestroyWindow(state.display, state.window); + XCloseDisplay(state.display); + } + state.window = 0; + state.parent = 0; + state.display = nullptr; + } + + /** + * Creates a small red window that will serve as a marker for the virtual cursor position. + * This is useful for debugging and visualizing where the bot is "clicking" on the screen. + */ + void create(Window parent) + { + if (!parent) + return; + + destroy(); + + state.display = XOpenDisplay(NULL); + if (!state.display) + return; + + int scr = DefaultScreen(state.display); + + Colormap cmap = DefaultColormap(state.display, scr); + XColor color; + XColor exact; + if (!XAllocNamedColor(state.display, cmap, dot_color, &color, &exact)) + { + color.pixel = 0; // fallback black + } + + XSetWindowAttributes attr; + attr.background_pixel = color.pixel; + attr.background_pixmap = None; + + unsigned long mask = CWBackPixel; + state.window = XCreateWindow( + state.display, + parent, + 0, 0, dot_size, dot_size, 0, + CopyFromParent, + InputOutput, + CopyFromParent, + mask, + &attr); + state.parent = parent; + + // make the window input-transparent so it doesn't grab events + int shape_event, shape_error; + if (XShapeQueryExtension(state.display, &shape_event, &shape_error)) + { + XRectangle rect = {0, 0, 0, 0}; + XShapeCombineRectangles(state.display, + state.window, + ShapeInput, + 0, 0, + &rect, + 1, + ShapeSet, + Unsorted); + } + + XMapRaised(state.display, state.window); + XFlush(state.display); + } + + /** + * Checks if the cursor marker should be hidden due to inactivity (no updates for 3 seconds) and hides it if necessary. + */ + void maybe_clear() + { + std::lock_guard lock(state.mutex); + if (!state.enabled) + return; + + auto now = std::chrono::steady_clock::now(); + if (now - state.last_time >= std::chrono::seconds(3)) + { + destroy(); + } + } + + /** + * Updates the position of the cursor marker to the given coordinates. + */ + void update(int x, int y, int flash_pid, int browser_pid) + { + if (!state.enabled || flash_pid == -1 || !window::x11_control_available()) + return; + + // record last update time + { + std::lock_guard lock(state.mutex); + state.last_time = std::chrono::steady_clock::now(); + } + + window::with_browser(flash_pid, browser_pid, [&](Display *display, Window browser) { + if (!state.window || !state.display || state.parent != browser || !window::try_get_attrs(state.display, state.window)) + create(browser); + + if (!state.window || !state.display) + return false; + + const int offset = static_cast(std::lround(dot_size / 2.0)); + XMoveWindow(state.display, state.window, x - offset, y - offset); + XMapRaised(state.display, state.window); + XFlush(state.display); + return true; + }); + + // schedule a hide check in 3 seconds + std::thread([]() { + std::this_thread::sleep_for(std::chrono::seconds(3)); + maybe_clear(); + }).detach(); + } +} + enum class MessageType { @@ -119,18 +743,136 @@ union Message GetSignatureMessage sig; }; +BotClient::BotClient() : m_browser_ipc(new SockIpc()) {} + +/** + * Continuously reads from the browser process's log output pipe + * and logs any lines that contain the "[browser]" tag. + */ +static void browser_log_drain(int read_fd) +{ + static constexpr std::string_view browser_tag = "[browser]"; + char buf[4096]; + std::string partial; + + while (true) + { + ssize_t n = read(read_fd, buf, sizeof(buf)); + if (n == 0) + break; + + if (n < 0) + { + if (errno == EINTR) + continue; + break; + } + + partial.append(buf, static_cast(n)); + + size_t scan_start = 0; + size_t newline_pos = 0; + while ((newline_pos = partial.find('\n', scan_start)) != std::string::npos) + { + const std::string_view line(partial.data() + scan_start, newline_pos - scan_start); + if (line.find(browser_tag) != std::string_view::npos) + utils::log("{}\n", line); + + scan_start = newline_pos + 1; + } + + if (scan_start > 0) + partial.erase(0, scan_start); + } + + if (!partial.empty()) + { + std::string_view tail(partial.data(), partial.size()); + if (tail.find(browser_tag) != std::string_view::npos) + utils::log("{}\n", tail); + } + + close(read_fd); +} -BotClient::BotClient() : - m_browser_ipc(new SockIpc()) +void BotClient::ToggleBrowserVisibility(bool visible) { + window::with_browser(FlashPid(), Pid(), [=](Display *display, Window browser) { + visible ? XMapWindow(display, browser) : XUnmapWindow(display, browser); + return true; + }); } BotClient::~BotClient() { - if (m_browser_pid > 0) + if (Pid() > 0) + { + kill(Pid(), SIGKILL); + } +} + +static std::string shell_escape(const std::string &value) +{ + std::string escaped = "'"; + for (char c : value) + { + if (c == '\'') + escaped += "'\\''"; + else + escaped += c; + } + escaped += "'"; + return escaped; +} + +static bool compute_sha256(const std::string &file_path, std::string &out_hash) +{ + std::string command = "sha256sum " + shell_escape(file_path); + FILE *pipe = popen(command.c_str(), "r"); + if (!pipe) + return false; + + char buffer[256]; + if (!fgets(buffer, sizeof(buffer), pipe)) + { + pclose(pipe); + return false; + } + + int status = pclose(pipe); + if (status != 0) + return false; + + std::istringstream iss(buffer); + if (!(iss >> out_hash)) + return false; + + if (out_hash.size() != 64) + return false; + + for (unsigned char c : out_hash) { - kill(m_browser_pid, SIGKILL); + if (!std::isxdigit(c)) + return false; } + + std::transform(out_hash.begin(), out_hash.end(), out_hash.begin(), [](unsigned char c) { return std::tolower(c); }); + return true; +} + +static bool validate_file_sha256(const std::string &file_path, const std::string &expected_hex) +{ + if (expected_hex.empty()) + return false; + + std::string actual_hash; + if (!compute_sha256(file_path, actual_hash)) + return false; + + std::string normalized_expected = expected_hex; + std::transform(normalized_expected.begin(), normalized_expected.end(), normalized_expected.begin(), [](unsigned char c) { return std::tolower(c); }); + + return actual_hash == normalized_expected; } void sigchld_handler(int signal) @@ -139,20 +881,77 @@ void sigchld_handler(int signal) waitpid(0, &status, WNOHANG); } + +void BotClient::Refresh() +{ + utils::log("[Refresh] Triggering browser refresh\n"); + + if (!SendBrowserCommand("refresh")) + { + utils::log("[Refresh] refresh command failed\n"); + // Reset IPC so we can attempt to reconnect + m_browser_ipc.reset(new SockIpc()); + return; + } + + // if there's an existing flash process, kill it so we don't keep + // reusing the same PID after a refresh. + if (FlashPid() > 0) + kill(FlashPid(), SIGKILL); + + reset(); +} + void BotClient::LaunchBrowser() { + const char *fpath = "lib/darkbot_browser_linux.AppImage"; + static const std::string expected_sha256 = BROWSER_APPIMAGE_SHA256; + + /* ensure the browser binary exists and is executable before attempting to fork/exec */ + if (access(fpath, F_OK) != 0) + { + utils::log("[LaunchBrowser] browser binary not found: {}\n", fpath); + return; + } + if (!validate_file_sha256(fpath, expected_sha256)) + { + utils::log("[LaunchBrowser] browser binary SHA256 validation failed: {}\n", fpath); + return; + } + if (access(fpath, X_OK) != 0) + { + mode_t mode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + if (chmod(fpath, mode) != 0) + { + utils::log("[LaunchBrowser] browser binary not executable and chmod failed: {} ({})\n", fpath, std::strerror(errno)); + return; + } + } + + int log_pipe[2] = {-1, -1}; + if (pipe(log_pipe) != 0) + utils::log("[LaunchBrowser] pipe() failed: {}\n", std::strerror(errno)); + int pid = fork(); switch (pid) { case -1: // https://rachelbythebay.com/w/2014/08/19/fork/ { - perror("Fork failed."); + utils::log("Fork failed: {}\n", std::strerror(errno)); + if (log_pipe[0] != -1) { close(log_pipe[0]); close(log_pipe[1]); } break; } case 0: { - const char *fpath = "lib/backpage-linux-x86_64.AppImage"; + // redirect browser stdout/stderr into the pipe so the parent can log them + if (log_pipe[1] != -1) + { + dup2(log_pipe[1], STDOUT_FILENO); + dup2(log_pipe[1], STDERR_FILENO); + close(log_pipe[0]); + close(log_pipe[1]); + } std::vector envp { @@ -177,64 +976,193 @@ void BotClient::LaunchBrowser() sid.replace(0, 6, ""); } - execle(fpath, fpath, "--sid", sid.c_str(), "--url", url.c_str(), "--launch", NULL, envp.data()); + std::string arg_sid = std::string("--sid=") + sid; + std::string arg_url = std::string("--url=") + url; + std::string arg_api_version = std::string("--api-version=") + std::to_string(API_VERSION); + + execle( + fpath, + fpath, + arg_sid.c_str(), + arg_url.c_str(), + arg_api_version.c_str(), + "--launch", + "--ozone-platform=x11", + "--disable-background-timer-throttling", + "--disable-renderer-backgrounding", + NULL, + envp.data()); + break; } default: { signal(SIGCHLD, sigchld_handler); + SetPid(pid); - m_browser_pid = pid; + // close write end in parent; read end passed to drain thread + if (log_pipe[1] != -1) close(log_pipe[1]); + if (log_pipe[0] != -1) + { + int read_fd = log_pipe[0]; + std::thread([read_fd]() { browser_log_drain(read_fd); }).detach(); + } break; } } } -void BotClient::SendBrowserCommand(const std::string &&message, int sync) +// helper used within SendBrowserCommand; returns true when the IPC +// connection is ready (either already connected or successfully created). +bool BotClient::ensure_browser_ipc_connected() +{ + if (m_browser_ipc->Connected()) + return true; + + if (Pid() < 0) + return false; + + std::string ipc_path = utils::format("/tmp/darkbot_ipc_{}", Pid()); + if (!m_browser_ipc->Connect(ipc_path)) + { + utils::log("[SendBrowserCommand] Failed to connect to browser {}\n", Pid()); + return false; + } + return true; +} + +/** + * Builds a JSON string for the given command and parameters, including a timestamp for uniqueness. + */ +static std::string build_browser_command_json(const std::string &cmd, std::initializer_list params) +{ + auto now = std::chrono::system_clock::now(); + auto ms = std::chrono::duration_cast(now.time_since_epoch()).count(); + + char ts_buf[32]; + auto [ts_ptr, ts_ec] = std::to_chars(ts_buf, ts_buf + sizeof(ts_buf), ms); + if (ts_ec != std::errc()) + { + ts_ptr = ts_buf; + } + + size_t reserve_size = cmd.size() + static_cast(ts_ptr - ts_buf) + 16; + for (const JsonParam ¶m : params) + { + reserve_size += 4 + std::strlen(param.key) + param.value.size(); + } + + std::string json; + json.reserve(reserve_size); + json.append("{\"cmd\":\""); + json.append(cmd); + json.append("\",\"ts\":"); + json.append(ts_buf, static_cast(ts_ptr - ts_buf)); + + for (const JsonParam ¶m : params) + { + json.append(",\""); + json.append(param.key); + json.append("\":"); + json.append(param.value.data(), param.value.size()); + } + + json.push_back('}'); + return json; +} + +/** + * Sends a command to the browser process via IPC, with retries and acknowledgment handling. + * Params format: {"arg1": "value1", "arg2": "value2"} which gets converted to JSON and sent to the browser. + */ +bool BotClient::SendBrowserCommand(const std::string &cmd, std::initializer_list params) { - if (m_browser_pid > 0 && !ProcUtil::ProcessExists(m_browser_pid)) + if (Pid() > 0 && !ProcUtil::ProcessExists(Pid())) { - fprintf(stderr, "[SendBrowserCommand] Browser process not found, restarting it\n"); + utils::log("[SendBrowserCommand] Browser process not found, restarting it\n"); LaunchBrowser(); - m_flash_pid = -1; - return; + reset(); + return false; } - if (!m_browser_ipc->Connected()) + if (!ensure_browser_ipc_connected()) { - if (m_browser_pid < 0) - { - return; - } + return false; + } - std::string ipc_path = utils::format("/tmp/darkbot_ipc_{}", m_browser_pid); + std::string json = build_browser_command_json(cmd, params); + std::string expected_ack; + expected_ack.reserve(json.size() + 3); + expected_ack.append(json); + expected_ack.append("|ok"); // the JS side appends "|ok" to acknowledge receipt and processing + int maxAttempts = 3; + std::chrono::milliseconds timeout = std::chrono::milliseconds(500); - //printf("[SendBrowserCommand] Connecting to %s\n", ipc_path.c_str()); + for (int attempt = 1; attempt <= maxAttempts; ++attempt) + { + if (!m_browser_ipc->Send(json.c_str())) + { + utils::log("[SendBrowserCommand] send failed on attempt {}\n", attempt); + // try reconnect + m_browser_ipc.reset(new SockIpc()); + if (!ensure_browser_ipc_connected()) + { + utils::log("[SendBrowserCommand] reconnect attempt failed\n"); + return false; + } + continue; // retry send + } - if (!m_browser_ipc->Connect(ipc_path)) + auto deadline = std::chrono::steady_clock::now() + timeout; + while (std::chrono::steady_clock::now() < deadline) { - printf("[SendBrowserCommand] Failed to connect to browser %d\n", m_browser_pid); - return; + std::string reply; + if (m_browser_ipc->Recv(reply)) + { + if (reply.find(expected_ack) != std::string::npos) + { + return true; // success + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } - } - //printf("[SendBrowserCommand] Sending message %s\n", message.c_str()); + utils::log("[SendBrowserCommand] no ack for '{}' retrying ({}/{})\n", json.c_str(), attempt, maxAttempts); + } - m_browser_ipc->Send(message); - return; + utils::log("[SendBrowserCommand] failed to get ack for '{}'\n", json.c_str()); + return false; } bool BotClient::find_flash_process() { - auto procs = ProcUtil::FindProcsByName("no-sandbox"); + // require all substrings when scanning /proc. + auto procs = ProcUtil::FindProcsByName({"darkbot_browser", "no-sandbox", "ppapi"}); + + int best_pid = -1; + uint64_t best_memory = 0; + for (int proc_pid : procs) { - if (ProcUtil::IsChildOf(proc_pid, m_browser_pid) && ProcUtil::GetPages(proc_pid, "libpepflashplayer").size() > 0) + if (ProcUtil::IsChildOf(proc_pid, Pid()) && ProcUtil::GetPages(proc_pid, "libpepflashplayer").size() > 0) { - m_flash_pid = proc_pid; - return true; + // Search for the flash process with the most memory usage, + // since the browser can spawn multiple and we want to target the main one + uint64_t memory = ProcUtil::GetMemoryUsage(proc_pid); + if (memory >= best_memory) + { + best_memory = memory; + best_pid = proc_pid; + } } } + + if (best_pid > 0) + { + SetFlashPid(best_pid); + return true; + } + return false; } @@ -246,69 +1174,68 @@ void BotClient::reset() m_shared_mem_flash = nullptr; - m_flash_pid = -1; + SetFlashPid(-1); m_flash_sem = -1; m_flash_shmid = -1; - m_flash_pid = -1; } // Not a great name since it has side-effects like refreshgin or restarting the browser bool BotClient::IsValid() { - if (m_browser_pid > 0 && !ProcUtil::ProcessExists(m_browser_pid)) + if (Pid() > 0 && !ProcUtil::ProcessExists(Pid())) { - fprintf(stderr, "[IsValid] Browser process not found, restarting it\n"); + utils::log("[IsValid] Browser process not found, restarting it\n"); LaunchBrowser(); + reset(); return false; } - - if (m_flash_pid == -1) + if (FlashPid() == -1) { return find_flash_process(); } - if (!ProcUtil::ProcessExists(m_flash_pid)) + if (!ProcUtil::ProcessExists(FlashPid())) { - fprintf(stderr, "[IsValid] Flash process not found, trying to refresh %d, %d\n", m_flash_pid, m_browser_pid); - SendBrowserCommand("refresh", 1); - reset(); + utils::log("[IsValid] Flash process not found, trying to refresh {}, {}\n", FlashPid(), Pid()); + Refresh(); return false; } return true; } -void BotClient::SendFlashCommand(Message *message, Message *response) +/** + * Sends a command message to the flash process via shared memory and semaphores, and optionally waits for a response. + */ +bool BotClient::SendFlashCommand(Message *message, Message *response) { if (!IsValid()) { - return; + return false; } + if ((m_flash_shmid = shmget(FlashPid(), MEM_SIZE, IPC_CREAT | 0666)) < 0) { - if ((m_flash_shmid = shmget(m_flash_pid, MEM_SIZE, IPC_CREAT | 0666)) < 0) - { - fprintf(stderr, "[SendFlashCommand] Failed to get shared memory\n"); - return; - } + utils::log("[SendFlashCommand] Failed to get shared memory\n"); + return false; } if (!m_shared_mem_flash || m_shared_mem_flash == (void *)-1) { if ((m_shared_mem_flash = reinterpret_cast(shmat(m_flash_shmid, NULL, 0))) == (void *)-1) { - fprintf(stderr, "[SendFlashCommand] Failed to attach shared memory to our process\n"); - return; + utils::log("[SendFlashCommand] Failed to attach shared memory to our process\n"); + return false; } } if (m_flash_sem < 0) { - if ((m_flash_sem = semget(m_flash_pid , 2, IPC_CREAT | 0600)) < 0) + if ((m_flash_sem = semget(FlashPid(), 2, IPC_CREAT | 0600)) < 0) { - m_flash_pid = -1; - fprintf(stderr, "[SendFlashCommand] Failed to create semaphore"); - return; + SetFlashPid(-1); + utils::log("[SendFlashCommand] Failed to create semaphore\n"); + return false; } } @@ -318,45 +1245,55 @@ void BotClient::SendFlashCommand(Message *message, Message *response) static timespec timeout { .tv_sec = 1, .tv_nsec = 0 }; sembuf sop[2] { { 0, -1, 0 }, { 1, 0, 0 } }; + bool success = true; + // Notify - sop[0] = { 0, -1, 0 }; if (semtimedop(m_flash_sem, &sop[0], 1, &timeout) == -1) { if (errno == EAGAIN) { - fprintf(stderr, "[SendFlashCommand] Failed to send command to flash, timeout\n"); - return; + utils::log("[SendFlashCommand] Failed to send command to flash, notify timeout\n"); } - perror("[SendFlashCommand] semop failed"); - return; + else + { + utils::log("[SendFlashCommand] semop failed: {}\n", std::strerror(errno)); + } + success = false; } // Wait - sop[1] = { 1, 0, 0 }; - if (semtimedop(m_flash_sem, &sop[1], 1, &timeout) == -1) + if (success && semtimedop(m_flash_sem, &sop[1], 1, &timeout) == -1) { if (errno == EAGAIN) { - fprintf(stderr, "[SendFlashCommand] Failed to send command to flash, timeout\n"); - return; + utils::log("[SendFlashCommand] Failed to send command to flash, wait timeout\n"); } - perror("[SendFlashCommand] semop failed"); - return; + else + { + utils::log("[SendFlashCommand] semop failed: {}\n", std::strerror(errno)); + } + success = false; } - if (response) + if (response && success) { memcpy(response, m_shared_mem_flash, sizeof(Message)); } + + return success; } bool BotClient::SendNotification(uintptr_t screen_manager, const std::string &name, const std::vector &args) { Message message; message.type = MessageType::SEND_NOTIFICATION; - message.notify.argc = args.size(); - std::memcpy(message.notify.argv, args.data(), sizeof(message.notify.argv)); + size_t cap = sizeof(message.notify.argv) / sizeof(message.notify.argv[0]); + size_t to_copy = std::min(args.size(), cap); + message.notify.argc = to_copy; + if (to_copy) + std::memcpy(message.notify.argv, args.data(), to_copy * sizeof(message.notify.argv[0])); std::strncpy(message.notify.name, name.c_str(), sizeof(message.notify.name)); + message.notify.name[sizeof(message.notify.name) - 1] = '\0'; SendFlashCommand(&message); return true; } @@ -380,6 +1317,7 @@ bool BotClient::UseItem(const std::string &name, uint8_t type, uint8_t bar) message.item.action_type = type; message.item.action_bar = bar; std::strncpy(message.item.name, name.c_str(), sizeof(message.item.name)); + message.item.name[sizeof(message.item.name) - 1] = '\0'; SendFlashCommand(&message); return true; } @@ -391,8 +1329,11 @@ uintptr_t BotClient::CallMethod(uintptr_t obj, uint32_t index, const std::vector message.call.object = obj; message.call.index = index; - message.call.argc = args.size(); - memcpy(message.call.argv, args.data(), args.size() * sizeof(uintptr_t)); + size_t cap = sizeof(message.call.argv) / sizeof(message.call.argv[0]); + size_t to_copy = std::min(args.size(), cap); + message.call.argc = to_copy; + if (to_copy) + memcpy(message.call.argv, args.data(), to_copy * sizeof(uintptr_t)); Message response; @@ -401,24 +1342,231 @@ uintptr_t BotClient::CallMethod(uintptr_t obj, uint32_t index, const std::vector return response.result.value; } -bool BotClient::ClickKey(uint32_t key) +/** + * Sends a key click event to the flash process via shared memory and semaphores. + * + * Note: works a bit better than sending command to the browser. + */ +bool BotClient::KeyClickLegacy(uint32_t key) { Message message; message.type = MessageType::KEY_CLICK; message.key.key = key; - SendFlashCommand(&message); - return true; + return SendFlashCommand(&message); } -bool BotClient::MouseClick(int32_t x, int32_t y, uint32_t button) +void BotClient::KeyClick(uint32_t key) +{ + // First try sending key click via legacy flash IPC method (works a bit better). + bool success = KeyClickLegacy(key); + + // If failed, then send via browser command + if (!success) + success = SendBrowserCommand("keyClick", {{"key", std::to_string(key)}}); +} + +void BotClient::KeyDown(uint32_t key) +{ + SendBrowserCommand("keyDown", {{"key", std::to_string(key)}}); +} + +void BotClient::KeyUp(uint32_t key) +{ + SendBrowserCommand("keyUp", {{"key", std::to_string(key)}}); +} + +void BotClient::SendText(const std::string &text) +{ + SendBrowserCommand("text", {{"text", utils::escape_json(text)}}); +} + +/** + * Sends a mouse click event to the flash process via shared memory and semaphores, + * using the legacy method when X11 control is unavailable. + * + * Note: may not work properly for some game actions. + */ +bool BotClient::MouseClickLegacy(int32_t x, int32_t y) { Message message; message.type = MessageType::MOUSE_CLICK; message.click.x = x; message.click.y = y; - message.click.button = button; - SendFlashCommand(&message); - return true; + message.click.button = 1; + return SendFlashCommand(&message); +} + +void BotClient::MouseClick(int32_t x, int32_t y) +{ + // First try sending click via X11 for better compatibility with all game actions + bool success = window::with_browser(FlashPid(), Pid(), [=](Display *display, Window browser) { + return mouse::send_button(display, browser, x, y, Button1, true, true); + }); + + // If X11 method failed, fall back to legacy flash IPC method. + if (!success) + success = MouseClickLegacy(x, y); + + if (success) + UpdateCursorMarker(x, y); +} + +void BotClient::MouseMove(int32_t x, int32_t y) +{ + bool success = window::with_browser(FlashPid(), Pid(), [=](Display *display, Window browser) { + return mouse::send_move(display, browser, x, y); + }); + + if (success) + UpdateCursorMarker(x, y); +} + +void BotClient::MouseDown(int32_t x, int32_t y) +{ + bool success = window::with_browser(FlashPid(), Pid(), [=](Display *display, Window browser) { + return mouse::send_button(display, browser, x, y, Button1, true, false); + }); + + if (success) + UpdateCursorMarker(x, y); +} + +void BotClient::MouseUp(int32_t x, int32_t y) +{ + bool success = window::with_browser(FlashPid(), Pid(), [=](Display *display, Window browser) { + return mouse::send_button(display, browser, x, y, Button1, false, true); + }); + + if (success) + UpdateCursorMarker(x, y); +} + +void BotClient::MouseScroll(int32_t x, int32_t y, int32_t delta) +{ + int button = delta >= 0 ? Button4 : Button5; + bool success = window::with_browser(FlashPid(), Pid(), [=](Display *display, Window browser) { + return mouse::send_wheel(display, browser, x, y, button); + }); + + if (success) + UpdateCursorMarker(x, y); +} + +// process a batch of encoded native actions. +void BotClient::PostActions(const std::vector &actions) +{ + std::lock_guard lock(m_post_actions_mutex); + + const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(5000); + + for (uint64_t value : actions) + { + if (std::chrono::steady_clock::now() >= deadline) + break; + + uint16_t message = static_cast((value >> 48) & 0x7fff); + int16_t wparam = static_cast((value >> 32) & 0xffff); + int16_t lparam_low = static_cast(value & 0xffff); + int16_t lparam_high = static_cast((value >> 16) & 0xffff); + + int32_t x = static_cast(lparam_low); + int32_t y = static_cast(lparam_high); + uint32_t key = static_cast(wparam); + + // Handle native mouse and keyboard events based on the message type. + // https://github.com/darkbot-reloaded/DarkBot/blob/master/src/main/java/eu/darkbot/api/utils/NativeAction.java + + switch (message) + { + case 0x1FF: // Mouse CLICK + MouseClick(x, y); + break; + case 0x200: // Mouse MOVE + MouseMove(x, y); + break; + case 0x201: // Mouse DOWN + MouseDown(x, y); + break; + case 0x202: // Mouse UP + MouseUp(x, y); + break; + case 0x20A: // Mouse WHEEL + MouseScroll(x, y, wparam); + break; + case 0x1FE: // Key CLICK + KeyClick(key); + break; + case 0x100: // Key DOWN + KeyDown(key); + break; + case 0x101: // Key UP + KeyUp(key); + break; + case 0x102: // Key CHAR + { + std::string text(1, static_cast(wparam)); + SendText(text); + } + break; + default: + // unsupported message, ignore + break; + } + } +} + +// paste a string to the game, optionally performing native actions before/after +void BotClient::PasteText(const std::string &text, const std::vector &actions) +{ + // split inline vector into before/after lists using high-bit flag + const uint64_t AFTER_MASK = (1ULL << 63); + std::vector before; + std::vector after; + for (uint64_t v : actions) { + if (v & AFTER_MASK) + after.push_back(v); + else + before.push_back(v); + } + + { + std::lock_guard lock(m_paste_mutex); + m_paste_queue.push({before, text, after}); + } + + // start worker thread once + if (!m_paste_worker_running.exchange(true)) { + std::thread([this]() { + while (true) { + std::tuple, std::string, std::vector> item; + { + std::lock_guard lock(m_paste_mutex); + if (m_paste_queue.empty()) + break; + item = m_paste_queue.front(); + m_paste_queue.pop(); + } + + auto &[before_actions, str, after_actions] = item; + + if (!before_actions.empty()) + { + PostActions(before_actions); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + + SendText(str); + std::this_thread::sleep_for(std::chrono::milliseconds(750)); + + if (!after_actions.empty()) + { + PostActions(after_actions); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + } + m_paste_worker_running = false; + }).detach(); + } } int BotClient::CheckMethodSignature(uintptr_t object, uint32_t index, bool check_name, const std::string &sig) @@ -430,9 +1578,28 @@ int BotClient::CheckMethodSignature(uintptr_t object, uint32_t index, bool check message.sig.method_name = check_name; strncpy(message.sig.signature, sig.c_str(), sizeof(message.sig.signature)); + message.sig.signature[sizeof(message.sig.signature) - 1] = '\0'; Message response; SendFlashCommand(&message, &response); return response.sig.result; } + +void BotClient::EnableCursorMarker(bool enable) +{ + if (enable == cursor_marker::state.enabled) + return; + + cursor_marker::state.enabled = enable; + if (!enable) + { + std::lock_guard lock(cursor_marker::state.mutex); + cursor_marker::destroy(); + } +} + +void BotClient::UpdateCursorMarker(int32_t x, int32_t y) +{ + cursor_marker::update(x, y, FlashPid(), Pid()); +} diff --git a/client/bot_client.h b/client/bot_client.h index b677bb8..d9cc7d0 100644 --- a/client/bot_client.h +++ b/client/bot_client.h @@ -1,11 +1,24 @@ #ifndef BOT_CLIENT_H #define BOT_CLIENT_H #include +#include +#include +#include +#include +#include +#include +#include #include "proc_util.h" class SockIpc; union Message; +struct JsonParam +{ + const char *key; + std::string_view value; +}; + class BotClient { public: @@ -18,26 +31,50 @@ class BotClient m_url = url; } + void Refresh(); void LaunchBrowser(); void SetPid(int pid) { m_browser_pid = pid; } + void SetFlashPid(int pid) { m_flash_pid = pid; } inline int Pid() const { return m_browser_pid; } inline int FlashPid() const { return m_flash_pid; } bool IsValid(); - void SendBrowserCommand(const std::string &&s, int sync); + bool SendBrowserCommand(const std::string &cmd, std::initializer_list params = {}); + void ToggleBrowserVisibility(bool visible); - void SendFlashCommand(Message *message, Message *response = nullptr); + // returns true if the command was successfully processed by flash + bool SendFlashCommand(Message *message, Message *response = nullptr); bool RefineOre(uintptr_t refine_util, uint32_t ore, uint32_t amount); bool SendNotification(uintptr_t screen_manager, const std::string &name, const std::vector &args); bool UseItem(const std::string &name, uint8_t type, uint8_t bar); uintptr_t CallMethod(uintptr_t obj, uint32_t index, const std::vector &args); - bool ClickKey(uint32_t key); - bool MouseClick(int32_t x, int32_t y, uint32_t button); + bool KeyClickLegacy(uint32_t key); + void KeyClick(uint32_t key); + void KeyDown(uint32_t key); + void KeyUp(uint32_t key); + void SendText(const std::string &text); + bool MouseClickLegacy(int32_t x, int32_t y); + void MouseClick(int32_t x, int32_t y); + void MouseMove(int32_t x, int32_t y); + void MouseDown(int32_t x, int32_t y); + void MouseUp(int32_t x, int32_t y); + void MouseScroll(int32_t x, int32_t y, int32_t delta); int CheckMethodSignature(uintptr_t object, uint32_t index, bool check_name, const std::string &sig); + // batch processing of native actions coming from the Java layer + void PostActions(const std::vector &actions); + + // paste text with optional before/after actions; thread‑safe queuing + void PasteText(const std::string &text, const std::vector &actions); + + // testing helper - show a red dot at the virtual cursor position + void EnableCursorMarker(bool enable); + void UpdateCursorMarker(int32_t x, int32_t y); + + // utility templates that are used by JNI wrapper; keep public so the JNI code can call them template T Read(uintptr_t address, int *result = nullptr) { @@ -92,7 +129,6 @@ class BotClient } - private: std::unique_ptr m_browser_ipc; char *m_shared_mem = nullptr; @@ -106,8 +142,19 @@ class BotClient int m_browser_pid = -1, m_flash_pid = -1; + // protects PostActions from concurrent invocation + std::mutex m_post_actions_mutex; + + // queue used by PasteText + std::mutex m_paste_mutex; + std::queue, std::string, std::vector>> m_paste_queue; + std::atomic m_paste_worker_running{false}; + bool find_flash_process(); void reset(); + + // helpers for browser IPC + bool ensure_browser_ipc_connected(); }; diff --git a/client/eu_darkbot_api_DarkTanos.cpp b/client/eu_darkbot_api_DarkTanos.cpp index 4c00f5a..866f41c 100644 --- a/client/eu_darkbot_api_DarkTanos.cpp +++ b/client/eu_darkbot_api_DarkTanos.cpp @@ -2,12 +2,17 @@ #include "eu_darkbot_api_DarkTanos.h" #include #include +#include #include "bot_client.h" #include "utils.h" static BotClient client; +__attribute__((constructor)) static void lib_ctor() +{ + utils::log_timestamp_str(); // force timestamp init before any log call +} JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_setData (JNIEnv *env, jobject, jstring jurl, jstring jsid, jstring preloader, jstring vars) @@ -32,25 +37,27 @@ JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_createWindow JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_setSize (JNIEnv *, jobject, jint jw, jint jh) { - client.SendBrowserCommand(utils::format("setSize|{}|{}", jw, jh), 0); + client.SendBrowserCommand("setSize", {{"w", std::to_string(jw)}, {"h", std::to_string(jh)}}); } JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_setVisible (JNIEnv *, jobject, jboolean jv) { - client.SendBrowserCommand(utils::format("setVisible|{}", int(jv)), 0); + client.ToggleBrowserVisibility(jv); } JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_setMinimized (JNIEnv *, jobject, jboolean jv) { - client.SendBrowserCommand(utils::format("minimize|{}", int(jv)), 0); + // Using the same hiding approach as with "setVisible", since minimizing causes lags and increases the tick. + // The boolean "jv" is inverted because "setMinimized(true)" should hide the window. + client.ToggleBrowserVisibility(!jv); } JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_reload (JNIEnv *, jobject) { - client.SendBrowserCommand("refresh", 1); + client.Refresh(); } JNIEXPORT jboolean JNICALL Java_eu_darkbot_api_DarkTanos_isValid @@ -62,24 +69,27 @@ JNIEXPORT jboolean JNICALL Java_eu_darkbot_api_DarkTanos_isValid JNIEXPORT jlong JNICALL Java_eu_darkbot_api_DarkTanos_getMemoryUsage (JNIEnv *, jobject) { - if (client.FlashPid() > 0) - { - return ProcUtil::GetMemoryUsage(client.FlashPid()) / 1024; - } - return ProcUtil::GetMemoryUsage(client.Pid()) / 1024; + pid_t pid = client.FlashPid() > 0 ? client.FlashPid() : client.Pid(); + return ProcUtil::GetMemoryUsage(pid) / 1024; +} + +JNIEXPORT jdouble JNICALL Java_eu_darkbot_api_DarkTanos_getCpuUsage + (JNIEnv *, jobject) +{ + pid_t pid = client.FlashPid() > 0 ? client.FlashPid() : client.Pid(); + return ProcUtil::GetCpuUsage(pid); } JNIEXPORT jint JNICALL Java_eu_darkbot_api_DarkTanos_getVersion (JNIEnv *, jobject) { - return 6; + return API_VERSION; } JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_keyClick (JNIEnv *, jobject, jint c) { - //client.SendBrowserCommand(utils::format("pressKey|{}", c), 1); - client.ClickKey(c); + client.KeyClick(c); } JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_sendText @@ -88,32 +98,75 @@ JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_sendText const char *cstr = env->GetStringUTFChars(jtext, NULL); std::string text = cstr; env->ReleaseStringUTFChars(jtext, cstr); - client.SendBrowserCommand("text|" + text, 1); + client.SendText(text); +} + +JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_pasteText + (JNIEnv *env, jobject, jstring jtext, jlongArray jactions) +{ + // convert string + const char *cstr = env->GetStringUTFChars(jtext, NULL); + std::string text = cstr; + env->ReleaseStringUTFChars(jtext, cstr); + + // convert actions array + std::vector actions; + if (jactions) { + jsize len = env->GetArrayLength(jactions); + if (len > 0) { + actions.resize(static_cast(len)); + env->GetLongArrayRegion(jactions, 0, len, reinterpret_cast(actions.data())); + } + } + + client.PasteText(text, actions); } JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_mouseMove (JNIEnv *, jobject, jint x, jint y) { - client.SendBrowserCommand(utils::format("mouseMove|{}|{}", x, y), 1); + client.MouseMove(x, y); } JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_mouseDown (JNIEnv *, jobject, jint x, jint y) { - client.SendBrowserCommand(utils::format("mousePress|{}|{}", x, y), 1); + client.MouseDown(x, y); } JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_mouseUp (JNIEnv *, jobject, jint x, jint y) { - client.SendBrowserCommand(utils::format("mouseRelease|{}|{}", x, y), 1); + client.MouseUp(x, y); } JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_mouseClick (JNIEnv *, jobject, jint x, jint y) { - //client.SendBrowserCommand(utils::format("mouseClick|{}|{}", x, y), 1); - client.MouseClick(x, y, 1); + client.MouseClick(x, y); +} + +JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_setCursorMarker + (JNIEnv *, jobject, jboolean enable) +{ + // Show red dot at the cursor position (useful for debugging) + client.EnableCursorMarker(enable); +} + +JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_postActions + (JNIEnv *env, jobject, jlongArray jactions) +{ + if (!jactions) + return; + + jsize len = env->GetArrayLength(jactions); + if (len <= 0) + return; + + std::vector actions(static_cast(len)); + env->GetLongArrayRegion(jactions, 0, len, reinterpret_cast(actions.data())); + + client.PostActions(actions); } JNIEXPORT jint JNICALL Java_eu_darkbot_api_DarkTanos_readInt diff --git a/client/eu_darkbot_api_DarkTanos.h b/client/eu_darkbot_api_DarkTanos.h index e2950fa..77a6150 100644 --- a/client/eu_darkbot_api_DarkTanos.h +++ b/client/eu_darkbot_api_DarkTanos.h @@ -71,6 +71,14 @@ JNIEXPORT jboolean JNICALL Java_eu_darkbot_api_DarkTanos_isValid JNIEXPORT jlong JNICALL Java_eu_darkbot_api_DarkTanos_getMemoryUsage (JNIEnv *, jobject); +/* + * Class: eu_darkbot_api_DarkTanos + * Method: getCpuUsage + * Signature: ()D + */ +JNIEXPORT jdouble JNICALL Java_eu_darkbot_api_DarkTanos_getCpuUsage + (JNIEnv *, jobject); + /* * Class: eu_darkbot_api_DarkTanos * Method: getVersion @@ -95,6 +103,14 @@ JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_keyClick JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_sendText (JNIEnv *, jobject, jstring); +/* + * Class: eu_darkbot_api_DarkTanos + * Method: pasteText + * Signature: (Ljava/lang/String;[J)V + */ +JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_pasteText + (JNIEnv *, jobject, jstring, jlongArray); + /* * Class: eu_darkbot_api_DarkTanos * Method: mouseMove @@ -127,6 +143,22 @@ JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_mouseUp JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_mouseClick (JNIEnv *, jobject, jint, jint); +/* + * Class: eu_darkbot_api_DarkTanos + * Method: setCursorMarker + * Signature: (Z)V + */ +JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_setCursorMarker + (JNIEnv *, jobject, jboolean); + +/* + * Class: eu_darkbot_api_DarkTanos + * Method: postActions + * Signature: ([J)V + */ +JNIEXPORT void JNICALL Java_eu_darkbot_api_DarkTanos_postActions + (JNIEnv *, jobject, jlongArray); + /* * Class: eu_darkbot_api_DarkTanos * Method: readInt diff --git a/client/ipc.cpp b/client/ipc.cpp index 38dad4f..2a90dd5 100644 --- a/client/ipc.cpp +++ b/client/ipc.cpp @@ -37,29 +37,31 @@ union Message }; -bool ipc::Server::Init(int pid) +bool ipc::Server::Init() { + int pid = getpid(); + if ((shmid = shmget(pid, MEM_SIZE, IPC_CREAT | 0666)) < 0) { - utils::format("[ipc::Server::init] Failed to get shared memory: {}\n", strerror(errno)); + utils::log("[ipc::Server::init] Failed to get shared memory: {}\n", strerror(errno)); return false; } if ((sem = semget(pid, 2, IPC_CREAT | 0666)) < 0) { - utils::format("[ipc::Server::init] Failed to create shared semaphore: {}\n", strerror(errno)); + utils::log("[ipc::Server::init] Failed to create shared semaphore: {}\n", strerror(errno)); return false; } if ((shared = reinterpret_cast(shmat(shmid, NULL, 0))) == (Message *)-1) { - utils::format("[ipc::Server::init] Failed to attach shared memory to our process: {}\n", strerror(errno)); + utils::log("[ipc::Server::init] Failed to attach shared memory to our process: {}\n", strerror(errno)); return false; } if (semctl(sem, 0, SETVAL, 1) == -1 || semctl(sem, 1, SETVAL, 0) == -1) { - utils::format("[ipc::Server::init] semctl failed: {}\n", strerror(errno)); + utils::log("[ipc::Server::init] semctl failed: {}\n", strerror(errno)); return false; } @@ -70,9 +72,9 @@ void ipc::Server::Remove() { union semun dummy; if (shmctl(shmid, IPC_RMID, NULL) == -1) - utils::format("[ipc::Server::Remove] shmctl failed: {}\n", strerror(errno)); + utils::log("[ipc::Server::Remove] shmctl failed: {}\n", strerror(errno)); if (semctl(sem, 0, IPC_RMID, dummy) == -1) - utils::format("[ipc::Server::Remove] semctl failed: {}\n", strerror(errno)); + utils::log("[ipc::Server::Remove] semctl failed: {}\n", strerror(errno)); } void ipc::Server::handle_message() @@ -97,7 +99,7 @@ void ipc::Server::runner() { if (errno == EINTR) continue; - utils::format("[ipc::Server::runner] semop1 failed {}\n", strerror(errno)); + utils::log("[ipc::Server::runner] semop1 failed {}\n", strerror(errno)); running = false; break; } @@ -108,7 +110,7 @@ void ipc::Server::runner() sop[1] = { .sem_num=1, .sem_op=-1, .sem_flg=0 }; if (semop(sem, sop, 2) == -1) { - utils::format("[ipc::Server::runner] semop2 failed {}\n", strerror(errno)); + utils::log("[ipc::Server::runner] semop2 failed {}\n", strerror(errno)); running = false; break; } @@ -120,25 +122,25 @@ bool ipc::Client::Connect(int ipc_id) { if ((shmid = shmget(ipc_id, MEM_SIZE, IPC_CREAT | 0666)) < 0) { - perror("Failed to get shared memory"); + utils::log("[ipc::Client::Connect] Failed to get shared memory: {}\n", strerror(errno)); return false; } if ((shared = shmat(shmid, NULL, 0)) == (void *)-1) { - perror("Failed to attach shared memory to our process"); + utils::log("[ipc::Client::Connect] Failed to attach shared memory to our process: {}\n", strerror(errno)); return false; } if ((sem = semget(ipc_id, 2, IPC_CREAT | 0600)) < 0) { - perror("Failed to create shared semaphore"); + utils::log("[ipc::Client::Connect] Failed to create shared semaphore: {}\n", strerror(errno)); return false; } if (semctl(sem, 0, SETVAL, 1) == -1 || semctl(sem, 1, SETVAL, 0) == -1) { - perror("Failed to set browser ipc sempahore value"); + utils::log("[ipc::Client::Connect] Failed to set browser ipc semaphore value: {}\n", strerror(errno)); return false; } diff --git a/client/proc_util.cpp b/client/proc_util.cpp index 9881a02..532eb00 100644 --- a/client/proc_util.cpp +++ b/client/proc_util.cpp @@ -2,67 +2,82 @@ #include #include #include +#include +#include + #include +#include "masked_bmh.h" +#include +#include #include #include bool ProcUtil::IsChildOf(pid_t child_pid, pid_t test_parent) { auto pid = child_pid; - while (true) + // walk up the parent chain but avoid an infinite loop; limit depth + const int max_depth = 128; + for (int depth = 0; depth < max_depth; ++depth) { - if (std::ifstream fi { "/proc/"+std::to_string(pid)+"/stat" } ) - { - pid_t _pid; - std::string name; - char state; - pid_t parent; - fi >> _pid >> name >> state >> parent; - - if (parent == test_parent) - return true; - else if (parent == 1) - break; - - pid = parent; - } + std::ifstream fi { "/proc/"+std::to_string(pid)+"/stat" }; + if (!fi) + break; + + pid_t _pid; + std::string name; + char state; + pid_t parent; + fi >> _pid >> name >> state >> parent; + + if (parent == test_parent) + return true; + if (parent <= 1 || parent == pid) + break; + + pid = parent; } return false; } -std::vector ProcUtil::FindProcsByName(const std::string &pattern) +// Concrete entry point; callers pass an initializer_list of patterns. +std::vector ProcUtil::FindProcsByName(const std::initializer_list &patterns) { std::vector result; + + // nothing to search for + if (patterns.size() == 0) + return result; + for (const auto &entry : std::filesystem::directory_iterator("/proc/")) { const std::string &path_name = entry.path().filename().string(); - pid_t pid = atoi(path_name.c_str()); if (!entry.is_directory() || !pid) continue; - auto cmd_path = entry.path() / "cmdline"; - - if (std::ifstream cmdline_f { cmd_path.string(), std::ios::binary}) + auto cmd_path = std::filesystem::path("/proc") / std::to_string(pid) / "cmdline"; + std::ifstream cmdline_f { cmd_path.string(), std::ios::binary }; + if (cmdline_f) { - std::string contents; - - char buf[1024] { 0 }; - - bool stop = false; - while (!stop) + std::string contents((std::istreambuf_iterator(cmdline_f)), std::istreambuf_iterator()); + if (!contents.empty()) { - stop = !cmdline_f.read(buf, sizeof(buf)); - contents.insert(contents.end(), buf, &buf[cmdline_f.gcount()]); + std::replace_if(contents.begin(), contents.end(), [] (char c) { return c == 0; }, ' '); + bool match = true; + for (const auto &pat : patterns) + { + if (contents.find(pat) == std::string::npos) + { + match = false; + break; + } + } + if (match) + result.push_back(pid); } - - std::replace_if(contents.begin(), contents.end(), [] (char c) { return c == 0; }, ' '); - - if (contents.find(pattern) != std::string::npos) - result.push_back(pid); } } @@ -124,60 +139,202 @@ std::vector ProcUtil::GetPages(pid_t pid, const std::string & uint64_t ProcUtil::GetMemoryUsage(pid_t pid) { - if (std::ifstream fi { "/proc/"+std::to_string(pid)+"/stat" }) + char path[64]; + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + + char buf[512]; + int fd = ::open(path, O_RDONLY); + if (fd < 0) return 0; + ssize_t n = ::read(fd, buf, sizeof(buf) - 1); + ::close(fd); + if (n <= 0) return 0; + buf[n] = '\0'; + + // Skip comm field (may contain spaces/parens): scan past last ')' + const char *rp = strrchr(buf, ')'); + if (!rp || rp[1] == '\0') return 0; + + char state; + int ppid, pgrp, session, tty_nr, tpgid; + unsigned int flags; + unsigned long minflt, cminflt, majflt, cmajflt, utime, stime; + long cutime, cstime, priority, nice, num_threads, itrealvalue; + unsigned long long starttime; + unsigned long vsize; + long rss; + + if (sscanf(rp + 2, + "%c %d %d %d %d %d %u " + "%lu %lu %lu %lu %lu %lu %ld %ld " + "%ld %ld %ld %ld %llu " + "%lu %ld", + &state, &ppid, &pgrp, &session, &tty_nr, &tpgid, &flags, + &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime, + &cutime, &cstime, + &priority, &nice, &num_threads, &itrealvalue, + &starttime, + &vsize, &rss) != 22) + return 0; + + const long page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; + return static_cast(rss) * static_cast(page_size_kb); +} + +double ProcUtil::GetCpuUsage(pid_t pid) +{ + struct CpuStat { + uint64_t proc_ticks = 0; + uint64_t start_time = 0; + uint64_t last_ms = 0; + double cached = 0.0; + }; + + static thread_local std::unordered_map prev; + + static const double clk_tck = static_cast(sysconf(_SC_CLK_TCK)); + static const double nproc = static_cast(sysconf(_SC_NPROCESSORS_ONLN)); + + auto &p = prev[pid]; + + // --- Time --- + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + const uint64_t now_ms = + static_cast(now.tv_sec) * 1000ull + + static_cast(now.tv_nsec) / 1'000'000ull; + + const uint64_t elapsed_ms = now_ms - p.last_ms; + + if (elapsed_ms < 250u) + return p.cached; + + const double total_delta = static_cast(elapsed_ms) * clk_tck / 1000.0; + if (total_delta <= 0.0) + return p.cached; + + // --- Read /proc//stat --- + uint64_t proc_ticks = 0; + uint64_t start_time = 0; + { - std::string _pid, comm, state, ppid, pgrp, session, tty_nr; - std::string tpgid, flags, minflt, cminflt, majflt, cmajflt; - std::string utime, stime, cutime, cstime, priority, nice; - std::string O, itrealvalue, starttime; - - // the two fields we want - uint64_t vsize; - int64_t rss; - - fi >> _pid >> comm >> state >> ppid >> pgrp >> session >> tty_nr - >> tpgid >> flags >> minflt >> cminflt >> majflt >> cmajflt - >> utime >> stime >> cutime >> cstime >> priority >> nice - >> O >> itrealvalue >> starttime >> vsize >> rss; // don't care about the rest - - int64_t page_size_kb = sysconf(_SC_PAGE_SIZE) / 1024; // in case x86-64 is configured to use 2MB pages - auto vm_usage = vsize / 1024; - auto resident_set = rss * page_size_kb; - return resident_set; + char path[64]; + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + + char buf[1024]; // safer buffer + int fd = ::open(path, O_RDONLY); + if (fd < 0) + return p.cached; + + ssize_t n = ::read(fd, buf, sizeof(buf) - 1); + ::close(fd); + + if (n <= 0) + return p.cached; + + buf[n] = '\0'; + + const char *rp = strrchr(buf, ')'); + if (!rp || rp[1] == '\0') + return p.cached; + + char state; + int ppid, pgrp, session, tty_nr, tpgid; + unsigned int flags; + unsigned long minflt, cminflt, majflt, cmajflt, utime, stime; + long cutime, cstime, priority, nice, num_threads, itrealvalue; + unsigned long long starttime; + + if (sscanf(rp + 2, + "%c %d %d %d %d %d %u " + "%lu %lu %lu %lu %lu %lu %ld %ld " + "%ld %ld %ld %ld %llu", + &state, &ppid, &pgrp, &session, &tty_nr, &tpgid, &flags, + &minflt, &cminflt, &majflt, &cmajflt, + &utime, &stime, &cutime, &cstime, + &priority, &nice, &num_threads, &itrealvalue, + &starttime) != 20) + { + return p.cached; + } + + proc_ticks = utime + stime + cutime + cstime; // include children + start_time = starttime; } - return 0; + + p.last_ms = now_ms; + + // --- Compute usage --- + if (p.proc_ticks > 0 && p.start_time == start_time) + { + const double proc_delta = static_cast(proc_ticks - p.proc_ticks); + + // Normalized CPU usage (max = 100%) + const double current = (proc_delta / total_delta) * 100.0 / nproc; + + constexpr double alpha = 0.35; + p.cached = p.cached * (1.0 - alpha) + current * alpha; + } + else + { + p.cached = 0.0; + } + + p.proc_ticks = proc_ticks; + p.start_time = start_time; + + return p.cached; } int ProcUtil::QueryMemory(pid_t pid, unsigned char *query, const char *mask, uintptr_t *out, uint32_t amount) { - uint32_t finds = 0, - alignment= 1; + if (!query || !mask || !out || amount == 0) + return 0; + + const size_t query_size = std::strlen(mask); + if (query_size == 0) + return 0; + + uint32_t finds = 0; + const uint32_t alignment = 1; - size_t query_size = strlen(mask); + static thread_local std::vector buffer; // reused per thread to avoid repeated allocations + const uint8_t *query_bytes = reinterpret_cast(query); - for (auto ®ion : GetPages(pid)) + for (const auto ®ion : GetPages(pid)) { - size_t size = region.end - region.start; - if (query_size > size) - continue; + if (finds == amount) break; - std::vector buf(size); + const size_t region_size = region.end - region.start; + if (query_size > region_size) continue; - ssize_t bytes_read = ReadMemoryBytes(pid, region.start, buf.data(), size); + buffer.resize(region_size); + const ssize_t bytes_read = ReadMemoryBytes(pid, region.start, buffer.data(), region_size); + if (bytes_read < static_cast(query_size)) continue; - if (bytes_read < static_cast(query_size)) - continue; + size_t offset = 0; + const size_t readable = static_cast(bytes_read); - for (size_t i = 0 ; finds != amount && i < (bytes_read - query_size) ; i+=alignment) + while (finds != amount) { - bool found = true; - for (uintptr_t j = 0; j < query_size && found; j++) - found &= (buf[i + j] == query[j]) | (mask[j] == '?'); - if (found) - out[finds++] = region.start + i; + const size_t found = masked_bmh_search( + buffer.data(), + readable, + query_bytes, + mask, + query_size, + offset, + alignment); + + if (found == SIZE_MAX) break; + + out[finds++] = region.start + found; + offset = found + 1; + if (offset + query_size > readable) break; } } - return finds; + + return static_cast(finds); } uintptr_t ProcUtil::FindPattern(pid_t pid, const std::string &query, const std::string &segment) diff --git a/client/proc_util.h b/client/proc_util.h index 82d1bf0..535c13d 100644 --- a/client/proc_util.h +++ b/client/proc_util.h @@ -29,7 +29,7 @@ namespace ProcUtil bool IsChildOf(pid_t child_pid, pid_t test_parent); - std::vector FindProcsByName(const std::string &n); + std::vector FindProcsByName(const std::initializer_list &patterns); bool ProcessExists(pid_t pid); @@ -45,6 +45,7 @@ namespace ProcUtil std::vector GetPages(pid_t pid, const std::string &name = ""); uint64_t GetMemoryUsage(pid_t pid); + double GetCpuUsage(pid_t pid); class Process { diff --git a/client/sock_ipc.cpp b/client/sock_ipc.cpp index df05c0c..4153bb4 100644 --- a/client/sock_ipc.cpp +++ b/client/sock_ipc.cpp @@ -6,13 +6,11 @@ #include #include #include +#include // for fcntl, O_NONBLOCK SockIpc::SockIpc() { - if ((m_sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) - { - throw std::runtime_error { "Failed to create ipc socket" }; - } + m_sock = -1; } SockIpc::~SockIpc() @@ -23,23 +21,101 @@ SockIpc::~SockIpc() } } +static int create_unix_socket() +{ + int s = socket(AF_UNIX, SOCK_STREAM, 0); + if (s < 0) + throw std::runtime_error { "Failed to create ipc socket" }; + return s; +} + bool SockIpc::Connect(const std::string &path) { + // if we already have a socket, close it before recreating + if (m_sock != -1) + { + close(m_sock); + m_sock = -1; + m_connected = false; + } + + m_sock = create_unix_socket(); + sockaddr_un m_remote; m_remote.sun_family = AF_UNIX; strncpy(m_remote.sun_path, path.c_str(), sizeof(m_remote.sun_path)); - + m_remote.sun_path[sizeof(m_remote.sun_path) - 1] = '\0'; if (connect(m_sock, reinterpret_cast(&m_remote), sizeof(m_remote)) < 0) { + close(m_sock); + m_sock = -1; return false; } + + // operate the socket in non-blocking mode so that a hung browser cannot + // cause our entire process to stall when we try to write to the socket. + int flags = fcntl(m_sock, F_GETFL, 0); + if (flags != -1) + fcntl(m_sock, F_SETFL, flags | O_NONBLOCK); + m_connected = true; return true; } -void SockIpc::Send(const std::string &msg) +bool SockIpc::Send(const std::string &msg) +{ + if (!m_connected || m_sock == -1) + return false; + + const char *buf = msg.data(); + size_t to_write = msg.size(); + size_t written = 0; + while (written < to_write) + { + // MSG_NOSIGNAL prevents SIGPIPE, MSG_DONTWAIT respects the + // non-blocking flag so we return immediately if the buffer is full. + ssize_t n = send(m_sock, buf + written, to_write - written, + MSG_NOSIGNAL | MSG_DONTWAIT); + if (n > 0) + { + written += static_cast(n); + continue; + } + if (n == -1 && errno == EINTR) + continue; + + // an error occurred, mark ourselves disconnected and bail out + m_connected = false; + close(m_sock); + m_sock = -1; + return false; + } + + return true; +} + +// non-blocking receive; returns true if data was read into msg. +bool SockIpc::Recv(std::string &msg) { - write(m_sock, msg.data(), msg.size()); + if (!m_connected || m_sock == -1) + return false; + + char buf[1024]; + ssize_t n = recv(m_sock, buf, sizeof(buf) - 1, MSG_DONTWAIT); + if (n <= 0) + { + if (n == 0 || (n == -1 && errno != EAGAIN && errno != EWOULDBLOCK)) + { + m_connected = false; + close(m_sock); + m_sock = -1; + } + return false; + } + + buf[n] = '\0'; + msg.assign(buf, static_cast(n)); + return true; } diff --git a/client/sock_ipc.h b/client/sock_ipc.h index 46a43ad..030edb9 100644 --- a/client/sock_ipc.h +++ b/client/sock_ipc.h @@ -12,10 +12,21 @@ class SockIpc bool Connected() const { return m_connected; } + // try to establish a connection to the unix domain socket at |path|. + // if a previous socket exists it will be closed and recreated. returns + // true on success, false otherwise. bool Connect(const std::string &path); - void Send(const std::string &msg); + // send a message over the socket. returns true on success; if the write + // fails (broken pipe, connection reset, etc) the object will mark itself + // disconnected so callers can attempt to reconnect. + bool Send(const std::string &msg); + // try to read a message from the socket. returns true if any data was read. + // the call is non‑blocking and will mark the object disconnected on failure. + bool Recv(std::string &msg); + + // internal state; public for legacy code but should not be touched bool m_connected = false; int m_sock = -1; }; diff --git a/client/utils.h b/client/utils.h deleted file mode 100644 index 751aaf7..0000000 --- a/client/utils.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef UTILS_H -#define UTILS_H -#include - -#include -#include -#include - -#define LOG_F(msg, ...) utils::format("[%s] " msg, __func__, __VA_ARGS__); - -namespace utils -{ - static inline void format(std::stringstream &of, const char *data) - { - of << data; - } - - static inline std::string format(const char *data) - { - std::stringstream ss; - format(ss, data); - return ss.str(); - } - - template - static void format(std::stringstream &of, const char *s, T value, Args ... args) - { - const char *start = s; - for (; *s != 0; s++) - { - if (*s == '{' && (s == start || *(s-1) != '\\')) - { - char key = '\x00'; - for (s++; *s != 0; s++) - { - if (*s == ' ') - continue; - else if (*s == '}' && *(s-1) != '\\') - { - if (key == 'x') - of << std::hex; - else - of << std::dec; - of << value; - format(of, s+1, args...); - return; - } - else if(key) - { - key = '\x00'; - break; - } - else - key = *s; - } - } - of << *s; - } - } - - template - static inline std::string format(const char *s, T value, Args ... args) - { - std::stringstream ss; - format(ss, s, value, args...); - return ss.str(); - } - - template - static inline std::string format(const std::string &s, T value, Args ... args) - { - return format(s.c_str(), value, args...); - } -}; - -#endif /* UTILS_H */ diff --git a/copy.sh.example b/copy.sh.example new file mode 100644 index 0000000..9c31f03 --- /dev/null +++ b/copy.sh.example @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +# THIS IS AN EXAMPLE SCRIPT. Copy this file to "copy.sh", make it +# executable (chmod +x copy.sh) and adjust DEST to the location of +# your bot's lib directory on your machine before running the build +# helper (./build.sh) which invokes it automatically if present. + +# Path to bot's lib directory +DEST="../darkbot/lib" + + +# ----------------------------------------------------------------------------- +# DO NOT CHANGE ANYTHING BELOW THIS LINE UNLESS NECESSARY. The paths +# below are used by the build helper; altering them may break the copy step. +# ----------------------------------------------------------------------------- + +# Source directories for the files to copy +BROWSER_APP_DIR="./browser/dist" +CLIENT_LIB_DIR="./build/client" +DO_LIB_DIR="./build/do_lib" + +for src in \ + "$BROWSER_APP_DIR/darkbot_browser_linux.AppImage" \ + "$CLIENT_LIB_DIR/DarkTanos.so" \ + "$DO_LIB_DIR/libdo_lib.so"; do + if [[ -f "$src" ]]; then + cp -v "$src" "$DEST/$(basename "$src")" + else + echo "WARNING: source file not found: $src" >&2 + fi +done diff --git a/do_lib/CMakeLists.txt b/do_lib/CMakeLists.txt index 81a4cf7..5a2b104 100644 --- a/do_lib/CMakeLists.txt +++ b/do_lib/CMakeLists.txt @@ -7,7 +7,7 @@ add_library(${PROJECT_NAME} SHARED disassembler.cpp ipc.cpp darkorbit.cpp - memory_linux.cpp + memory.cpp avm.cpp singleton.cpp flash_stuff.cpp diff --git a/do_lib/avm.cpp b/do_lib/avm.cpp index f22de70..c329ec3 100755 --- a/do_lib/avm.cpp +++ b/do_lib/avm.cpp @@ -1,6 +1,8 @@ #include "avm.h" #include "binary_stream.h" #include "utils.h" +#include +#include avm::ClassClosure * avm::AbcEnv::finddef(const std::string &name) { @@ -37,8 +39,167 @@ avm::ClassClosure * avm::AbcEnv::finddef(std::function cache; + + // quick-path: check cache under lock, but do expensive work outside + { + std::lock_guard lock(cache_mutex); + auto it = cache.find(this); + if (it != cache.end()) + return it->second; + } + + // compute resolved name without holding the mutex + std::string resolved_name = pool->get_method_name(id); + + if (!resolved_name.empty() && declarer.is_traits()) + { + auto *traits = declarer.traits(); + if (traits && traits->traits_pos) + { + BinaryStream s { traits->traits_pos }; + uint32_t trait_count = 0; + + switch (traits->pos_type) + { + case 0: // instance_info + { + /* name */ s.read_u32(); + /* super_name */ s.read_u32(); + + auto flags = s.read_u32(); + if ((flags & 0x8) != 0) + { + /* protected_ns */ s.read_u32(); + } + + auto interface_count = s.read_u32(); + for (uint32_t i = 0; i < interface_count; i++) + { + /* interface */ s.read_u32(); + } + + /* iinit */ s.read_u32(); + trait_count = s.read_u32(); + break; + } + case 1: // class_info + case 2: // script_info + { + /* cinit/init */ s.read_u32(); + trait_count = s.read_u32(); + break; + } + default: + { + break; + } + } + + for (uint32_t j = 0; j < trait_count; j++) + { + /* name */ s.read_u32(); + unsigned char tag = s.read(); + int kind = (tag & avm::TRAIT_mask); + + switch(kind) + { + case avm::TRAIT_Slot: + case avm::TRAIT_Const: + { + /* slot_id */ s.read_u32(); + /* type_name */ s.read_u32(); + uint32_t vindex = s.read_u32(); + if (vindex) + { + /* vkind */ s.read(); + } + break; + } + case avm::TRAIT_Class: + case avm::TRAIT_Function: + { + /* slot_id */ s.read_u32(); + /* class/function index */ s.read_u32(); + break; + } + case avm::TRAIT_Method: + case avm::TRAIT_Getter: + case avm::TRAIT_Setter: + { + /* disp_id */ s.read_u32(); + uint32_t method_index = s.read_u32(); + + if (static_cast(method_index) == id) + { + switch (kind) + { + case avm::TRAIT_Setter: + resolved_name = "set " + resolved_name; + break; + case avm::TRAIT_Getter: + resolved_name = "get " + resolved_name; + break; + default: + break; + } + + // insert into cache and return + std::lock_guard lock(cache_mutex); + cache.try_emplace(this, resolved_name); + return resolved_name; + } + break; + } + default: + { + break; + } + } + + if (tag & avm::ATTR_metadata) + { + uint32_t metadata_count = s.read_u32(); + for (uint32_t i = 0; i < metadata_count; i++) + { + /* metadata index */ s.read_u32(); + } + } + } + } + } + + // insert final resolved name into cache and return + { + std::lock_guard lock(cache_mutex); + auto [it, inserted] = cache.try_emplace(this, resolved_name); + if (!inserted) + return it->second; + return it->second; + } +} + +#include +#include + +static std::mutex g_traits_cache_mutex; +static std::unordered_map g_traits_cache; + avm::MyTraits avm::Traits::parse_traits(avm::PoolObject *custom_pool) { + // fast-path: check global cache + { + std::lock_guard lock(g_traits_cache_mutex); + auto it = g_traits_cache.find(this); + if (it != g_traits_cache.end()) + { + return it->second; // copy + } + } + BinaryStream s { traits_pos }; custom_pool = custom_pool ? custom_pool : pool; MyTraits traits; @@ -62,6 +223,8 @@ avm::MyTraits avm::Traits::parse_traits(avm::PoolObject *custom_pool) /* auto iinit = */ s.read_u32(); uint32_t trait_count = s.read_u32(); + traits.traits.reserve(trait_count); // avoid repeated reallocations + for (uint32_t j = 0; j < trait_count; j++) { MyTrait trait; @@ -100,6 +263,13 @@ avm::MyTraits avm::Traits::parse_traits(avm::PoolObject *custom_pool) trait.id = class_index; break; } + case avm::TRAIT_Function: + { + /* uint32_t slot_id = */ s.read_u32(); + uint32_t function_index = s.read_u32(); + trait.id = function_index; + break; + } case avm::TRAIT_Method: case avm::TRAIT_Getter: case avm::TRAIT_Setter: @@ -131,6 +301,19 @@ avm::MyTraits avm::Traits::parse_traits(avm::PoolObject *custom_pool) traits.add_trait(trait); } + + // insert into cache + { + std::lock_guard lock(g_traits_cache_mutex); + g_traits_cache.try_emplace(this, traits); + } + return traits; } +void avm::clear_traits_cache() +{ + std::lock_guard lock(g_traits_cache_mutex); + g_traits_cache.clear(); +} + diff --git a/do_lib/avm.h b/do_lib/avm.h index 3ca25ca..84fa239 100755 --- a/do_lib/avm.h +++ b/do_lib/avm.h @@ -39,6 +39,7 @@ namespace avm TRAIT_Getter = 0x02, TRAIT_Setter = 0x03, TRAIT_Class = 0x04, + TRAIT_Function = 0x05, TRAIT_Const = 0x06, TRAIT_COUNT = TRAIT_Const+1, TRAIT_mask = 15 @@ -461,6 +462,7 @@ namespace avm BinaryStream str(abc_info); uint32_t param_count = str.read_u32(); /*uint32_t ret_type =*/ str.read_u32(); + result.reserve(param_count); for (uint32_t i = 0; i < param_count; i++) { @@ -469,10 +471,7 @@ namespace avm return result; } - std::string name() - { - return pool->get_method_name(id); - } + std::string name(); inline bool compiled() { return ((flags >> 21) & 1) == 1; @@ -617,6 +616,9 @@ namespace avm MyTraits parse_traits(avm::PoolObject *custom_pool = nullptr); }; + /* Clear cached parsed traits (used to invalidate cached entries when VM memory is freed) */ + void clear_traits_cache(); + struct VTable { uintptr_t vtable; @@ -658,9 +660,9 @@ namespace avm ScriptObject *delegate; template - void write_at(uintptr_t offset, T val) + void write_at(uintptr_t offset, T value) { - *reinterpret_cast(uintptr_t(this) + offset) = val; + std::memcpy(reinterpret_cast((uintptr_t)this + offset), &value, sizeof(T)); } inline AvmCore *core() @@ -707,7 +709,7 @@ namespace avm template void set_at(const T &value, uintptr_t offset) { - *(T *)((uintptr_t)this + offset) = value; + std::memcpy(reinterpret_cast((uintptr_t)this + offset), &value, sizeof(T)); } template @@ -719,7 +721,9 @@ namespace avm template T get_at(uintptr_t offset) const { - return *(T *)((uintptr_t)this + offset); + T v; + std::memcpy(&v, reinterpret_cast((uintptr_t)this + offset), sizeof(T)); + return v; } template diff --git a/do_lib/binary_stream.h b/do_lib/binary_stream.h index 51a954e..dd4fbd7 100644 --- a/do_lib/binary_stream.h +++ b/do_lib/binary_stream.h @@ -12,12 +12,14 @@ class BinaryStream std::string read_string() { - std::string out_str; - for (; data[position] != 0x00; position++) - { - out_str += char(data[position]); - } - + // find the null terminator and construct the string in one allocation + size_t start = position; + while (data[position] != 0x00) + ++position; + size_t len = position - start; + std::string out_str(reinterpret_cast(&data[start]), len); + // advance past the terminator + ++position; return out_str; } @@ -30,7 +32,8 @@ class BinaryStream template inline T read() { - T r = *reinterpret_cast(&data[position]); + T r; + std::memcpy(&r, &data[position], sizeof(T)); position += sizeof(T); return r; } @@ -38,7 +41,8 @@ class BinaryStream template inline T peek() { - T r = *reinterpret_cast(&data[position]); + T r; + std::memcpy(&r, &data[position], sizeof(T)); return r; } diff --git a/do_lib/darkorbit.cpp b/do_lib/darkorbit.cpp index dc9e906..206d1d0 100755 --- a/do_lib/darkorbit.cpp +++ b/do_lib/darkorbit.cpp @@ -120,6 +120,9 @@ void Darkorbit::notify_jit(avm::MethodInfo *method) void Darkorbit::notify_freechunk(uintptr_t chunk) { + // Clear parsed-traits cache because VM memory regions may be freed/recycled + avm::clear_traits_cache(); + for (auto &[id, hook] : m_hooks) { if ((reinterpret_cast(hook.method) & ~0xfff) == chunk) @@ -151,7 +154,11 @@ std::unordered_map Darkorbit::get_ships() std::future Darkorbit::call_sync(const std::function &f) { std::scoped_lock lk { m_call_mut }; - return m_async_calls.emplace_back(f).get_future(); // c++ 17 + // push the task into the vector, then return its future in a portable way + m_async_calls.emplace_back(std::packaged_task(f)); + auto &task = m_async_calls.back(); + std::future fut = task.get_future(); + return fut; } void Darkorbit::handle_async_calls(avm::MethodEnv *env, uint32_t argc, uintptr_t *argv) @@ -297,15 +304,15 @@ bool Darkorbit::use_item(const std::string &name, uint8_t type, uint8_t bar) return false; } -bool Darkorbit::send_notification(const std::string &name, std::vector args) +bool Darkorbit::send_notification(const std::string &name, const std::vector &args) { utils::log("[*] Send notification {}\n", name); auto facade = m_screen_manager->get_at(0x100, 0x78, 0x28); // no need to cache these, ref count is not increased - auto *arg_array = - reinterpret_cast(flash_stuff::newarray(facade->vtable->methods[0], args.size(), args.data())); + auto *arg_array = reinterpret_cast( + flash_stuff::newarray(facade->vtable->methods[0], static_cast(args.size()), const_cast(args.data()))); avm::String *notification = create_string("MapAssetNotificationTRY_TO_SELECT_MAPASSET"); facade->call(8, notification, (uintptr_t)arg_array | 1); @@ -367,7 +374,7 @@ std::string Darkorbit::get_method_signature(avm::MethodInfo *mi, bool method_nam bool Darkorbit::install(uintptr_t main_app_address) { - mouse_click(0, 0, 1); // Mouse click to initialize the click param + mouse_click(0, 0, 1); // Mouse click to initialize the click param for flash IPC m_main = memory::read(main_app_address + 0x540); m_screen_manager = m_main->get_at(0x1f8); diff --git a/do_lib/darkorbit.h b/do_lib/darkorbit.h index ee96de1..5dfc7e6 100755 --- a/do_lib/darkorbit.h +++ b/do_lib/darkorbit.h @@ -143,17 +143,17 @@ class Darkorbit : public Singleton return r; } - bool mouse_click(int x, int y, int button); - bool key_click(uint32_t key); + bool mouse_click(int x, int y, int button); + bool lock_entity(uint32_t id); bool refine_ore(uint32_t ore, uint32_t amount); bool use_item(const std::string &name, uint8_t type, uint8_t bar); - bool send_notification(const std::string &name, std::vector args); + bool send_notification(const std::string &name, const std::vector &args); void hook_flash_function(avm::MethodEnv *method, MyInvoke_t handler); diff --git a/do_lib/disassembler.cpp b/do_lib/disassembler.cpp index 4f4a54b..1e837a1 100644 --- a/do_lib/disassembler.cpp +++ b/do_lib/disassembler.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "binary_stream.h" #include "instructions.h" @@ -11,13 +12,18 @@ std::string AbcInstruction::ToString() const { - std::stringstream ss; - ss << ABC::Instructions[static_cast(opcode)].name << " "; + const std::string &name = ABC::Instructions[static_cast(opcode)].name; + std::string out; + out.reserve(32 + operands.size() * 8); + out.append(name); + out.push_back(' '); + char buf[32]; for (auto param : operands) { - ss << std::hex << param << " "; + int n = snprintf(buf, sizeof(buf), "%x ", param); + out.append(buf, (n > 0) ? n : 0); } - return ss.str(); + return out; } Disassembler::Disassembly Disassembler::Disassemble(const uint8_t *data) { diff --git a/do_lib/do_lib_linux.cpp b/do_lib/do_lib_linux.cpp index 8159e19..f546242 100644 --- a/do_lib/do_lib_linux.cpp +++ b/do_lib/do_lib_linux.cpp @@ -25,13 +25,13 @@ void *dlopen(const char *filename, int flags) int __attribute__((constructor)) lib_ctor () { - utils::log("[+] Loaded!\n"); + utils::log("[+] Loading shared library do_lib\n"); return 0; } int __attribute__((destructor)) lib_dtor() { - utils::log("[+] Unloading!\n"); + utils::log("[+] Unloading shared library do_lib\n"); Darkorbit::get().uninstall(); flash_stuff::uninstall(); return 0; diff --git a/do_lib/flash_stuff.cpp b/do_lib/flash_stuff.cpp index 682aaea..03520cb 100755 --- a/do_lib/flash_stuff.cpp +++ b/do_lib/flash_stuff.cpp @@ -165,9 +165,9 @@ bool flash_stuff::install() newarray_f = reinterpret_cast(base + offsets::newarray); newstring_f = reinterpret_cast(base + offsets::newstring); finddef_f = reinterpret_cast(base + offsets::finddef); - mouse_release_f = reinterpret_cast(base + offsets::mouse_release); + mouse_release_f = reinterpret_cast(base + offsets::mouse_release); mouse_press_f = reinterpret_cast(base + offsets::mouse_press); - get_method_signature_f = reinterpret_cast(base + offsets::get_method_sig); + get_method_signature_f = reinterpret_cast(base + offsets::get_method_sig); return true; } diff --git a/do_lib/ipc.cpp b/do_lib/ipc.cpp index 2453593..71afec8 100644 --- a/do_lib/ipc.cpp +++ b/do_lib/ipc.cpp @@ -239,7 +239,7 @@ void Ipc::handle_message() case MessageType::SEND_NOTIFICATION: { auto *msg = reinterpret_cast(m_shared); - std::string name = msg->name; + std::string name(msg->name, strnlen(msg->name, sizeof(msg->name))); if (static_cast(msg->argc) > sizeof(msg->argv) / sizeof(msg->argv[0])) { @@ -251,7 +251,8 @@ void Ipc::handle_message() std::vector args(&msg->argv[0], &msg->argv[msg->argc]); - auto res = Darkorbit::get().call_sync([name, args] () + // move args into the lambda to avoid an extra copy; Darkorbit::send_notification takes a const-ref + auto res = Darkorbit::get().call_sync([name, args = std::move(args)] () { return Darkorbit::get().send_notification(name, args); }); @@ -266,7 +267,7 @@ void Ipc::handle_message() case MessageType::USE_ITEM: { auto *msg = reinterpret_cast(m_shared); - std::string name = msg->name; + std::string name(msg->name, strnlen(msg->name, sizeof(msg->name))); auto res = Darkorbit::get().call_sync([name] () { @@ -332,11 +333,11 @@ void Ipc::handle_message() { auto *sig_result = reinterpret_cast(m_shared); auto *msg = reinterpret_cast(m_shared); + std::string signature(msg->signature, strnlen(msg->signature, sizeof(msg->signature))); - - auto res = Darkorbit::get().call_sync([msg] + auto res = Darkorbit::get().call_sync([msg, signature]() { - return Darkorbit::get().check_method_signature(msg->object, msg->index, msg->method_name, msg->signature); + return Darkorbit::get().check_method_signature(msg->object, msg->index, msg->method_name, signature); }); diff --git a/do_lib/mac_offsets.h b/do_lib/mac_offsets.h deleted file mode 100644 index 4fdf04b..0000000 --- a/do_lib/mac_offsets.h +++ /dev/null @@ -1,8 +0,0 @@ -std::ptrdiff_t getproperty = 0x00155fd0; -std::ptrdiff_t setproperty = 0x001570c0; -std::ptrdiff_t verifyjit = 0x00121860; -std::ptrdiff_t get_traits_binding = 0x0015f100; -std::ptrdiff_t newarray = 0x000bd5d0; -std::ptrdiff_t newstring = 0x00154fb0; -std::ptrdiff_t free_chunk = 0x00a690f0; -std::ptrdiff_t finddef = 0x0013eb60; diff --git a/do_lib/memory_linux.cpp b/do_lib/memory.cpp similarity index 60% rename from do_lib/memory_linux.cpp rename to do_lib/memory.cpp index a13a46d..cd272a7 100644 --- a/do_lib/memory_linux.cpp +++ b/do_lib/memory.cpp @@ -6,8 +6,9 @@ #include #include #include +#include -#include "utils.h" +#include "masked_bmh.h" int memory:: unprotect(uint64_t address) @@ -48,41 +49,58 @@ std::vector memory::get_pages(const std::string &name) return pages; } + uintptr_t memory::query_memory(uint8_t *query, const char *mask, uint32_t alignment, const std::string &area) { - uintptr_t query_size = strlen(mask); - uintptr_t size = 0; + if (!query || !mask) + return 0ULL; + + const uintptr_t query_size = std::strlen(mask); + if (query_size == 0) + return 0ULL; + + const uintptr_t query_addr = reinterpret_cast(query); - for (auto ®ion : get_pages(area)) + static thread_local std::vector buffer; // reused across threads to avoid repeated allocations + + for (const auto ®ion : get_pages(area)) { - size = region.end - region.start; + const uintptr_t region_size = region.end - region.start; - if (query_size > size - || (uintptr_t(query) > region.start && uintptr_t(query) < region.end) - || region.read == '-' - || region.name == "[vvar]" - ) + if (query_size > region_size + || (query_addr > region.start && query_addr < region.end) + || region.read == '-' + || region.name == "[vvar]") { continue; } - for (uintptr_t i = region.start; i < region.end-query_size; i++) + try { - bool found = true; - for (uintptr_t j = 0; j < query_size; j++) - { - if (*reinterpret_cast(i + j) != query[j] && mask[j] != '?') - { - found = false; - break; - } - } + buffer.resize(region_size); + std::memcpy(buffer.data(), reinterpret_cast(region.start), region_size); + size_t offset = 0; - if (found) + while (true) { - return i; + const size_t found = masked_bmh_search( + buffer.data(), + region_size, + reinterpret_cast(query), + mask, + query_size, + offset, + alignment); + + if (found == SIZE_MAX) break; + return region.start + found; } } + catch (const std::bad_alloc &) + { + // Skip regions that are too large + continue; + } } return 0ULL; diff --git a/do_lib/memory.h b/do_lib/memory.h index 5e681ef..44e14ca 100755 --- a/do_lib/memory.h +++ b/do_lib/memory.h @@ -1,6 +1,7 @@ #ifndef MEMORY_H #define MEMORY_H #include +#include #include #include @@ -43,27 +44,33 @@ namespace memory std::vector get_pages(const std::string &name = ""); template - constexpr T read(uintptr_t addr) + inline T read(uintptr_t addr) { - return *reinterpret_cast(addr); + T v; + std::memcpy(&v, reinterpret_cast(addr), sizeof(T)); + return v; } template - constexpr T read(uintptr_t address, uintptr_t ofs, Offsets ... offsets) + inline T read(uintptr_t address, uintptr_t ofs, Offsets ... offsets) { - return read(*reinterpret_cast(address) + ofs, offsets...); + uintptr_t next = 0; + std::memcpy(&next, reinterpret_cast(address), sizeof(next)); + return read(next + ofs, offsets...); } template - constexpr void write(uintptr_t address, T value) + inline void write(uintptr_t address, T value) { - *reinterpret_cast(address) = value; + std::memcpy(reinterpret_cast(address), &value, sizeof(T)); } template - constexpr T write(uintptr_t address, T value, uintptr_t ofs, Offsets ... offsets) + inline T write(uintptr_t address, T value, uintptr_t ofs, Offsets ... offsets) { - return write(*reinterpret_cast(address) + ofs, value, offsets...); + uintptr_t next = 0; + std::memcpy(&next, reinterpret_cast(address), sizeof(next)); + return write(next + ofs, value, offsets...); } }; diff --git a/do_lib/memory_win.cpp b/do_lib/memory_win.cpp deleted file mode 100755 index def975f..0000000 --- a/do_lib/memory_win.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "memory.h" -#include - -#include -#include -#include - - -static uintptr_t module_base = 0; -static uintptr_t module_size = 0; - -void memory::set_module_info(uintptr_t b, uintptr_t s) -{ - module_base = b; - module_size = s; -} - -int memory::make_writable(uint64_t address) -{ - return 0; -} - -uintptr_t pattern_match(uint8_t *query, uintptr_t query_size, const char *mask, uint8_t *data, uintptr_t data_size) -{ - for (uintptr_t i = 0; i < data_size - query_size; i++) - { - bool found = true; - for (int j = 0; j < query_size; j++) - { - if (data[i + j] != query[j] && mask[j] != '?') - { - found = false; - break; - } - } - if (found) - return reinterpret_cast(data) + i; - } - - return 0ULL; -} - -uintptr_t memory::query_memory(uint8_t *query, const char *mask, const std::string &area) -{ - uintptr_t begin = 0; - uintptr_t size = -1; - - MEMORY_BASIC_INFORMATION mbi { 0 }; - auto pattern_size = static_cast(strlen(mask)); - - if (!area.empty()) - { - HMODULE module = GetModuleHandleA(area.c_str()); - MODULEINFO modinfo { 0 }; - - if (module == 0) - { - printf("[!] Module %s not found\n", area.c_str()); - return 0; - } - - GetModuleInformation(GetCurrentProcess(), module, &modinfo, sizeof(MODULEINFO)); - - begin = reinterpret_cast(modinfo.lpBaseOfDll); - size = modinfo.SizeOfImage; - } - - - MEMORY_BASIC_INFORMATION this_mbi; - VirtualQuery(reinterpret_cast(&query_memory), &this_mbi, sizeof(this_mbi)); - - for (uintptr_t address = begin; address < begin+size; address += mbi.RegionSize) - { - if (!VirtualQuery(reinterpret_cast(address), &mbi, sizeof(mbi))) - continue; - if (pattern_size > mbi.RegionSize || (mbi.Protect & PAGE_NOACCESS) || mbi.State != MEM_COMMIT || (mbi.Protect & PAGE_GUARD) || (mbi.State & PAGE_READONLY)) - continue; - - auto r = pattern_match(query, pattern_size, mask, reinterpret_cast(address), mbi.RegionSize); - if (r && r != reinterpret_cast(query)) - return r; - } - - return 0ULL; -} - -uintptr_t memory::find_pattern(const std::string &query, const std::string &segment) -{ - std::stringstream ss(query); - std::string data{ }; - std::string mask{ }; - std::vector bytes; - - while (std::getline(ss, data, ' ')) { - if (data.find('?') != std::string::npos) { - mask += "?"; - bytes.push_back(0); - } else { - bytes.push_back(static_cast(std::stoi(data, nullptr, 16))); - mask += "x"; - } - } - - return query_memory(&bytes.at(0), mask.c_str(), segment); -} - -std::vector memory::getpages(const std::string &name) -{ - return {}; -} diff --git a/do_lib/utils.h b/do_lib/utils.h deleted file mode 100644 index b2cebba..0000000 --- a/do_lib/utils.h +++ /dev/null @@ -1,262 +0,0 @@ -#ifndef UTILS_H -#define UTILS_H -#include - -#include -#include -#include - -#define LOG_F(msg, ...) utils::log("[%s] " msg, __func__, __VA_ARGS__); - -#define LOG_FILE "/tmp/do_output.txt" - -#define DEBUG 1 - -namespace utils -{ - static inline void log(std::ofstream &of, const char *data) - { - of << data; - } - - static inline void log(const char *data) - { -#ifdef DEBUG - std::ofstream fhandle { LOG_FILE, std::ios::app}; - log(fhandle, data); -#endif - } - - template - static void log(std::ofstream &of, const char *s, T value, Args ... args) - { - const char *start = s; - for (; *s != 0; s++) - { - if (*s == '{' && (s == start || *(s-1) != '\\')) - { - char key = '\x00'; - for (s++; *s != 0; s++) - { - if (*s == ' ') - { - continue; - } - else if (*s == '}' && *(s-1) != '\\') - { - if (key == 'x') - of << std::hex; - else - of << std::dec; - of << value; - log(of, s+1, args...); - return; - } - else if(key) - { - key = '\x00'; - break; - } - else - key = *s; - } - } - of << *s; - } - } - - template - static inline void log(const char *s, T value, Args ... args) - { -#ifdef DEBUG - std::ofstream fhandle { LOG_FILE, std::ios::app}; - log(fhandle, s, value, args...); -#endif - } - - template - static inline void log(const std::string &s, T value, Args ... args) - { - log(s.c_str(), value, args...); - } - - -class vec2 -{ -public: - vec2() : x(0), y(0) { } - vec2(int n) : x(n), y(n) { } - vec2(int x, int y) : x(x), y(y) { } - vec2(float n) : x(n), y(n) { } - vec2(float x, float y) : x(x), y(y) { } - vec2(double x, double y) : x(x), y(y) { } - vec2(const vec2 &p) : x(p.x), y(p.y) { } - - float distance(float x, float y) const - { - return sqrt(pow(this->x - x, 2) + pow(this->y - y, 2)); - } - - float distance(int x, int y) const - { - return distance(static_cast(x), static_cast(y)); - } - - float distance(const vec2 &other) const - { - return distance(other.x, other.y); - } - - vec2 MapTo(float mx, float my) const - { - return vec2(x * mx, y * my); - } - - vec2 operator+=(const vec2 &rhs) - { - x += rhs.x; - y += rhs.y; - return *this; - } - vec2 operator+=(const float &rhs) - { - x += rhs; - y += rhs; - return *this; - } - vec2 operator+=(const int &rhs) { return *this += static_cast(rhs); } - - vec2 operator-=(const vec2 &rhs) - { - x -= rhs.x; - y -= rhs.y; - return *this; - } - vec2 operator-=(const float &rhs) - { - x -= rhs; - y -= rhs; - return *this; - } - vec2 operator-=(const int &rhs) { return *this -= static_cast(rhs); } - - vec2 operator*=(const vec2 &rhs) - { - x *= rhs.x; - y *= rhs.y; - return *this; - } - vec2 operator*=(const float &rhs) - { - x *= rhs; - y *= rhs; - return *this; - } - vec2 operator*=(const int &rhs) { return *this *= static_cast(rhs); } - - vec2 operator/=(const vec2 &rhs) - { - x /= rhs.x; - y /= rhs.y; - return *this; - } - vec2 operator/=(const float &rhs) - { - x /= rhs; - y /= rhs; - return *this; - } - vec2 operator/=(const int &rhs) { return *this /= static_cast(rhs); } - - // from cpprefeerence - // friends defined inside class body are inline and are hidden from non-ADL lookup - // passing lhs by value helps optimize chained a+b+c - // otherwise, both parameters may be const references - friend vec2 operator+(vec2 lhs, const vec2 &rhs) - { - lhs += rhs; - return lhs; - } - - friend vec2 operator+(vec2 lhs, const float &rhs) - { - lhs += rhs; - return lhs; - } - - friend vec2 operator+(vec2 lhs, const int &rhs) { return lhs + static_cast(rhs); } - - friend vec2 operator-(vec2 lhs, const vec2 &rhs) - { - lhs -= rhs; - return lhs; - } - - friend vec2 operator-(vec2 lhs, const float &rhs) - { - lhs -= rhs; - return lhs; - } - - friend vec2 operator-(vec2 lhs, const int &rhs) { return lhs - static_cast(rhs); } - - friend vec2 operator*(vec2 lhs, const vec2 &rhs) - { - lhs *= rhs; - return lhs; - } - - friend vec2 operator*(vec2 lhs, const float &rhs) - { - lhs *= rhs; - return lhs; - } - - friend vec2 operator*(vec2 lhs, const int &rhs) { return lhs * static_cast(rhs); } - - friend vec2 operator/(vec2 lhs, const vec2 &rhs) - { - lhs /= rhs; - return lhs; - } - - friend vec2 operator/(vec2 lhs, const float &rhs) - { - lhs /= rhs; - return lhs; - } - - friend vec2 operator/(vec2 lhs, const int &rhs) { return lhs / static_cast(rhs); } - - bool operator==(const vec2 &other) const - { - return(x == other.x && y == other.y); - } - - bool operator!=(const vec2 &other) - { - return !(*this == other); - } - - vec2 &operator=(const vec2 &other) - { - if (this != &other) { - x = other.x; - y = other.y; - } - return *this; - } - - friend std::ostream& operator<<(std::ostream& os, const utils::vec2& vec) - { - os << std::fixed << "vec2(" << vec.x << ", " << vec.y << ") "; - return os; - } - - - float x, y; -}; - -}; - -#endif /* UTILS_H */ diff --git a/tools/masked_bmh.h b/tools/masked_bmh.h new file mode 100644 index 0000000..f0f981d --- /dev/null +++ b/tools/masked_bmh.h @@ -0,0 +1,70 @@ +#ifndef MASKED_BMH_H +#define MASKED_BMH_H + +#include +#include +#include + +// Global masked BMH search helper (header-only) +static inline size_t masked_bmh_search(const uint8_t *haystack, size_t hay_len, + const uint8_t *needle, const char *mask, size_t nlen, + size_t start_offset = 0, size_t alignment = 1) +{ + if (nlen == 0 || hay_len < nlen) return SIZE_MAX; + + // find right-most non-wildcard position (anchor) + ssize_t anchor = -1; + for (ssize_t i = static_cast(nlen) - 1; i >= 0; --i) { + if (mask[i] != '?') { anchor = i; break; } + } + + // if pattern is all-wildcards, any aligned position is a match + if (anchor < 0) { + size_t i = start_offset; + while (i + nlen <= hay_len) { + if (alignment == 1 || (i % alignment) == 0) return i; + ++i; + } + return SIZE_MAX; + } + + std::array shift; + // default shift: move past the anchor + shift.fill(nlen - static_cast(anchor)); + for (size_t i = 0; i < static_cast(anchor); ++i) { + if (mask[i] != '?') { + shift[needle[i]] = static_cast(anchor) - i; + } + } + + size_t i = start_offset; + while (i + nlen <= hay_len) { + if (alignment > 1 && (i % alignment) != 0) { i += (alignment - (i % alignment)); continue; } + + // quick check at the anchor position + if (haystack[i + anchor] != needle[anchor]) { + uint8_t next = haystack[i + anchor]; + size_t s = shift[next]; + if (s == 0) s = 1; + i += s; + continue; + } + + // verify remaining non-wild bytes + bool ok = true; + for (size_t k = 0; k < nlen; ++k) { + if (mask[k] == '?') continue; + if (haystack[i + k] != needle[k]) { ok = false; break; } + } + if (ok) return i; + + // advance by shift based on the anchor byte + uint8_t next = haystack[i + anchor]; + size_t s = shift[next]; + if (s == 0) s = 1; + i += s; + } + return SIZE_MAX; +} + +#endif // MASKED_BMH_H diff --git a/tools/utils.h b/tools/utils.h new file mode 100644 index 0000000..eeb0e5c --- /dev/null +++ b/tools/utils.h @@ -0,0 +1,329 @@ +#ifndef TOOLS_UTILS_H +#define TOOLS_UTILS_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace utils +{ + inline const std::string &log_timestamp_str() + { + static const std::string ts = []() -> std::string { + if (const char *env = getenv("TANOS_START_TIME")) + return env; + + std::time_t t = std::time(nullptr); + std::tm tm{}; + localtime_r(&t, &tm); + std::stringstream ss; + ss << std::put_time(&tm, "%Y-%m-%d_%H-%M-%S"); + const std::string s = ss.str(); + setenv("TANOS_START_TIME", s.c_str(), 0); + return s; + }(); + return ts; + } + + inline const std::string &get_log_file_path() + { + static const std::string path = []() + { + const std::string &ts = log_timestamp_str(); + if (ts.empty()) + return std::string{}; + + std::string filename = ts + "_TanosApi.log"; + fs::path log_dir = "logs"; + std::error_code ec; + fs::create_directories(log_dir, ec); + if (ec) + return filename; + + return (log_dir / filename).string(); + }(); + + return path; + } + + static inline void format(std::stringstream &of, const char *data) + { + of << data; + } + + static inline std::string format(const char *data) + { + std::stringstream ss; + format(ss, data); + return ss.str(); + } + + template + static void format(std::stringstream &of, const char *s, T value, Args... args) + { + const char *start = s; + for (; *s != 0; s++) + { + if (*s == '{' && (s == start || *(s - 1) != '\\')) + { + char key = '\x00'; + for (s++; *s != 0; s++) + { + if (*s == ' ') + continue; + else if (*s == '}' && *(s - 1) != '\\') + { + if (key == 'x') + of << std::hex; + else + of << std::dec; + of << value; + format(of, s + 1, args...); + return; + } + else if (key) + { + key = '\x00'; + break; + } + else + key = *s; + } + } + of << *s; + } + } + + template + static inline std::string format(const char *s, T value, Args... args) + { + std::stringstream ss; + format(ss, s, value, args...); + return ss.str(); + } + + template + static inline std::string format(const std::string &s, T value, Args... args) + { + return format(s.c_str(), value, args...); + } + + static inline void log(const char *data) + { + const std::string &path = get_log_file_path(); + if (path.empty()) return; + std::time_t t = std::time(nullptr); + std::tm tm{}; + localtime_r(&t, &tm); + std::ofstream fhandle{path, std::ios::app}; + fhandle << std::put_time(&tm, "[%Y-%m-%d %H:%M:%S] ") << data; + } + + template + static inline void log(const char *s, T value, Args... args) + { + std::string formatted = format(s, value, args...); + log(formatted.c_str()); + } + + static inline std::string escape_json(const std::string& s) + { + std::string res = "\""; + for (char c : s) { + if (c == '"') res += "\\\""; + else if (c == '\\') res += "\\\\"; + else if (c == '\n') res += "\\n"; + else if (c == '\r') res += "\\r"; + else if (c == '\t') res += "\\t"; + else if (c == '\b') res += "\\b"; + else if (c == '\f') res += "\\f"; + else res += c; + } + res += "\""; + return res; + } + + class vec2 + { + public: + vec2() : x(0), y(0) { } + vec2(int n) : x(n), y(n) { } + vec2(int x, int y) : x(x), y(y) { } + vec2(float n) : x(n), y(n) { } + vec2(float x, float y) : x(x), y(y) { } + vec2(double x, double y) : x(x), y(y) { } + vec2(const vec2 &p) : x(p.x), y(p.y) { } + + float distance(float x, float y) const + { + return sqrt(pow(this->x - x, 2) + pow(this->y - y, 2)); + } + + float distance(int x, int y) const + { + return distance(static_cast(x), static_cast(y)); + } + + float distance(const vec2 &other) const + { + return distance(other.x, other.y); + } + + vec2 MapTo(float mx, float my) const + { + return vec2(x * mx, y * my); + } + + vec2 operator+=(const vec2 &rhs) + { + x += rhs.x; + y += rhs.y; + return *this; + } + vec2 operator+=(const float &rhs) + { + x += rhs; + y += rhs; + return *this; + } + vec2 operator+=(const int &rhs) { return *this += static_cast(rhs); } + + vec2 operator-=(const vec2 &rhs) + { + x -= rhs.x; + y -= rhs.y; + return *this; + } + vec2 operator-=(const float &rhs) + { + x -= rhs; + y -= rhs; + return *this; + } + vec2 operator-=(const int &rhs) { return *this -= static_cast(rhs); } + + vec2 operator*=(const vec2 &rhs) + { + x *= rhs.x; + y *= rhs.y; + return *this; + } + vec2 operator*=(const float &rhs) + { + x *= rhs; + y *= rhs; + return *this; + } + vec2 operator*=(const int &rhs) { return *this *= static_cast(rhs); } + + vec2 operator/=(const vec2 &rhs) + { + x /= rhs.x; + y /= rhs.y; + return *this; + } + vec2 operator/=(const float &rhs) + { + x /= rhs; + y /= rhs; + return *this; + } + vec2 operator/=(const int &rhs) { return *this /= static_cast(rhs); } + + friend vec2 operator+(vec2 lhs, const vec2 &rhs) + { + lhs += rhs; + return lhs; + } + + friend vec2 operator+(vec2 lhs, const float &rhs) + { + lhs += rhs; + return lhs; + } + + friend vec2 operator+(vec2 lhs, const int &rhs) { return lhs + static_cast(rhs); } + + friend vec2 operator-(vec2 lhs, const vec2 &rhs) + { + lhs -= rhs; + return lhs; + } + + friend vec2 operator-(vec2 lhs, const float &rhs) + { + lhs -= rhs; + return lhs; + } + + friend vec2 operator-(vec2 lhs, const int &rhs) { return lhs - static_cast(rhs); } + + friend vec2 operator*(vec2 lhs, const vec2 &rhs) + { + lhs *= rhs; + return lhs; + } + + friend vec2 operator*(vec2 lhs, const float &rhs) + { + lhs *= rhs; + return lhs; + } + + friend vec2 operator*(vec2 lhs, const int &rhs) { return lhs * static_cast(rhs); } + + friend vec2 operator/(vec2 lhs, const vec2 &rhs) + { + lhs /= rhs; + return lhs; + } + + friend vec2 operator/(vec2 lhs, const float &rhs) + { + lhs /= rhs; + return lhs; + } + + friend vec2 operator/(vec2 lhs, const int &rhs) { return lhs / static_cast(rhs); } + + bool operator==(const vec2 &other) const + { + return(x == other.x && y == other.y); + } + + bool operator!=(const vec2 &other) + { + return !(*this == other); + } + + vec2 &operator=(const vec2 &other) + { + if (this != &other) { + x = other.x; + y = other.y; + } + return *this; + } + + friend std::ostream& operator<<(std::ostream& os, const utils::vec2& vec) + { + os << std::fixed << "vec2(" << vec.x << ", " << vec.y << ") "; + return os; + } + + float x, y; + }; + +}; + +#endif /* TOOLS_UTILS_H */ \ No newline at end of file