This commit is contained in:
Vasyl Palamarchuk
2026-09-04 10:43:46 -07:00
commit 92bc816e0c
720 changed files with 515096 additions and 0 deletions
@@ -0,0 +1,17 @@
# Built-in API Discovery Worksheet
Complete this from **Local Script > Help** in your installed Multi-PROG version.
| Function | Exact signature | Return type | Buffer/file affected | Failure behavior | Tested version |
|---|---|---|---|---|---|
| AddFunctionButton | Verify in Help | Unknown | UI | Unknown | |
| ReadData | Verify in Help | Unknown | Current buffer | Unknown | |
| WriteData | Verify in Help | Unknown | Current buffer | Unknown | |
| GetOpenFileName | Verify in Help | Unknown | File dialog | Unknown | |
| GetSaveFileName | Verify in Help | Unknown | File dialog | Unknown | |
| ReadFile | Verify in Help | Unknown | File | Unknown | |
| WriteFile | Verify in Help | Unknown | File | Unknown | |
| Hex2Dec | Verify in Help | Unknown | None | Unknown | |
| areEqual | Verify in Help | Unknown | None | Unknown | |
Record screenshots and minimal read-only tests. Do not infer signatures from third-party posts.
+65
View File
@@ -0,0 +1,65 @@
# XHorse Multi-PROG Scripting Knowledge Base
## Purpose
This archive combines three deliverables: an expanded knowledge base, a curated link catalog, and a safe script starter kit. It is designed for legitimate module repair, data validation, research on owned hardware, and controlled bench work.
## Safety and legal scope
Use scripts only on modules and vehicles you own or are explicitly authorized to service. Keep immutable originals, record hashes, use current-limited bench power, verify part numbers and memory sizes, and never write a modified image until validation succeeds. This kit intentionally excludes operational immobilizer bypass, unauthorized key creation, mileage fraud, security-access circumvention, and theft-enabling instructions.
## Platform model
Multi-PROG provides Local Scripts for creating, opening, modifying, saving, debugging and publishing scripts, and Released Features for importing and running published scripts. A running script can add buttons to the main toolbar. Public documentation identifies JavaScript as the scripting language and shows .mjs examples.
## Recommended development lifecycle
1. Update Multi-PROG and archive the installed version.
2. Open Local Script and use Help as the API source of truth for that exact version.
3. Start with read-only operations and synthetic test buffers.
4. Assert expected buffer length, erased-state patterns and known signatures.
5. Hash and save the untouched input.
6. Make changes in a copy, never in the original buffer.
7. Produce a byte-level change report.
8. Validate ranges, checksums and invariants.
9. Save to a new filename.
10. Test on a spare bench module before any production use.
## Known interface concepts
The manual and screenshots describe functions and areas including AddFunctionButton, ReadData, WriteData, GetOpenFileName, GetSaveFileName, ReadFile and WriteFile, plus conversion/comparison helpers such as Hex2Dec and areEqual. Exact signatures can vary by software release, so verify each call in built-in Help.
## Automation patterns
- Read-only metadata inspection
- Buffer length and blank-area validation
- VIN or calibration identifier display without modification
- Byte-range extraction for reports
- Before/after diff generation
- Generic CRC test vectors
- File naming and audit logging
- Batch validation of known-good backups
- Controlled export of a modified copy after all checks pass
## Checksum strategy
Treat the green C indicator in the device library/main view as the product's signal that checksum handling is available for that specific EEPROM or Flash entry. Do not assume every listed ECU is supported. Keep checksum verification distinct from generic CRC examples: an ECU checksum may cover multiple regions, use proprietary transforms, or include stored complements.
## Testing checklist
- Correct module, MCU/EEPROM and memory region selected
- Exact file size verified
- Original SHA-256 recorded
- Two independent reads compared
- Power supply and current limit documented
- Patch ranges explicitly allow-listed
- Unchanged areas byte-identical
- Output checksum independently verified where possible
- Output saved under a new name
- Recovery path and known-good backup available
## Trust model for downloads
Official/built-in documentation has highest priority. Dealer blogs and mirrors are secondary. Forums, cloud-drive files and attachments are untrusted. Scan them, open in an isolated VM, compare hashes, and inspect source before import. Never run an opaque locked script on a production module without understanding its effect.
## Compatibility note
Node.js/NPM packages are learning references only. Multi-PROG's embedded JavaScript host may not provide Node APIs, package imports, Buffer, filesystem access beyond host functions, or modern module features. Port only small dependency-free routines and validate against known vectors.
## Archive navigation
- `01_Knowledge_Base`: this guide and API discovery worksheet
- `02_Resource_Catalog`: CSV and Markdown link inventories
- `03_Script_Starter_Kit`: safe, vendor-neutral templates and tests
- `04_Workflows`: repeatable validation and release procedures
- `05_Reference`: glossary, search queries, checklist and security-research notes
- `06_URL_Shortcuts`: browser shortcut files
+50
View File
@@ -0,0 +1,50 @@
Category,Title,URL,Description,Confidence
Official,Multi-PROG Script Function Manual PDF,https://www.xhorsevvdi.com/upload/pro/24040217120425363837.pdf,"Primary scripting manual: local scripts, released features, editor, testing, publishing, function browser and restrictions.",High
Official mirror,Multi-PROG Script Function Manual mirror,https://images.dkgcc.com/files/2025/5/8537_Xhorse%20Multi%20Prog%20User%20Manual.pdf,"Searchable mirror showing sample functions such as ReadData, WriteData, file dialogs, function buttons, AES/DES and SHA256 entries.",Medium
Manual,Multi-PROG Owner Manual,https://www.manualslib.com/manual/3420478/Xhorse-Multi-Prog.html,"Hardware, EEPROM/MCU/ECU/TCU operations, third-party scripting and batch writing.",High
Manual,Multi-PROG Operation Manual,https://www.manualslib.com/guide/4237862/xhorse-multi-prog-automotive-programmer-operation-manual.html,Operation guide with script-use and script-development chapters.,High
Tutorial,First Multi-PROG script tutorial,https://blog.xhorsetool.com/how-to-make-first-script-with-xhorse-multi-prog/,"Walkthrough of importing, testing, correcting and locking a JavaScript-based script; refers to built-in Help.",Medium
Tutorial,Third-party script introduction,https://www.obdexpress.co.uk/service/introduction-to-xhorse-multi-prog-third-party-script-function.html,Overview of processing original data with a third-party script and saving the result.,Medium
Tutorial,Third-party scripting how-to,https://obd2services.wordpress.com/2024/02/02/xhorse-multi-prog-third-party-scripting-how-to/,Community overview of Multi-PROG third-party scripting. Verify details against the manual.,Low
Repository,CarKeyGuyNL Multi-Prog-Scripts,https://github.com/CarKeyGuyNL/Multi-Prog-Scripts,Public .mjs example and installation notes. Review legality and authorization before use.,Medium
Forum,MHH Auto Multi-PROG script thread,https://mhhauto.com/Thread-Xhores-Multi-Prog-script,Community discussion and beta-script references. Treat attachments as untrusted and scan offline.,Low
Checksum,Xhorse checksum workflow guide,https://blog.xhorsetool.com/how-to-calculate-checksum-for-xhorse-multi-prog/,Explains green C support indicator and checksum/verification prompt during supported writes.,Medium
Checksum,Checksum function guide mirror,https://blog.xhorsevvdi.com/how-to-use-checksum-calculation-in-xhorse-multi-prog/,Alternate description of supported checksum workflow and cautions.,Medium
Checksum,Supported ECU checksum identification,https://blog.vvdishop.com/which-ecu-is-supported-for-checksum-in-xhorse-multi-prog/,Shows how supported EEPROM/Flash items are identified in the device library.,Medium
JavaScript,binary-parser GitHub,https://github.com/keichi/binary-parser,"Declarative JavaScript parser for integers, bit fields, strings, arrays and Uint8Array. External modules may not run inside Multi-PROG without adaptation.",High
JavaScript,Awesome Binary Parsing,https://github.com/dloss/binary-parsing,Curated cross-language list of binary parsing tools and learning resources.,High
JavaScript,jBinary,https://jdataview.github.io/jBinary/,High-level JavaScript binary read/write library and examples. Use as a learning reference unless runtime compatibility is proven.,Medium
JavaScript,js-crc,https://www.npmjs.com/package/js-crc,CRC-8/16/24/32/64 and custom-model reference. Multi-PROG may require a dependency-free port.,High
Automotive research,GitHub ECU topic,https://github.com/topics/ecu,Discovery page for open automotive diagnostics and ECU projects. Carefully evaluate licenses and permitted use.,Medium
Automotive research,EcuBus-Pro,https://github.com/ecubus/EcuBus-Pro,"Open automotive ECU development and test tool with TypeScript-like scripting; useful architecture reference, not a Multi-PROG library.",Medium
Binary analysis,OpenRemap,https://pypi.org/project/openremap/,"Offline ECU binary identification, health-checking and checksum verification project; use only on authorized files.",Medium
Professional security,NASTF VSP Registry,https://wp.nastf.org/?page_id=367,"Credentialing, application resources, secure-tool validation and VSP operational guidance for security-related automotive work.",High
Professional security,NASTF VSP definition,https://support.nastf.org/support/solutions/articles/43000755446-what-is-a-nastf-vehicle-security-professional-,"Explains qualification and business requirements for professionals performing key programming, code ordering or immobilizer resets.",High
Professional security,NASTF Memberships,https://wp.nastf.org/?page_id=3969,"Describes membership levels, VSP capabilities, customer authorization forms and access to key/immobilizer codes.",High
Professional security,NASTF SDRM portal,https://sdrm.nastfsecurityregistry.org/,"Secure Data Release Model account, login and VSP application portal for the US and Canada.",High
Reference,CyberChef,https://gchq.github.io/CyberChef/,Browser-based data transformation and analysis workbench. Avoid entering customer secrets into unapproved hosted tools; prefer an approved offline instance.,High
JavaScript,GitHub checksum topic,https://github.com/topics/checksum,Discovery catalog for checksum implementations. Validate licenses and test vectors before reuse.,Medium
JavaScript,CRC32 portable example,https://gist.github.com/diachedelic/582ac8fab84b7f2c34ff358ac5cc7cfa,Compact Uint8Array CRC-32 reference for JavaScript runtimes. Review license and verify against standard vectors.,Medium
JavaScript,Checksum algorithm examples,https://gist.github.com/bryc/8a0885a4be58b6bbf0ec54c7758c0841,"Educational implementations of Fletcher, Adler and related checksums. Independently verify before use.",Low
JavaScript,restructure,https://github.com/foliojs/restructure,"Declarative binary data structure parser/encoder for JS, alternative to binary-parser.",High
JavaScript,buffer-layout,https://github.com/pabigot/buffer-layout,"Structured binary data layout library (C-like structs) for Buffer/Uint8Array.",High
Hashing,js-sha256,https://github.com/emn178/js-sha256,Dependency-free SHA-256/224 implementation for JavaScript.,High
Hashing,js-sha1,https://github.com/emn178/js-sha1,Dependency-free SHA-1 implementation for JavaScript.,High
Hashing,blueimp JavaScript-MD5,https://github.com/blueimp/JavaScript-MD5,Dependency-free MD5 implementation for JavaScript.,High
Hashing,crypto-js,https://github.com/brix/crypto-js,"AES/DES/SHA/MD5 suite; evaluate bundle size and Multi-PROG runtime compatibility before embedding.",Medium
Encoding,MDN TextEncoder,https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder,Reference for UTF-8 encode/decode behavior in JavaScript runtimes.,High
Encoding,MDN DataView,https://developer.mozilla.org/en-US/docs/Web/API/DataView,Reference for typed binary read/write access to ArrayBuffers.,High
Encoding,MDN Uint8Array,https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array,Reference for the typed array used throughout Multi-PROG-style buffer handling.,High
Checksum,Wikipedia CRC,https://en.wikipedia.org/wiki/Cyclic_redundancy_check,Background on cyclic redundancy check theory and common polynomials.,High
Checksum,Wikipedia Fletcher checksum,https://en.wikipedia.org/wiki/Fletcher%27s_checksum,Background on the Fletcher-16/32 checksum algorithms.,High
Checksum,Wikipedia Adler-32,https://en.wikipedia.org/wiki/Adler-32,Background on the Adler-32 checksum algorithm.,High
Checksum,CRC RevEng catalogue,https://reveng.sourceforge.io/crc-catalogue/all.htm,Canonical CRC parameter and check-value catalogue for verifying implementations against known vectors.,High
Cryptography reference,NIST FIPS 180-4 (SHA),https://csrc.nist.gov/pubs/fips/180-4/upd1/final,Official Secure Hash Standard specification (SHA-1/SHA-2 family).,High
Cryptography reference,NIST FIPS 197 (AES),https://csrc.nist.gov/pubs/fips/197/final,Official Advanced Encryption Standard specification.,High
Cryptography reference,OWASP Cryptographic Storage Cheat Sheet,https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html,General guidance on safe use of cryptographic primitives and key handling.,High
JavaScript,pako,https://github.com/nodeca/pako,"zlib/deflate port for JS; occasionally relevant for compressed dump handling.",Medium
Reference,Node.js Buffer docs,https://nodejs.org/api/buffer.html,Reference for Buffer APIs when prototyping outside the Multi-PROG host runtime.,Medium
Professional security,NASTF Assisted Immobilizer Reprogramming,https://support.nastf.org/support/solutions/articles/43000755445-what-is-air-assisted-immobilizer-reprogramming-,High-level overview of AIR and its role in authorized immobilizer-related workflows.,High
Professional security,NASTF account types comparison,https://support.nastf.org/support/solutions/articles/43000755695-nastf-account-types-comparison,Describes different NASTF account types and how they map to professional access needs.,High
Professional security,Choosing the correct NASTF account,https://support.nastf.org/support/solutions/articles/43000761328-choosing-the-correct-nastf-account,Helps determine which NASTF account is appropriate for a given secure-service workflow.,High
Reference,NIST Cryptographic Standards and Guidelines,https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines,Official NIST cryptography guidance and standards for understanding general cryptographic principles.,High
1 Category Title URL Description Confidence
2 Official Multi-PROG Script Function Manual PDF https://www.xhorsevvdi.com/upload/pro/24040217120425363837.pdf Primary scripting manual: local scripts, released features, editor, testing, publishing, function browser and restrictions. High
3 Official mirror Multi-PROG Script Function Manual mirror https://images.dkgcc.com/files/2025/5/8537_Xhorse%20Multi%20Prog%20User%20Manual.pdf Searchable mirror showing sample functions such as ReadData, WriteData, file dialogs, function buttons, AES/DES and SHA256 entries. Medium
4 Manual Multi-PROG Owner Manual https://www.manualslib.com/manual/3420478/Xhorse-Multi-Prog.html Hardware, EEPROM/MCU/ECU/TCU operations, third-party scripting and batch writing. High
5 Manual Multi-PROG Operation Manual https://www.manualslib.com/guide/4237862/xhorse-multi-prog-automotive-programmer-operation-manual.html Operation guide with script-use and script-development chapters. High
6 Tutorial First Multi-PROG script tutorial https://blog.xhorsetool.com/how-to-make-first-script-with-xhorse-multi-prog/ Walkthrough of importing, testing, correcting and locking a JavaScript-based script; refers to built-in Help. Medium
7 Tutorial Third-party script introduction https://www.obdexpress.co.uk/service/introduction-to-xhorse-multi-prog-third-party-script-function.html Overview of processing original data with a third-party script and saving the result. Medium
8 Tutorial Third-party scripting how-to https://obd2services.wordpress.com/2024/02/02/xhorse-multi-prog-third-party-scripting-how-to/ Community overview of Multi-PROG third-party scripting. Verify details against the manual. Low
9 Repository CarKeyGuyNL Multi-Prog-Scripts https://github.com/CarKeyGuyNL/Multi-Prog-Scripts Public .mjs example and installation notes. Review legality and authorization before use. Medium
10 Forum MHH Auto Multi-PROG script thread https://mhhauto.com/Thread-Xhores-Multi-Prog-script Community discussion and beta-script references. Treat attachments as untrusted and scan offline. Low
11 Checksum Xhorse checksum workflow guide https://blog.xhorsetool.com/how-to-calculate-checksum-for-xhorse-multi-prog/ Explains green C support indicator and checksum/verification prompt during supported writes. Medium
12 Checksum Checksum function guide mirror https://blog.xhorsevvdi.com/how-to-use-checksum-calculation-in-xhorse-multi-prog/ Alternate description of supported checksum workflow and cautions. Medium
13 Checksum Supported ECU checksum identification https://blog.vvdishop.com/which-ecu-is-supported-for-checksum-in-xhorse-multi-prog/ Shows how supported EEPROM/Flash items are identified in the device library. Medium
14 JavaScript binary-parser GitHub https://github.com/keichi/binary-parser Declarative JavaScript parser for integers, bit fields, strings, arrays and Uint8Array. External modules may not run inside Multi-PROG without adaptation. High
15 JavaScript Awesome Binary Parsing https://github.com/dloss/binary-parsing Curated cross-language list of binary parsing tools and learning resources. High
16 JavaScript jBinary https://jdataview.github.io/jBinary/ High-level JavaScript binary read/write library and examples. Use as a learning reference unless runtime compatibility is proven. Medium
17 JavaScript js-crc https://www.npmjs.com/package/js-crc CRC-8/16/24/32/64 and custom-model reference. Multi-PROG may require a dependency-free port. High
18 Automotive research GitHub ECU topic https://github.com/topics/ecu Discovery page for open automotive diagnostics and ECU projects. Carefully evaluate licenses and permitted use. Medium
19 Automotive research EcuBus-Pro https://github.com/ecubus/EcuBus-Pro Open automotive ECU development and test tool with TypeScript-like scripting; useful architecture reference, not a Multi-PROG library. Medium
20 Binary analysis OpenRemap https://pypi.org/project/openremap/ Offline ECU binary identification, health-checking and checksum verification project; use only on authorized files. Medium
21 Professional security NASTF VSP Registry https://wp.nastf.org/?page_id=367 Credentialing, application resources, secure-tool validation and VSP operational guidance for security-related automotive work. High
22 Professional security NASTF VSP definition https://support.nastf.org/support/solutions/articles/43000755446-what-is-a-nastf-vehicle-security-professional- Explains qualification and business requirements for professionals performing key programming, code ordering or immobilizer resets. High
23 Professional security NASTF Memberships https://wp.nastf.org/?page_id=3969 Describes membership levels, VSP capabilities, customer authorization forms and access to key/immobilizer codes. High
24 Professional security NASTF SDRM portal https://sdrm.nastfsecurityregistry.org/ Secure Data Release Model account, login and VSP application portal for the US and Canada. High
25 Reference CyberChef https://gchq.github.io/CyberChef/ Browser-based data transformation and analysis workbench. Avoid entering customer secrets into unapproved hosted tools; prefer an approved offline instance. High
26 JavaScript GitHub checksum topic https://github.com/topics/checksum Discovery catalog for checksum implementations. Validate licenses and test vectors before reuse. Medium
27 JavaScript CRC32 portable example https://gist.github.com/diachedelic/582ac8fab84b7f2c34ff358ac5cc7cfa Compact Uint8Array CRC-32 reference for JavaScript runtimes. Review license and verify against standard vectors. Medium
28 JavaScript Checksum algorithm examples https://gist.github.com/bryc/8a0885a4be58b6bbf0ec54c7758c0841 Educational implementations of Fletcher, Adler and related checksums. Independently verify before use. Low
29 JavaScript restructure https://github.com/foliojs/restructure Declarative binary data structure parser/encoder for JS, alternative to binary-parser. High
30 JavaScript buffer-layout https://github.com/pabigot/buffer-layout Structured binary data layout library (C-like structs) for Buffer/Uint8Array. High
31 Hashing js-sha256 https://github.com/emn178/js-sha256 Dependency-free SHA-256/224 implementation for JavaScript. High
32 Hashing js-sha1 https://github.com/emn178/js-sha1 Dependency-free SHA-1 implementation for JavaScript. High
33 Hashing blueimp JavaScript-MD5 https://github.com/blueimp/JavaScript-MD5 Dependency-free MD5 implementation for JavaScript. High
34 Hashing crypto-js https://github.com/brix/crypto-js AES/DES/SHA/MD5 suite; evaluate bundle size and Multi-PROG runtime compatibility before embedding. Medium
35 Encoding MDN TextEncoder https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder Reference for UTF-8 encode/decode behavior in JavaScript runtimes. High
36 Encoding MDN DataView https://developer.mozilla.org/en-US/docs/Web/API/DataView Reference for typed binary read/write access to ArrayBuffers. High
37 Encoding MDN Uint8Array https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array Reference for the typed array used throughout Multi-PROG-style buffer handling. High
38 Checksum Wikipedia CRC https://en.wikipedia.org/wiki/Cyclic_redundancy_check Background on cyclic redundancy check theory and common polynomials. High
39 Checksum Wikipedia Fletcher checksum https://en.wikipedia.org/wiki/Fletcher%27s_checksum Background on the Fletcher-16/32 checksum algorithms. High
40 Checksum Wikipedia Adler-32 https://en.wikipedia.org/wiki/Adler-32 Background on the Adler-32 checksum algorithm. High
41 Checksum CRC RevEng catalogue https://reveng.sourceforge.io/crc-catalogue/all.htm Canonical CRC parameter and check-value catalogue for verifying implementations against known vectors. High
42 Cryptography reference NIST FIPS 180-4 (SHA) https://csrc.nist.gov/pubs/fips/180-4/upd1/final Official Secure Hash Standard specification (SHA-1/SHA-2 family). High
43 Cryptography reference NIST FIPS 197 (AES) https://csrc.nist.gov/pubs/fips/197/final Official Advanced Encryption Standard specification. High
44 Cryptography reference OWASP Cryptographic Storage Cheat Sheet https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html General guidance on safe use of cryptographic primitives and key handling. High
45 JavaScript pako https://github.com/nodeca/pako zlib/deflate port for JS; occasionally relevant for compressed dump handling. Medium
46 Reference Node.js Buffer docs https://nodejs.org/api/buffer.html Reference for Buffer APIs when prototyping outside the Multi-PROG host runtime. Medium
47 Professional security NASTF Assisted Immobilizer Reprogramming https://support.nastf.org/support/solutions/articles/43000755445-what-is-air-assisted-immobilizer-reprogramming- High-level overview of AIR and its role in authorized immobilizer-related workflows. High
48 Professional security NASTF account types comparison https://support.nastf.org/support/solutions/articles/43000755695-nastf-account-types-comparison Describes different NASTF account types and how they map to professional access needs. High
49 Professional security Choosing the correct NASTF account https://support.nastf.org/support/solutions/articles/43000761328-choosing-the-correct-nastf-account Helps determine which NASTF account is appropriate for a given secure-service workflow. High
50 Reference NIST Cryptographic Standards and Guidelines https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines Official NIST cryptography guidance and standards for understanding general cryptographic principles. High
+295
View File
@@ -0,0 +1,295 @@
# Expanded Resource Catalog
## Multi-PROG Script Function Manual PDF
- Category: Official
- URL: https://www.xhorsevvdi.com/upload/pro/24040217120425363837.pdf
- Confidence: High
- Description: Primary scripting manual: local scripts, released features, editor, testing, publishing, function browser and restrictions.
## Multi-PROG Script Function Manual mirror
- Category: Official mirror
- URL: https://images.dkgcc.com/files/2025/5/8537_Xhorse%20Multi%20Prog%20User%20Manual.pdf
- Confidence: Medium
- Description: Searchable mirror showing sample functions such as ReadData, WriteData, file dialogs, function buttons, AES/DES and SHA256 entries.
## Multi-PROG Owner Manual
- Category: Manual
- URL: https://www.manualslib.com/manual/3420478/Xhorse-Multi-Prog.html
- Confidence: High
- Description: Hardware, EEPROM/MCU/ECU/TCU operations, third-party scripting and batch writing.
## Multi-PROG Operation Manual
- Category: Manual
- URL: https://www.manualslib.com/guide/4237862/xhorse-multi-prog-automotive-programmer-operation-manual.html
- Confidence: High
- Description: Operation guide with script-use and script-development chapters.
## First Multi-PROG script tutorial
- Category: Tutorial
- URL: https://blog.xhorsetool.com/how-to-make-first-script-with-xhorse-multi-prog/
- Confidence: Medium
- Description: Walkthrough of importing, testing, correcting and locking a JavaScript-based script; refers to built-in Help.
## Third-party script introduction
- Category: Tutorial
- URL: https://www.obdexpress.co.uk/service/introduction-to-xhorse-multi-prog-third-party-script-function.html
- Confidence: Medium
- Description: Overview of processing original data with a third-party script and saving the result.
## Third-party scripting how-to
- Category: Tutorial
- URL: https://obd2services.wordpress.com/2024/02/02/xhorse-multi-prog-third-party-scripting-how-to/
- Confidence: Low
- Description: Community overview of Multi-PROG third-party scripting. Verify details against the manual.
## CarKeyGuyNL Multi-Prog-Scripts
- Category: Repository
- URL: https://github.com/CarKeyGuyNL/Multi-Prog-Scripts
- Confidence: Medium
- Description: Public .mjs example and installation notes. Review legality and authorization before use.
## MHH Auto Multi-PROG script thread
- Category: Forum
- URL: https://mhhauto.com/Thread-Xhores-Multi-Prog-script
- Confidence: Low
- Description: Community discussion and beta-script references. Treat attachments as untrusted and scan offline.
## Xhorse checksum workflow guide
- Category: Checksum
- URL: https://blog.xhorsetool.com/how-to-calculate-checksum-for-xhorse-multi-prog/
- Confidence: Medium
- Description: Explains green C support indicator and checksum/verification prompt during supported writes.
## Checksum function guide mirror
- Category: Checksum
- URL: https://blog.xhorsevvdi.com/how-to-use-checksum-calculation-in-xhorse-multi-prog/
- Confidence: Medium
- Description: Alternate description of supported checksum workflow and cautions.
## Supported ECU checksum identification
- Category: Checksum
- URL: https://blog.vvdishop.com/which-ecu-is-supported-for-checksum-in-xhorse-multi-prog/
- Confidence: Medium
- Description: Shows how supported EEPROM/Flash items are identified in the device library.
## binary-parser GitHub
- Category: JavaScript
- URL: https://github.com/keichi/binary-parser
- Confidence: High
- Description: Declarative JavaScript parser for integers, bit fields, strings, arrays and Uint8Array. External modules may not run inside Multi-PROG without adaptation.
## Awesome Binary Parsing
- Category: JavaScript
- URL: https://github.com/dloss/binary-parsing
- Confidence: High
- Description: Curated cross-language list of binary parsing tools and learning resources.
## jBinary
- Category: JavaScript
- URL: https://jdataview.github.io/jBinary/
- Confidence: Medium
- Description: High-level JavaScript binary read/write library and examples. Use as a learning reference unless runtime compatibility is proven.
## js-crc
- Category: JavaScript
- URL: https://www.npmjs.com/package/js-crc
- Confidence: High
- Description: CRC-8/16/24/32/64 and custom-model reference. Multi-PROG may require a dependency-free port.
## GitHub ECU topic
- Category: Automotive research
- URL: https://github.com/topics/ecu
- Confidence: Medium
- Description: Discovery page for open automotive diagnostics and ECU projects. Carefully evaluate licenses and permitted use.
## EcuBus-Pro
- Category: Automotive research
- URL: https://github.com/ecubus/EcuBus-Pro
- Confidence: Medium
- Description: Open automotive ECU development and test tool with TypeScript-like scripting; useful architecture reference, not a Multi-PROG library.
## OpenRemap
- Category: Binary analysis
- URL: https://pypi.org/project/openremap/
- Confidence: Medium
- Description: Offline ECU binary identification, health-checking and checksum verification project; use only on authorized files.
## NASTF VSP Registry
- Category: Professional security
- URL: https://wp.nastf.org/?page_id=367
- Confidence: High
- Description: Credentialing, application resources, secure-tool validation and VSP operational guidance for security-related automotive work.
## NASTF VSP definition
- Category: Professional security
- URL: https://support.nastf.org/support/solutions/articles/43000755446-what-is-a-nastf-vehicle-security-professional-
- Confidence: High
- Description: Explains qualification and business requirements for professionals performing key programming, code ordering or immobilizer resets.
## NASTF Memberships
- Category: Professional security
- URL: https://wp.nastf.org/?page_id=3969
- Confidence: High
- Description: Describes membership levels, VSP capabilities, customer authorization forms and access to key/immobilizer codes.
## NASTF SDRM portal
- Category: Professional security
- URL: https://sdrm.nastfsecurityregistry.org/
- Confidence: High
- Description: Secure Data Release Model account, login and VSP application portal for the US and Canada.
## CyberChef
- Category: Reference
- URL: https://gchq.github.io/CyberChef/
- Confidence: High
- Description: Browser-based data transformation and analysis workbench. Avoid entering customer secrets into unapproved hosted tools; prefer an approved offline instance.
## GitHub checksum topic
- Category: JavaScript
- URL: https://github.com/topics/checksum
- Confidence: Medium
- Description: Discovery catalog for checksum implementations. Validate licenses and test vectors before reuse.
## NASTF Assisted Immobilizer Reprogramming
- Category: Professional security
- URL: https://support.nastf.org/support/solutions/articles/43000755445-what-is-air-assisted-immobilizer-reprogramming-
- Confidence: High
- Description: High-level overview of AIR and its role in authorized immobilizer-related workflows.
## NASTF account types comparison
- Category: Professional security
- URL: https://support.nastf.org/support/solutions/articles/43000755695-nastf-account-types-comparison
- Confidence: High
- Description: Describes different NASTF account types and how they map to professional access needs.
## Choosing the correct NASTF account
- Category: Professional security
- URL: https://support.nastf.org/support/solutions/articles/43000761328-choosing-the-correct-nastf-account
- Confidence: High
- Description: Helps determine which NASTF account is appropriate for a given secure-service workflow.
## NIST Cryptographic Standards and Guidelines
- Category: Reference
- URL: https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines
- Confidence: High
- Description: Official NIST cryptography guidance and standards for understanding general cryptographic principles.
## CRC32 portable example
- Category: JavaScript
- URL: https://gist.github.com/diachedelic/582ac8fab84b7f2c34ff358ac5cc7cfa
- Confidence: Medium
- Description: Compact Uint8Array CRC-32 reference for JavaScript runtimes. Review license and verify against standard vectors.
## Checksum algorithm examples
- Category: JavaScript
- URL: https://gist.github.com/bryc/8a0885a4be58b6bbf0ec54c7758c0841
- Confidence: Low
- Description: Educational implementations of Fletcher, Adler and related checksums. Independently verify before use.
## restructure
- Category: JavaScript
- URL: https://github.com/foliojs/restructure
- Confidence: High
- Description: Declarative binary data structure parser/encoder for JS, alternative to binary-parser.
## buffer-layout
- Category: JavaScript
- URL: https://github.com/pabigot/buffer-layout
- Confidence: High
- Description: Structured binary data layout library (C-like structs) for Buffer/Uint8Array.
## js-sha256
- Category: Hashing
- URL: https://github.com/emn178/js-sha256
- Confidence: High
- Description: Dependency-free SHA-256/224 implementation for JavaScript.
## js-sha1
- Category: Hashing
- URL: https://github.com/emn178/js-sha1
- Confidence: High
- Description: Dependency-free SHA-1 implementation for JavaScript.
## blueimp JavaScript-MD5
- Category: Hashing
- URL: https://github.com/blueimp/JavaScript-MD5
- Confidence: High
- Description: Dependency-free MD5 implementation for JavaScript.
## crypto-js
- Category: Hashing
- URL: https://github.com/brix/crypto-js
- Confidence: Medium
- Description: AES/DES/SHA/MD5 suite; evaluate bundle size and Multi-PROG runtime compatibility before embedding.
## MDN TextEncoder
- Category: Encoding
- URL: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
- Confidence: High
- Description: Reference for UTF-8 encode/decode behavior in JavaScript runtimes.
## MDN DataView
- Category: Encoding
- URL: https://developer.mozilla.org/en-US/docs/Web/API/DataView
- Confidence: High
- Description: Reference for typed binary read/write access to ArrayBuffers.
## MDN Uint8Array
- Category: Encoding
- URL: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array
- Confidence: High
- Description: Reference for the typed array used throughout Multi-PROG-style buffer handling.
## Wikipedia CRC
- Category: Checksum
- URL: https://en.wikipedia.org/wiki/Cyclic_redundancy_check
- Confidence: High
- Description: Background on cyclic redundancy check theory and common polynomials.
## Wikipedia Fletcher checksum
- Category: Checksum
- URL: https://en.wikipedia.org/wiki/Fletcher%27s_checksum
- Confidence: High
- Description: Background on the Fletcher-16/32 checksum algorithms.
## Wikipedia Adler-32
- Category: Checksum
- URL: https://en.wikipedia.org/wiki/Adler-32
- Confidence: High
- Description: Background on the Adler-32 checksum algorithm.
## CRC RevEng catalogue
- Category: Checksum
- URL: https://reveng.sourceforge.io/crc-catalogue/all.htm
- Confidence: High
- Description: Canonical CRC parameter and check-value catalogue for verifying implementations against known vectors.
## NIST FIPS 180-4 (SHA)
- Category: Cryptography reference
- URL: https://csrc.nist.gov/pubs/fips/180-4/upd1/final
- Confidence: High
- Description: Official Secure Hash Standard specification (SHA-1/SHA-2 family).
## NIST FIPS 197 (AES)
- Category: Cryptography reference
- URL: https://csrc.nist.gov/pubs/fips/197/final
- Confidence: High
- Description: Official Advanced Encryption Standard specification.
## OWASP Cryptographic Storage Cheat Sheet
- Category: Cryptography reference
- URL: https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
- Confidence: High
- Description: General guidance on safe use of cryptographic primitives and key handling.
## pako
- Category: JavaScript
- URL: https://github.com/nodeca/pako
- Confidence: Medium
- Description: zlib/deflate port for JS; occasionally relevant for compressed dump handling.
## Node.js Buffer docs
- Category: Reference
- URL: https://nodejs.org/api/buffer.html
- Confidence: Medium
- Description: Reference for Buffer APIs when prototyping outside the Multi-PROG host runtime.
+41
View File
@@ -0,0 +1,41 @@
/* Dependency-free binary utilities for offline testing and authorized repair work.
Verify Multi-PROG host compatibility and API signatures in Local Script > Help. */
export function requireBytes(data) {
if (!(data instanceof Uint8Array)) throw new TypeError("Uint8Array required");
return data;
}
export function assertRange(data, offset, length) {
requireBytes(data);
if (!Number.isInteger(offset)||!Number.isInteger(length)||offset<0||length<0||offset+length>data.length) throw new RangeError("Range outside buffer");
}
export function cloneBytes(data){ requireBytes(data); return new Uint8Array(data); }
export function sliceBytes(data,offset,length){ assertRange(data,offset,length); return data.slice(offset,offset+length); }
export function bytesToHex(data,separator=" "){ requireBytes(data); return Array.from(data,b=>b.toString(16).padStart(2,"0")).join(separator).toUpperCase(); }
export function hexToBytes(text){
const s=String(text).replace(/[^0-9a-f]/gi,"");
if(s.length%2) throw new Error("Hex text must contain complete bytes");
const out=new Uint8Array(s.length/2); for(let i=0;i<out.length;i++) out[i]=parseInt(s.slice(i*2,i*2+2),16); return out;
}
export function asciiPreview(data,replacement="."){ requireBytes(data); return Array.from(data,b=>b>=32&&b<=126?String.fromCharCode(b):replacement).join(""); }
export function readU16LE(d,o){assertRange(d,o,2);return d[o]|(d[o+1]<<8);}
export function readU16BE(d,o){assertRange(d,o,2);return (d[o]<<8)|d[o+1];}
export function readU32LE(d,o){assertRange(d,o,4);return (d[o]|d[o+1]<<8|d[o+2]<<16|d[o+3]<<24)>>>0;}
export function readU32BE(d,o){assertRange(d,o,4);return ((d[o]<<24)|(d[o+1]<<16)|(d[o+2]<<8)|d[o+3])>>>0;}
export function writeU16LE(d,o,v){assertRange(d,o,2);d[o]=v&255;d[o+1]=(v>>>8)&255;}
export function writeU16BE(d,o,v){assertRange(d,o,2);d[o]=(v>>>8)&255;d[o+1]=v&255;}
export function writeU32LE(d,o,v){assertRange(d,o,4);for(let i=0;i<4;i++)d[o+i]=(v>>>(8*i))&255;}
export function writeU32BE(d,o,v){assertRange(d,o,4);for(let i=0;i<4;i++)d[o+i]=(v>>>(8*(3-i)))&255;}
export function findPattern(data,pattern,start=0){requireBytes(data);requireBytes(pattern);const hits=[];for(let i=start;i<=data.length-pattern.length;i++){let ok=true;for(let j=0;j<pattern.length;j++)if(data[i+j]!==pattern[j]){ok=false;break;}if(ok)hits.push(i);}return hits;}
export function countByte(data,value){requireBytes(data);let n=0;for(const b of data)if(b===(value&255))n++;return n;}
export function isBlank(data,offset=0,length=data.length-offset,blank=0xFF){assertRange(data,offset,length);for(let i=offset;i<offset+length;i++)if(data[i]!==blank)return false;return true;}
export function diffRanges(a,b){requireBytes(a);requireBytes(b);if(a.length!==b.length)throw new Error("Length mismatch");const out=[];let s=-1;for(let i=0;i<a.length;i++){if(a[i]!==b[i]&&s<0)s=i;if((a[i]===b[i]||i===a.length-1)&&s>=0){const e=a[i]===b[i]?i-1:i;out.push({start:s,end:e,length:e-s+1});s=-1;}}return out;}
export function changedByteCount(a,b){return diffRanges(a,b).reduce((n,r)=>n+r.length,0);}
export function sum8(data){requireBytes(data);let s=0;for(const b of data)s=(s+b)&255;return s;}
export function xor8(data){requireBytes(data);let x=0;for(const b of data)x^=b;return x;}
export function crc16CcittFalse(data){requireBytes(data);let crc=0xFFFF;for(const b of data){crc^=b<<8;for(let i=0;i<8;i++)crc=((crc&0x8000)?(crc<<1)^0x1021:crc<<1)&0xFFFF;}return crc;}
export function crc32(data){requireBytes(data);let crc=0xFFFFFFFF;for(const b of data){crc^=b;for(let i=0;i<8;i++)crc=(crc>>>1)^((crc&1)?0xEDB88320:0);}return (crc^0xFFFFFFFF)>>>0;}
export function adler32(data){requireBytes(data);let a=1,b=0;for(const v of data){a=(a+v)%65521;b=(b+a)%65521;}return ((b<<16)|a)>>>0;}
export function bcdToInt(byte){const hi=(byte>>>4)&15,lo=byte&15;if(hi>9||lo>9)throw new Error("Invalid BCD");return hi*10+lo;}
export function intToBcd(value){if(!Number.isInteger(value)||value<0||value>99)throw new RangeError("BCD value must be 0..99");return ((Math.floor(value/10)<<4)|(value%10));}
export function constantTimeEqual(a,b){requireBytes(a);requireBytes(b);if(a.length!==b.length)return false;let d=0;for(let i=0;i<a.length;i++)d|=a[i]^b[i];return d===0;}
export function allowListedWrite(copy,offset,newBytes,allowedRanges){requireBytes(copy);requireBytes(newBytes);assertRange(copy,offset,newBytes.length);const ok=allowedRanges.some(r=>offset>=r.start&&offset+newBytes.length-1<=r.end);if(!ok)throw new Error("Write is outside allow-list");copy.set(newBytes,offset);return copy;}
@@ -0,0 +1,2 @@
import { bytesToHex, asciiPreview, countByte, crc32 } from "../binary_utils.mjs";
export function inspect(data){return {length:data.length,first32:bytesToHex(data.slice(0,32)),ascii:asciiPreview(data.slice(0,64)),ffBytes:countByte(data,0xFF),crc32:crc32(data).toString(16).padStart(8,"0").toUpperCase()};}
@@ -0,0 +1,2 @@
import { constantTimeEqual, crc32 } from "../binary_utils.mjs";
export function verifyTwoReads(a,b){return {sameLength:a.length===b.length,identical:constantTimeEqual(a,b),crcA:crc32(a),crcB:crc32(b)};}
@@ -0,0 +1,2 @@
import { findPattern, hexToBytes } from "../binary_utils.mjs";
export function inventory(data,patterns){return patterns.map(p=>({label:p.label,offsets:findPattern(data,hexToBytes(p.hex))}));}
@@ -0,0 +1,3 @@
import { cloneBytes, allowListedWrite, diffRanges } from "../binary_utils.mjs";
// Synthetic demonstration only. No vehicle-security data or real module offsets.
export function demo(input){const output=cloneBytes(input);allowListedWrite(output,4,new Uint8Array([0xAA,0x55]),[{start:4,end:5}]);return {output,changes:diffRanges(input,output)};}
@@ -0,0 +1,2 @@
import { hexToBytes,crc16CcittFalse,crc32,adler32 } from "../binary_utils.mjs";
export function selfTest(){const d=new TextEncoder().encode("123456789");return {crc16Ok:crc16CcittFalse(d)===0x29B1,crc32Ok:crc32(d)===0xCBF43926,adler32:adler32(d)};}
@@ -0,0 +1,2 @@
const REQUIRED=["caseId","customerName","vehicleVin","proofOfOwnershipRef","authorizationRef","technician","timestampUtc","requestedService"];
export function validateAuthorizedJob(job){const missing=REQUIRED.filter(k=>!String(job[k]??"").trim());const vinOk=/^[A-HJ-NPR-Z0-9]{17}$/.test(String(job.vehicleVin||"").toUpperCase());return {valid:missing.length===0&&vinOk,missing,vinOk};}
@@ -0,0 +1,2 @@
// Administrative inventory only. Does not calculate, extract, clone, generate or program credentials.
export function auditKeyInventory(entries){return entries.map((e,i)=>({row:i+1,assetTag:String(e.assetTag||""),type:String(e.type||""),source:String(e.source||""),sealed:Boolean(e.sealed),assigned:Boolean(e.assigned),notes:String(e.notes||"")}));}
@@ -0,0 +1,2 @@
export function redact(value,keepLast=4){const s=String(value??"");return s.length<=keepLast?"*".repeat(s.length):"*".repeat(s.length-keepLast)+s.slice(-keepLast);}
export function securityReport(job){return {caseId:job.caseId,vin:redact(job.vehicleVin,6),authorizationRef:redact(job.authorizationRef,4),technician:job.technician,timestampUtc:job.timestampUtc,status:job.status};}
@@ -0,0 +1,19 @@
/* Authorized-shop administrative example.
Does not read, alter, or conceal crash/deployment data. */
const REQUIRED = [
"caseId", "timestampUtc", "technician", "customerName", "vehicleVin",
"proofOfOwnershipRef", "authorizationRef", "requestedService"
];
export function validateAirbagCase(caseRecord) {
const missing = REQUIRED.filter(k => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "").trim().toUpperCase();
const vinValid = /^[A-HJ-NPR-Z0-9]{17}$/.test(vin);
const authorizationValid = Boolean(String(caseRecord.authorizationRef ?? "").trim());
return {
valid: missing.length === 0 && vinValid && authorizationValid,
missing,
vinValid,
authorizationValid
};
}
@@ -0,0 +1,11 @@
import { constantTimeEqual, crc32 } from "../../binary_utils.mjs";
/* Verifies two independent reads of the same module dump match before any further processing. */
export function verifyAirbagReadConsistency(readA, readB) {
return {
sameLength: readA.length === readB.length,
identical: constantTimeEqual(readA, readB),
crcA: crc32(readA),
crcB: crc32(readB)
};
}
@@ -0,0 +1,19 @@
/* Redacts sensitive/identifying values before including module status in a shop report.
Does not delete, alter, or reinterpret crash/deployment records. */
export function redactAirbagValue(value, visibleTail = 4) {
const text = String(value ?? "");
const keep = Math.max(0, Math.min(Number(visibleTail) || 0, text.length));
return "*".repeat(text.length - keep) + text.slice(text.length - keep);
}
export function airbagStatusReport(job) {
return {
caseId: job.caseId,
vin: redactAirbagValue(job.vehicleVin, 6),
authorizationRef: redactAirbagValue(job.authorizationRef, 4),
technician: job.technician,
timestampUtc: job.timestampUtc,
deploymentStatusAsFound: job.deploymentStatusAsFound,
status: job.status
};
}
@@ -0,0 +1,12 @@
/* Produces a consistent, sanitized filename for authorized airbag/SRS module backups. */
function safe(value) {
return String(value ?? "unknown").trim().replace(/[^A-Za-z0-9._-]+/g, "_").slice(0, 50);
}
export function buildAirbagBackupName(meta) {
const stamp = safe(meta.timestampUtc).replace(/[:]/g, "-");
return [
safe(meta.caseId), safe(meta.vehicleVin), safe(meta.module),
safe(meta.memoryRegion), safe(meta.readNumber), stamp
].join("_") + ".bin";
}
@@ -0,0 +1,13 @@
/* Final administrative gate before returning the vehicle or module. */
export function auditAirbagCompletion(job) {
const checks = {
authorizationOnFile: Boolean(job.authorizationRef),
ownershipVerified: Boolean(job.proofOfOwnershipRef),
originalBackupPreserved: Boolean(job.originalBackupHash),
duplicateReadsMatched: job.duplicateReadsMatched === true,
crashDataUnaltered: job.crashDataUnaltered === true,
customerAcknowledged: Boolean(job.customerAcknowledgmentRef),
technicianSigned: Boolean(job.technicianSignatureRef)
};
return { complete: Object.values(checks).every(Boolean), checks };
}
@@ -0,0 +1,12 @@
# Safe Airbag/SRS Module Script Examples for Authorized Shop Work
These examples support administrative control, evidence preservation, read consistency, backup naming, and service completion auditing for authorized airbag/SRS module work.
They intentionally do not include:
- Crash/deployment data deletion, clearing, or alteration intended to conceal collision history
- Disabling, defeating, or bypassing airbag/SRS deployment logic
- DTC clearing to hide accident or repair history
- Any cryptographic, security-access, or seed-key material
Host-facing Multi-PROG calls are not assumed. Adapt only after validating exact API signatures in the installed Multi-PROG Local Script Help.
@@ -0,0 +1,18 @@
/* Authorized-shop administrative example. */
const REQUIRED = [
"caseId", "timestampUtc", "technician", "customerName", "vehicleVin",
"proofOfOwnershipRef", "authorizationRef", "requestedService"
];
export function validateEcuCase(caseRecord) {
const missing = REQUIRED.filter(k => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "").trim().toUpperCase();
const vinValid = /^[A-HJ-NPR-Z0-9]{17}$/.test(vin);
const authorizationValid = Boolean(String(caseRecord.authorizationRef ?? "").trim());
return {
valid: missing.length === 0 && vinValid && authorizationValid,
missing,
vinValid,
authorizationValid
};
}
@@ -0,0 +1,11 @@
import { constantTimeEqual, crc32 } from "../../binary_utils.mjs";
/* Verifies two independent reads of the same ECU calibration image match before any further processing. */
export function verifyEcuReadConsistency(readA, readB) {
return {
sameLength: readA.length === readB.length,
identical: constantTimeEqual(readA, readB),
crcA: crc32(readA),
crcB: crc32(readB)
};
}
@@ -0,0 +1,13 @@
import { sliceBytes, asciiPreview } from "../../binary_utils.mjs";
/* Read-only comparison of a declared part-number field against an expected value.
Uses a caller-supplied offset/length; no real module offsets are hard-coded. */
export function crosscheckPartNumber(data, field, expectedPartNumber) {
const raw = sliceBytes(data, field.offset, field.length);
const found = asciiPreview(raw, "").trim();
return {
found,
expected: String(expectedPartNumber ?? ""),
matches: found === String(expectedPartNumber ?? "")
};
}
@@ -0,0 +1,18 @@
/* Generic change-scope guard for an explicitly authorized ECU repair plan.
Uses synthetic/configured ranges only. No real calibration offsets are included. */
import { diffRanges } from "../../binary_utils.mjs";
function rangeInside(change, allowed) {
return change.start >= allowed.start && change.end <= allowed.end;
}
export function validateAuthorizedEcuChangeScope(original, modified, allowedRanges) {
const changes = diffRanges(original, modified);
const violations = changes.filter(c => !allowedRanges.some(a => rangeInside(c, a)));
return {
valid: violations.length === 0,
changes,
violations,
changedBytes: changes.reduce((n, r) => n + r.length, 0)
};
}
@@ -0,0 +1,13 @@
/* Final administrative gate before returning the vehicle or module. */
export function auditEcuCompletion(job) {
const checks = {
authorizationOnFile: Boolean(job.authorizationRef),
ownershipVerified: Boolean(job.proofOfOwnershipRef),
originalBackupPreserved: Boolean(job.originalBackupHash),
duplicateReadsMatched: job.duplicateReadsMatched === true,
partNumberVerified: job.partNumberVerified === true,
customerAcknowledged: Boolean(job.customerAcknowledgmentRef),
technicianSigned: Boolean(job.technicianSignatureRef)
};
return { complete: Object.values(checks).every(Boolean), checks };
}
@@ -0,0 +1,11 @@
# Safe ECU Script Examples for Authorized Shop Work
These examples support administrative control, calibration read consistency, part-number cross-checking, allow-listed change-scope validation, and service completion auditing for authorized ECU work.
They intentionally do not include:
- Emissions defeat, torque/power "unlock" cracking, or immobilizer/security-access bypass
- Any cryptographic, security-access, or seed-key material
- Real calibration offsets or tuning maps
Host-facing Multi-PROG calls are not assumed. Adapt only after validating exact API signatures in the installed Multi-PROG Local Script Help.
@@ -0,0 +1,19 @@
/* Authorized-shop administrative example.
Does not read, derive, calculate, program, clone, or bypass immobilizer credentials. */
const REQUIRED = [
"caseId", "timestampUtc", "technician", "customerName", "vehicleVin",
"proofOfOwnershipRef", "authorizationRef", "requestedService"
];
export function validateImmoCase(caseRecord) {
const missing = REQUIRED.filter(k => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "").trim().toUpperCase();
const vinValid = /^[A-HJ-NPR-Z0-9]{17}$/.test(vin);
const authorizationValid = Boolean(String(caseRecord.authorizationRef ?? "").trim());
return {
valid: missing.length === 0 && vinValid && authorizationValid,
missing,
vinValid,
authorizationValid
};
}
@@ -0,0 +1,18 @@
/* Confirms that two authorized module reads are identical.
It does not locate or expose security fields. */
import { constantTimeEqual, crc32, diffRanges } from "../../binary_utils.mjs";
export function compareAuthorizedReads(readA, readB) {
if (readA.length !== readB.length) {
return { valid: false, reason: "Different file lengths", lengthA: readA.length, lengthB: readB.length };
}
const identical = constantTimeEqual(readA, readB);
return {
valid: identical,
identical,
length: readA.length,
crc32A: crc32(readA).toString(16).padStart(8, "0").toUpperCase(),
crc32B: crc32(readB).toString(16).padStart(8, "0").toUpperCase(),
changedRanges: identical ? [] : diffRanges(readA, readB)
};
}
@@ -0,0 +1,18 @@
/* Generic dump-quality report for authorized repair intake.
Does not identify, decode, or reveal immobilizer secrets. */
import { countByte, crc32, asciiPreview } from "../../binary_utils.mjs";
export function buildDumpQualityReport(data) {
if (!data.length) return { valid: false, reason: "Empty input" };
const ff = countByte(data, 0xFF);
const zero = countByte(data, 0x00);
return {
valid: true,
length: data.length,
crc32: crc32(data).toString(16).padStart(8, "0").toUpperCase(),
ffPercent: Number((ff * 100 / data.length).toFixed(2)),
zeroPercent: Number((zero * 100 / data.length).toFixed(2)),
printablePreview: asciiPreview(data.slice(0, 64)),
warning: ff === data.length || zero === data.length ? "Input appears blank or erased" : ""
};
}
@@ -0,0 +1,16 @@
/* Reconciles physical key inventory for a documented job.
Does not generate, clone, learn, or program keys. */
export function reconcileKeys(record) {
const received = Number(record.keysReceived ?? 0);
const returned = Number(record.keysReturned ?? 0);
const added = Number(record.newKeysIssued ?? 0);
const retained = Number(record.keysRetainedByShop ?? 0);
const expectedReturn = received + added - retained;
return {
received, returned, added, retained, expectedReturn,
balanced: Number.isInteger(received) && Number.isInteger(returned) &&
Number.isInteger(added) && Number.isInteger(retained) &&
[received, returned, added, retained].every(v => v >= 0) &&
returned === expectedReturn
};
}
@@ -0,0 +1,17 @@
/* Redacts sensitive values in shop reports.
Raw security values should not appear in general logs. */
export function redactSecurityValue(value, visibleTail = 4) {
const text = String(value ?? "");
const keep = Math.max(0, Math.min(Number(visibleTail) || 0, text.length));
return "*".repeat(text.length - keep) + text.slice(text.length - keep);
}
export function redactRecord(record, fields) {
const copy = { ...record };
for (const field of fields) {
if (Object.prototype.hasOwnProperty.call(copy, field)) {
copy[field] = redactSecurityValue(copy[field]);
}
}
return copy;
}
@@ -0,0 +1,12 @@
/* Produces a consistent, sanitized filename for authorized backups. */
function safe(value) {
return String(value ?? "unknown").trim().replace(/[^A-Za-z0-9._-]+/g, "_").slice(0, 50);
}
export function buildBackupName(meta) {
const stamp = safe(meta.timestampUtc).replace(/[:]/g, "-");
return [
safe(meta.caseId), safe(meta.vehicleVin), safe(meta.module),
safe(meta.memoryRegion), safe(meta.readNumber), stamp
].join("_") + ".bin";
}
@@ -0,0 +1,18 @@
/* Generic change-scope guard for an explicitly authorized repair plan.
Uses synthetic/configured ranges only. No real module offsets are included. */
import { diffRanges } from "../../binary_utils.mjs";
function rangeInside(change, allowed) {
return change.start >= allowed.start && change.end <= allowed.end;
}
export function validateAuthorizedChangeScope(original, modified, allowedRanges) {
const changes = diffRanges(original, modified);
const violations = changes.filter(c => !allowedRanges.some(a => rangeInside(c, a)));
return {
valid: violations.length === 0,
changes,
violations,
changedBytes: changes.reduce((n, r) => n + r.length, 0)
};
}
@@ -0,0 +1,14 @@
/* Final administrative gate before returning the vehicle or module. */
export function auditCompletion(job) {
const checks = {
authorizationOnFile: Boolean(job.authorizationRef),
ownershipVerified: Boolean(job.proofOfOwnershipRef),
originalBackupPreserved: Boolean(job.originalBackupHash),
duplicateReadsMatched: job.duplicateReadsMatched === true,
physicalKeysReconciled: job.physicalKeysReconciled === true,
customerAcknowledged: Boolean(job.customerAcknowledgmentRef),
securityDataRedacted: job.securityDataRedacted === true,
technicianSigned: Boolean(job.technicianSignatureRef)
};
return { complete: Object.values(checks).every(Boolean), checks };
}
@@ -0,0 +1,13 @@
# Safe Immobilizer Script Examples for Authorized Shop Work
These examples support administrative control, evidence preservation, read consistency, dump-quality assessment, key inventory reconciliation, redaction, backup naming, change-scope validation, and service completion auditing.
They intentionally do not include:
- PIN, CS, MAC, ISN, password, secret-key, or seed-key extraction
- Transponder cloning or credential generation
- All-keys-lost programming sequences
- Immobilizer off, delete, bypass, virginization, synchronization, or security-access circumvention
- Real EEPROM/DFLASH/PFLASH security offsets or cryptographic material
Host-facing Multi-PROG calls are not assumed. Adapt only after validating exact API signatures in the installed Multi-PROG Local Script Help.
@@ -0,0 +1,18 @@
/* Authorized-shop administrative example for BCM/instrument cluster/TCU work. */
const REQUIRED = [
"caseId", "timestampUtc", "technician", "customerName", "vehicleVin",
"proofOfOwnershipRef", "authorizationRef", "requestedService", "module"
];
export function validateModuleCase(caseRecord) {
const missing = REQUIRED.filter(k => !String(caseRecord[k] ?? "").trim());
const vin = String(caseRecord.vehicleVin ?? "").trim().toUpperCase();
const vinValid = /^[A-HJ-NPR-Z0-9]{17}$/.test(vin);
const authorizationValid = Boolean(String(caseRecord.authorizationRef ?? "").trim());
return {
valid: missing.length === 0 && vinValid && authorizationValid,
missing,
vinValid,
authorizationValid
};
}
@@ -0,0 +1,11 @@
import { constantTimeEqual, crc32 } from "../../binary_utils.mjs";
/* Verifies two independent reads of the same module dump match before any further processing. */
export function verifyModuleReadConsistency(readA, readB) {
return {
sameLength: readA.length === readB.length,
identical: constantTimeEqual(readA, readB),
crcA: crc32(readA),
crcB: crc32(readB)
};
}
@@ -0,0 +1,12 @@
/* Produces a consistent, sanitized filename for authorized module backups. */
function safe(value) {
return String(value ?? "unknown").trim().replace(/[^A-Za-z0-9._-]+/g, "_").slice(0, 50);
}
export function buildModuleBackupName(meta) {
const stamp = safe(meta.timestampUtc).replace(/[:]/g, "-");
return [
safe(meta.caseId), safe(meta.vehicleVin), safe(meta.module),
safe(meta.memoryRegion), safe(meta.readNumber), stamp
].join("_") + ".bin";
}
@@ -0,0 +1,20 @@
/* Generic change-scope guard for an explicitly authorized repair plan.
Uses synthetic/configured ranges only. No real module offsets are included.
Callers must exclude any mileage/odometer field from allowedRanges; this guard
only checks that changes stay within the caller-declared allow-list. */
import { diffRanges } from "../../binary_utils.mjs";
function rangeInside(change, allowed) {
return change.start >= allowed.start && change.end <= allowed.end;
}
export function validateAuthorizedModuleChangeScope(original, modified, allowedRanges) {
const changes = diffRanges(original, modified);
const violations = changes.filter(c => !allowedRanges.some(a => rangeInside(c, a)));
return {
valid: violations.length === 0,
changes,
violations,
changedBytes: changes.reduce((n, r) => n + r.length, 0)
};
}
@@ -0,0 +1,13 @@
/* Final administrative gate before returning the vehicle or module. */
export function auditModuleCompletion(job) {
const checks = {
authorizationOnFile: Boolean(job.authorizationRef),
ownershipVerified: Boolean(job.proofOfOwnershipRef),
originalBackupPreserved: Boolean(job.originalBackupHash),
duplicateReadsMatched: job.duplicateReadsMatched === true,
mileageFieldUnchanged: job.mileageFieldUnchanged === true,
customerAcknowledged: Boolean(job.customerAcknowledgmentRef),
technicianSigned: Boolean(job.technicianSignatureRef)
};
return { complete: Object.values(checks).every(Boolean), checks };
}
@@ -0,0 +1,12 @@
# Safe Other-Module (BCM / Instrument Cluster / TCU) Script Examples for Authorized Shop Work
These examples support administrative control, read consistency, backup naming, allow-listed change-scope validation, and service completion auditing for authorized body/cluster/transmission module work.
They intentionally do not include:
- Odometer/mileage alteration, rollback, or correction of any kind
- Feature-unlock cracking or piracy of paid options
- Immobilizer/security-access bypass or cryptographic material
- Real module memory offsets
Host-facing Multi-PROG calls are not assumed. Adapt only after validating exact API signatures in the installed Multi-PROG Local Script Help.
@@ -0,0 +1,20 @@
/* SAFE MULTI-PROG ADAPTATION TEMPLATE
This file intentionally uses placeholders. Replace them only with exact functions
documented in your installed Multi-PROG Local Script > Help. */
function inspectCurrentBuffer() {
// const data = HOST_READ_CURRENT_BUFFER();
// if (!data || data.length !== EXPECTED_SIZE) { HOST_MESSAGE("Unexpected size"); return; }
// Perform read-only inspection and display metadata.
}
function exportValidatedCopy() {
// 1. Read current buffer.
// 2. Clone it.
// 3. Apply only an explicitly authorized, allow-listed transformation.
// 4. Generate a diff report.
// 5. Refuse output unless every invariant passes.
// 6. Save to a new filename. Never overwrite the original.
}
// HOST_ADD_FUNCTION_BUTTON("Inspect", inspectCurrentBuffer);
@@ -0,0 +1,7 @@
import { strict as assert } from "node:assert";
import { crc32, diffRanges, hex } from "../binary_utils.mjs";
const v=new TextEncoder().encode("123456789");
assert.equal(crc32(v),0xCBF43926);
assert.equal(hex(new Uint8Array([0,15,255])),"00 0F FF");
assert.deepEqual(diffRanges(new Uint8Array([1,2,3,4]),new Uint8Array([1,9,8,4])),[{start:1,end:2,length:2}]);
console.log("All tests passed");
@@ -0,0 +1,28 @@
# Validation and Release Workflow
## Intake
- Record customer authorization, VIN, module identity, part number and complaint.
- Photograph connections and note adapter/pinout source.
## Acquisition
- Perform two reads.
- Save as `CASEID_module_region_read01.bin` and `read02.bin`.
- Compare byte-for-byte; stop if they differ.
- Record SHA-256 hashes.
## Development
- Work only on a copy.
- Use synthetic or known-good test vectors first.
- Maintain an allow-list of writable offsets.
- Log every changed range with original and new bytes.
## Validation
- Verify size, identifiers, blank regions, changed ranges and checksum.
- Save a machine-readable diff report.
- Peer-review production scripts.
## Release
- Version the script and record Multi-PROG software/firmware versions.
- Sign or hash the release bundle.
- Test on spare hardware.
- Preserve rollback files and notes.
+15
View File
@@ -0,0 +1,15 @@
# Research Queries
- `"Multi-PROG Script Function Manual"`
- `site:github.com "Multi-Prog" mjs`
- `site:github.com "AddFunctionButton" "ReadData"`
- `"Xhorse Multi-PROG" script JavaScript`
- `"Multi-PROG" checksum green C`
- `JavaScript Uint8Array binary parser`
- `JavaScript CRC known test vectors`
- `NASTF Vehicle Security Professional`
- `NASTF Assisted Immobilizer Reprogramming`
- `OpenRemap ECU binary identification`
## Evaluation rules
Prefer official manuals and built-in Help. Verify mirrors against official text. Check repository license, activity, issue history and source transparency. Do not collect or deploy content intended for unauthorized access, immobilizer bypass, odometer fraud or circumvention.
+43
View File
@@ -0,0 +1,43 @@
# Security-sensitive topic research notes
This note gathers high-level, legitimate references for topics that are often discussed around XHorse Multi-PROG and immobilizer work. These materials support documentation review, professional credentialing, and safe bench-study work only. They are not operational instructions for extraction, bypass, cloning, or unauthorized patching.
## PIN / CS / MAC / ISN / password / secret-key / seed-key extraction
- NASTF Vehicle Security Professional Registry — official credentialing and secure-access context for professionals.
- NASTF "I need a code" article — explains legitimate code-ordering options and the role of secure automotive information access.
- NASTF SDRM portal — secure registry portal and VSP workflow entry point.
## Real immobilizer memory offsets
- OpenRemap — offline ECU binary identification and health-checking workflows.
- EcuBus-Pro — ECU development and test-tool architecture reference for understanding how ECUs and security-related data are organized.
- Binary parser and checksum references in this kit — useful for safe, read-only inspection of dumps and binary images.
## Key or transponder cloning
- NASTF AIR (Assisted Immobilizer Reprogramming) — high-level professional context for immobilizer reprogramming and authorized workflows.
- NASTF VSP Registry — qualification and governance material for secure vehicle-security work.
- Multi-PROG owner/manual references — for understanding the tool’s supported workflows and limits.
## Credential generation
- NASTF VSP Registry — explains how credentialing, secure tool validation and access are managed.
- NASTF account types comparison — helps differentiate free, member and VSP access paths.
- NASTF choosing the correct account — useful for understanding which account fits a professional workflow.
## All-keys-lost procedures
- NASTF AIR article — professional context for assisted immobilizer reprogramming.
- NASTF "I need a code" article — legitimate code procurement and approved workflow context.
- NASTF VSP Registry — official access and governance route for security-related operations.
## Immobilizer disabling or bypassing
- Treat this topic as out of scope for operational instructions in this kit.
- Use NASTF scan tool validation and VSP terms/conditions as governance references for legitimate security work.
- Prefer read-only inspection, vehicle documentation review, and authorized service procedures.
## Virginization or synchronization patches
- OpenRemap and checksum references — useful for validating dumps and understanding patch safety.
- Multi-PROG checksum workflow guides — helpful for understanding supported validation and verification steps.
- Keep immutable originals, record hashes, and validate in a controlled bench environment before any write.
## Security-access algorithms or cryptographic material
- CyberChef — safe, offline-friendly byte-level analysis and transformation reference.
- GitHub checksum topic and js-crc — high-level learning references for checksum and validation routines.
- NIST Cryptographic Standards and Guidelines — official, general-purpose cryptography reference material.
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://www.xhorsevvdi.com/upload/pro/24040217120425363837.pdf
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://images.dkgcc.com/files/2025/5/8537_Xhorse%20Multi%20Prog%20User%20Manual.pdf
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://www.manualslib.com/manual/3420478/Xhorse-Multi-Prog.html
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://www.manualslib.com/guide/4237862/xhorse-multi-prog-automotive-programmer-operation-manual.html
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://blog.xhorsetool.com/how-to-make-first-script-with-xhorse-multi-prog/
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://www.obdexpress.co.uk/service/introduction-to-xhorse-multi-prog-third-party-script-function.html
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://obd2services.wordpress.com/2024/02/02/xhorse-multi-prog-third-party-scripting-how-to/
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://github.com/CarKeyGuyNL/Multi-Prog-Scripts
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://mhhauto.com/Thread-Xhores-Multi-Prog-script
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://blog.xhorsetool.com/how-to-calculate-checksum-for-xhorse-multi-prog/
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://blog.xhorsevvdi.com/how-to-use-checksum-calculation-in-xhorse-multi-prog/
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://blog.vvdishop.com/which-ecu-is-supported-for-checksum-in-xhorse-multi-prog/
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://github.com/keichi/binary-parser
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://github.com/dloss/binary-parsing
+2
View File
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://jdataview.github.io/jBinary/
+2
View File
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://www.npmjs.com/package/js-crc
+2
View File
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://github.com/topics/ecu
+2
View File
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://github.com/ecubus/EcuBus-Pro
+2
View File
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://pypi.org/project/openremap/
@@ -0,0 +1 @@
case_id,timestamp_utc,customer_name,customer_id_ref,proof_of_ownership_ref,VIN,vehicle_year_make_model,requested_service,authorization_ref,technician,module_asset_tag,keys_received,keys_returned,status,notes
1 case_id timestamp_utc customer_name customer_id_ref proof_of_ownership_ref VIN vehicle_year_make_model requested_service authorization_ref technician module_asset_tag keys_received keys_returned status notes
@@ -0,0 +1 @@
case_id,item_id,item_type,from_person,to_person,timestamp_utc,condition,seal_number,location,signature_ref,notes
1 case_id item_id item_type from_person to_person timestamp_utc condition seal_number location signature_ref notes
@@ -0,0 +1,29 @@
# Authorized Key and Immobilizer Work
This section supports lawful shop operations without providing credential extraction, bypass, cloning, PIN calculation, transponder generation, or immobilizer-disable procedures.
## Required controls
1. Verify government-issued identification and proof of ownership or documented agent authority.
2. Record VIN, registration/title reference, customer authorization, requested service and technician identity.
3. For security-related services in the United States or Canada, use applicable OEM-approved processes and the NASTF Vehicle Security Professional/SDRM framework where required.
4. Keep credentials, PINs, key codes and security files out of general logs. Store them only in approved encrypted systems and redact routine reports.
5. Maintain chain of custody for modules, keys and dumps.
6. Perform two independent reads and preserve immutable hashes before authorized repair.
7. Separate diagnostic evidence from security secrets.
8. Record completion, returned keys, consumed inventory and customer acknowledgment.
## Supported examples in this archive
- Authorization/job-record validation
- Key inventory administration
- Redacted reporting
- Dual-read verification
- Read-only metadata inspection
- Audit log and chain-of-custody templates
## Deliberately excluded
- PIN, CS, MAC, ISN, password or secret extraction
- Seed-key/security-access algorithms
- Key/transponder cloning or generation
- All-keys-lost programming sequences
- Immobilizer bypass, delete, virginize or disable patches
- Real EEPROM offsets, cryptographic material or vehicle-specific credential maps
Submodule 08_Collected_MultiPROG_Scripts/GitHub/CarKeyGuyNL_Multi-Prog-Scripts added at b1b20916b0
@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
@@ -0,0 +1,14 @@
node_modules
dist
out
cli
build
tools
.gitignore
resources/bin
resources/lib
resources/docs/scriptApi
resources/buildInScript
src/renderer/src/views/uds/panel/panel-designer
src/renderer/src/views/ostrace/timeline
src/main/worker/canopen/source
@@ -0,0 +1,23 @@
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
extends: [
'eslint:recommended',
'plugin:vue/vue3-recommended',
'@electron-toolkit',
'@electron-toolkit/eslint-config-ts/eslint-recommended',
'@vue/eslint-config-typescript/recommended',
'@vue/eslint-config-prettier'
],
rules: {
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/ban-types': 'off',
'prettier/prettier': 'off'
}
}
@@ -0,0 +1,55 @@
node_modules
dist
out
.DS_Store
*.log*
__pycache__
.env.local
build
cache
!/build
.env
.ScriptBuild
resources/docs/scriptApi
resources/lib/kerneldlls/devices_property/*.xml
ecb_cli.exe
resources/examples/**/tsconfig.json
resources/examples/**/*.code-workspace
resources/examples/**/*.html
resources/examples/**/**/*.html
resources/examples/**/**/*.csv
resources/examples/**/**/*.blf
resources/examples/**/**/*.txt
resources/examples/**/**/*.bin
resources/examples/**/**/.claude
*.bak
log.txt
tools/a.bin
tools/a.txt
tools/a.csv
.env.development
doip-certs
# generated lib js and d.ts docs
resources/lib/js/cryptoExt.js
resources/lib/js/cryptoExt.js.map
resources/lib/js/plugin.js
resources/lib/js/plugin.js.map
resources/lib/js/secureAccess.js
resources/lib/js/uds.js
resources/lib/js/uds.js.map
resources/lib/js/utli.js
resources/lib/js/utli.js.map
resources/lib/js/index.js
resources/lib/js/index.js.map
src/main/share/crc.d.ts.html
src/main/share/cryptoExt.d.ts.html
src/main/share/uds.d.ts.html
src/main/share/utli.d.ts.html
src/main/share/index.d.ts.html
# python
resources/python
resources/get-pip.py*.tsbuildinfo
@@ -0,0 +1,2 @@
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
@@ -0,0 +1,13 @@
out
dist
/cli
build
tools
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json
resources/bin
resources/lib
**/*.html
webpack.config.js
@@ -0,0 +1,4 @@
singleQuote: true
semi: false
printWidth: 100
trailingComma: none
@@ -0,0 +1,106 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
EcuBus-Pro is an open-source automotive ECU (Electronic Control Unit) development and testing tool built with Electron. It serves as an alternative to commercial tools like CAN-OE.
**Key Features:**
- Cross-platform (Windows, Linux, macOS)
- Multi-hardware support (PEAK, Kvaser, Vector, ZLG, Toomotss, EcuBus-LinCable, SLCAN, GS_USB)
- Protocol support: CAN/CAN-FD, LIN, DoIP, SOME/IP
- UDS diagnostic capabilities
- TypeScript-based scripting and HIL testing framework
- DBC/LDF database support
- Panel builder for custom UI creation
## Architecture
This is an Electron application with a clear separation between main and renderer processes:
**Main Process (src/main/):**
- `docan/` - CAN protocol native module (C++)
- `dolin/` - LIN protocol native module (C++)
- `doip/` - DoIP protocol implementation
- `uds/` - Unified Diagnostic Services
- `vsomeip/` - SOME/IP protocol with C++ bindings
- `worker/` - **Third-party scripts provided to users** (must run `npm run worker:js` after any changes)
- `ipc/` - IPC communication handlers
**Renderer Process (src/renderer/src/):**
- Vue 3 + TypeScript frontend
- `views/` - UI pages (home, uds, ostrace, etc.)
- `stores/` - Pinia state management
- `router/` - Vue Router
- `database/` - DBC/LDF/ORTI parsers
**CLI (src/cli/):** Command-line interface for automation
## Development Commands
| Command | Description |
|---------|-------------|
| `npm run dev` | Start development server with HMR |
| `npm run build` | Build production version (runs typecheck first) |
| `npm run start` | Preview production build |
| `npm run test` | Run tests with Vitest |
| `npm run lint` | Run ESLint with auto-fix |
| `npm run format` | Run Prettier format |
| `npm run typecheck` | Run TypeScript type checks for both node and web |
| `npm run worker` | Build worker processes (requires Python/build tools) |
| `npm run worker:js` | **Build worker scripts (JS only) - MUST RUN after any changes to `src/main/worker/`** |
| `npm run native` | Build all native modules (docan, dolin, someip) |
| `npm run docan` | Build CAN native module |
| `npm run dolin` | Build LIN native module |
| `npm run someip` | Build SOME/IP native module |
| `npm run build:win` | Build for Windows (NSIS installer) |
| `npm run build:linux` | Build for Linux (deb, rpm) |
| `npm run build:mac` | Build for macOS |
| `npm run docs:dev` | Start VitePress docs dev server |
| `npm run docs:build` | Build VitePress documentation |
**Note:** Native module builds require Python and build tools (Visual Studio on Windows, gcc on Linux).
## Key Configuration Files
- `package.json` - Dependencies, scripts, and vendor hardware support config
- `electron-builder.yml` - Electron builder configuration
- `electron.vite.config.ts` - Vite configuration for Electron
- `tsconfig.json`, `tsconfig.node.json`, `tsconfig.web.json` - TypeScript configs
- `vitest.config.ts` - Vitest test configuration
- `webpack.config.js` - Webpack config for workers
## Internationalization
The project uses i18next for internationalization with support for English and Chinese. Translation files are located in `resources/locales/`.
## Frontend Development Guidelines
**UI Component Library:**
- Prefer using **Element Plus** components for all UI implementation
- For tables:
- Simple tables: Use `el-table` (from Element Plus)
- Complex tables: Use `vxe-table`
## Important Notes
**SWIG Generated Files:** All `*_wrap.cxx` files are generated by SWIG (Simplified Wrapper and Interface Generator). These files should be ignored and never modified manually. They are generated from the corresponding `.i` interface files.
**Worker Scripts:** The `src/main/worker/` directory contains scripts provided to third-party users, all code in here should has detailed typedoc format comments. **Any changes to this directory must be followed by running `npm run worker:js`** to rebuild the worker scripts.
## Cursor Cloud specific instructions
This section captures non-obvious, durable notes for developing EcuBus-Pro in a Cursor Cloud VM. Standard commands live in the tables above and in `package.json`; only the caveats specific to this Linux headless environment are listed here.
**Environment startup layer (already handled by the update script):** `npm install`, downloading the embedded standalone Python into `resources/python` plus `pip install -r resources/requirements.txt`, `npm run native`, and `npm run worker:js`. You do NOT need to rerun these manually on a fresh session unless you change the relevant sources.
**Node version:** CI uses Node 24, but the VM ships Node 22 and everything (install, native build, lint, typecheck, tests, `npm run dev`) works on it. Don't switch Node versions unless you hit a concrete incompatibility.
**Native modules on Linux:** `src/main/docan|dolin|vsomeip/binding.gyp` compile only `fake_linux.cxx` stubs on Linux — no SWIG or vendor SDKs are needed to build them, and the committed `*_wrap.cxx` files are only used on Windows. Only the `simulate` and `slcan` CAN vendors are functional on Linux (see `ecubusPro.vendor` in `package.json`); other vendors are stubbed. After changing `src/main/worker/`, rerun `npm run worker:js`.
**Python for diagnostic DB parsing:** ODX (`test/odx`) and CDD (`test/cdd`) parsing shell out to `resources/python/bin/python3` (see `getPythonPath()`). If that embedded Python or its packages (`odxtools`, `canmatrix`, `openpyxl`, ...) are missing, those tests and the app's ODX/CDD features fail with `spawn ... ENOENT`.
**Running the GUI:** This is an Electron desktop app. Run it with `DISPLAY=:1 npm run dev` (a headless X server is already running on display `:1`). It launches without extra `--no-sandbox` flags. The `Autofill.enable`/`Autofill.setAddresses` DevTools console errors at startup are harmless.
**Known pre-existing test failures on Linux (not environment issues):** There is no CI job that runs the Vitest suite, so some tests only pass on the original author's setup. `test/util/s19Parse.spec.ts > ok1` reads `./CMSIS-DAP_OpenSDA.s19` but the committed fixture is `CMSIS-DAP_OpenSDA.S19` (case mismatch, fails on case-sensitive Linux filesystems). `test/dbc/dbc.test.ts > id2001.dbc` has the same problem: it reads `id2001.dbc` but the committed fixture is `ID2001.dbc`. One `test/odx` assertion (`subfunc as first param`) is sensitive to the installed `odxtools` version. Hardware-backed suites under `test/docan`, `test/dolin`, `test/pwm`, `test/sa_dll` require physical devices and are expected to fail/skip. For a quick smoke run of hardware-independent tests: `npm run test -- --run test/util test/dbc test/odx test/viewer test/cdd test/encoding`.
@@ -0,0 +1,118 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
EcuBus-Pro is an open-source automotive ECU (Electronic Control Unit) development and testing tool built with Electron. It serves as an alternative to commercial tools like CAN-OE.
**Key Features:**
- Cross-platform (Windows, Linux, macOS)
- Multi-hardware support (PEAK, Kvaser, Vector, ZLG, Toomotss, EcuBus-LinCable, SLCAN, GS_USB)
- Protocol support: CAN/CAN-FD, LIN, DoIP, SOME/IP
- UDS diagnostic capabilities
- TypeScript-based scripting and HIL testing framework
- DBC/LDF database support
- Panel builder for custom UI creation
## Development Commands
| Command | Description |
|---------|-------------|
| `npm run dev` | Start development server with HMR |
| `npm run build` | Build production version (runs typecheck first) |
| `npm run start` | Preview production build |
| `npm run test` | Run all tests with Vitest |
| `npm run test -- test/util/hexParse.spec.ts` | Run single test file |
| `npm run test -- test/util/hexParse.spec.ts -t "test name"` | Run single test by name pattern |
| `npm run lint` | Run ESLint with auto-fix |
| `npm run format` | Run Prettier format |
| `npm run typecheck` | Run TypeScript type checks (node + web) |
| `npm run typecheck:node` | Type-check main/preload/CLI/tests only |
| `npm run typecheck:web` | Type-check renderer only |
| `npm run worker:js` | **Build worker scripts (JS only) - MUST RUN after any changes to `src/main/worker/`** |
| `npm run worker` | Build worker bundle including native secure-access addon |
| `npm run native` | Build all native modules (docan, dolin, someip) |
| `npm run docan` / `dolin` / `someip` | Build individual native modules |
| `npm run build:win` / `build:linux` / `build:mac` | Platform-specific builds |
| `npm run docs:dev` / `docs:build` | VitePress documentation |
| `npm run cli:build` | Build CLI |
**Note:** Native module builds require Python and platform build tools (Visual Studio on Windows, gcc on Linux).
## Architecture
This is an Electron application with clear separation between processes:
**Main Process (`src/main/`):**
- `index.ts` - Creates frameless BrowserWindow, registers `local-resource://` protocol, initializes logging/analytics/i18n
- `docan/`, `dolin/` - CAN/LIN protocol native modules (C++ with SWIG bindings)
- `doip/`, `uds/`, `vsomeip/` - DoIP, UDS, SOME/IP protocol implementations
- `worker/` - **Public worker script API provided to users** (must run `npm run worker:js` after changes)
- `workerClient.ts` - Runs user scripts in Node `worker_threads`, handles RPC/event messages
- `ipc/*.ts` - IPC handlers registered via side-effect imports in `ipc/index.ts`
- `multiWin.ts` - Manages extra Electron windows with MessageChannelMain for log sharing
- `share/` - Shared types and utilities accessible via `nodeCan/*` alias
**Preload (`src/preload/`):**
- Exposes `window.electron`, `window.api`, `window.store`, `window.path`, `window.dataParseWorker`
- Renderer must use these bridges instead of importing Electron/Node APIs directly
**Renderer (`src/renderer/src/`):**
- Vue 3 + Pinia + Vue Router (memory history) + Element Plus + VXE components
- `stores/` - `project.ts` (project metadata), `data.ts` (ECU/device/data), `runtime.ts` (runtime flags)
- `views/uds/layout.ts` - UDS workspace panels declared as `layoutMap` items
- State sync between windows uses `BroadcastChannel` pattern in `main.ts`
**CLI (`src/cli/`):** Automation CLI with separate Electron Vite config (`cli.vite.ts`)
## Path Aliases
| Alias | Target |
|-------|--------|
| `src/*` | Repository source root |
| `@r/*` | `src/renderer/src/` |
| `nodeCan/*` | `src/main/share/` |
Use existing aliases instead of long relative paths.
## Key Configuration Files
- `package.json` - Dependencies, scripts, vendor hardware support under `ecubusPro.vendor`
- `electron-builder.yml` - Electron builder configuration
- `electron.vite.config.ts` - Vite configuration for Electron
- `vitest.config.ts` - Vitest test configuration with path aliases
- `webpack.config.js` - Webpack config for worker bundling
## Repository Conventions
**UI Components:** Prefer Element Plus. Use `el-table` for simple tables, `vxe-table` for complex tables.
**Theme:** Dark mode maps to `VxeUI.setTheme('dark')` via `useDark()` watcher in `App.vue`.
**Plugin State Sync:** Use Wujie `bus` events (`update:dataStore`, `update:dataStore:fromMain`, `update:globalStart:fromMain`).
**Worker API Documentation:** Keep `src/main/worker/` documented with TSDoc (`@param`, `@returns`, `@throws`, `@example`, `@category`). This is shipped to users via TypeDoc.
**Formatter:** Single quotes, no semicolons, print width 100, no trailing commas.
**SWIG Generated Files:** Never edit `*_wrap.cxx` files - they are generated from `.i` interface files.
## Tests
Tests live under `test/` and run with Vitest.
- Hardware-dependent: `test/docan/`, `test/dolin/`, `test/pwm/` (require matching devices)
- Hardware-independent: `test/util/*.spec.ts`, `test/dbc/`, `test/odx/`, `test/viewer/` (good smoke tests)
## Internationalization
i18next-based. App translations in `resources/locales/<lang>/translation.json`. Plugin translations loaded from `locales/<lang>/translation.json` and merged via IPC.
## Git Commit Rules
- Do NOT add `Co-authored-by` trailers to commit messages.
## MCP Servers
Workspace MCP config in `.vscode/mcp.json`. The `playwright` server (`npx -y @playwright/mcp@latest`) is useful for browser-based UI exploration when dev server is running.
@@ -0,0 +1,118 @@
<div align="center">
<a href="https://app.whyengineer.com">
<img width="160" height="160" src="https://ecubus.oss-cn-chengdu.aliyuncs.com/img/logo256.png">
</a>
<h1>EcuBus-Pro</h1>
<div style="margin:5px; display: flex; justify-content: center; align-items: center;gap:4px">
<a href="https://github.com/ecubus/EcuBus-Pro/releases">
<img src="https://github.com/ecubus/EcuBus-Pro/actions/workflows/build.yml/badge.svg" alt="github-ci" />
</a>
<a href="https://github.com/ecubus/EcuBus-Pro/releases">
<img src="https://github.com/ecubus/EcuBus-Pro/actions/workflows/build-linux.yml/badge.svg" alt="github-ci" />
</a>
<a href="https://repology.org/project/ecubus-pro/versions">
<img src="https://repology.org/badge/version-for-repo/aur/ecubus-pro.svg" alt="AUR package">
</a>
<a href="https://github.com/ecubus/EcuBus-Pro">
<img src="https://img.shields.io/github/stars/ecubus/EcuBus-Pro"/>
</a>
</div>
<b style="font-size:20px;margin:10px;display:block">A powerful automotive ECU development tool</b>
<i>Easy of use, Cross platform, Multi dongle, Powerful script ability, CLI support</i><br/>
Document: <a href="https://app.whyengineer.com">https://app.whyengineer.com</a> | <a href="https://app.whyengineer.com/zh">中文文档</a>
</div>
## Overview
![main](https://ecubus.oss-cn-chengdu.aliyuncs.com/img/main.png)
EcuBus-Pro is an open-source alternative to commercial automotive diagnostic tools like `CAN-OE`. It provides a comprehensive solution for ECU development and testing with:
- 🆓 Open-source and free to use
- 🚀 Modern, intuitive user interface
- 💻 Cross-platform support (Windows, Linux, MacOS) - [Install](./docs/about/install.md)
- 🔌 Multi-hardware support
- **[EcuBus-LinCable](https://app.whyengineer.com/docs/um/hardware/lincable.html)**: LIN (Support Lin conformance test), [PWM](https://app.whyengineer.com/docs/um/pwm/pwm.html)
- **PEAK**: CAN, CAN-FD, LIN
- **KVASER**: CAN, CAN-FD, LIN
- **ZLG**: CAN, CAN-FD
- **Toomotss**: CAN, CAN-FD, LIN
- **VECTOR**: CAN, CAN-FD, LIN
- **SLCAN**: CAN, CAN-FD [Detail](https://app.whyengineer.com/docs/um/can/can.html#slcan-special)
- **GS_USB (CANDLE)**: CAN, CAN-FD [Detail](https://app.whyengineer.com/docs/um/can/can.html#gs-usb)
- 🛠️ Comprehensive diagnostic capabilities
- **Diagnostic Protocols**: CAN/CAN-FD, DoIP, LIN
- 🌐 **SOME/IP**: SOME/IP protocol support - [Details](./docs/um/someip/index.md)
- 📝 **Scripting**: Advanced TypeScript-based automation - [Details](./docs/um/script.md)
- 🧪 **Test**: HIL Test Framework - [Details](./docs/um/test/test.md)
- 📊 **Database Support**: LIN LDF (edit & export), CAN DBC (view) - [Details](./docs/um/database.md)
- 📈 **Data Visualization**: Real-time signal graphing and analysis - [Details](./docs/um/graph/graph.md)
- ⌨️ **Command Line**: Full-featured CLI for automation and integration - [Details](./docs/um/cli.md)
- 🎨 **Panel**: Drag-and-drop interface builder for custom UI - [Details](./docs/um/panel/index.md)
[Read the Docs to Learn More.](https://app.whyengineer.com)
## Support & Sponsorship
<div align="center">
<h3 style="padding:20px;font-size:22px">Platinum Sponsors</h3>
<table style="width: 80%; margin: 0 auto; border-collapse: collapse;">
<tbody>
<tr>
<td style="width: 33.33%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">Become a Sponsor</a>
</td>
<td style="width: 33.33%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">Become a Sponsor</a>
</td>
<td style="width: 33.33%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">Become a Sponsor</a>
</td>
</tr>
</tbody>
</table>
<h3 style="padding:20px;font-size:20px">Gold Sponsors</h3>
<table style="width: 90%; margin: 0 auto; border-collapse: collapse;">
<tbody>
<tr>
<td style="width: 25%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="http://www.cdkhdz.com" target="_blank">
<img src="./public/logo/KUNHONG-LOGO - re-E1.png" alt="KUNHONG" width="120"/>
</a>
</td>
<td style="width: 25%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">Become a Sponsor</a>
</td>
<td style="width: 25%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">Become a Sponsor</a>
</td>
<td style="width: 25%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">Become a Sponsor</a>
</td>
</tr>
</tbody>
</table>
</div>
---
Consider [becoming a sponsor](./docs/about/sponsor) to support ongoing development. Sponsors receive prominent logo placement with website links. 🙏
## Contributors
Thanks to all the contributors who have helped shape EcuBus-Pro:
<a href="https://github.com/ecubus/EcuBus-Pro/graphs/contributors" target="_blank"><img src="https://contrib.rocks/image?repo=ecubus/EcuBus-Pro"></a>
We welcome contributions! Please review our [contribution guidelines](./.github/contributing.md) before getting started.
## License
[Apache License 2.0](./license.txt)
@@ -0,0 +1,117 @@
<div align="center">
<a href="https://app.whyengineer.com/zh">
<img width="160" height="160" src="https://ecubus.oss-cn-chengdu.aliyuncs.com/img/logo256.png">
</a>
<h1>EcuBus-Pro</h1>
<div style="margin:5px; display: flex; justify-content: center; align-items: center;gap:4px">
<a href="https://github.com/ecubus/EcuBus-Pro/releases">
<img src="https://github.com/ecubus/EcuBus-Pro/actions/workflows/build.yml/badge.svg" alt="github-ci" />
</a>
<a href="https://github.com/ecubus/EcuBus-Pro/releases">
<img src="https://github.com/ecubus/EcuBus-Pro/actions/workflows/build-linux.yml/badge.svg" alt="github-ci" />
</a>
<a href="https://repology.org/project/ecubus-pro/versions">
<img src="https://repology.org/badge/version-for-repo/aur/ecubus-pro.svg" alt="AUR package">
</a>
<a href="https://github.com/ecubus/EcuBus-Pro">
<img src="https://img.shields.io/github/stars/ecubus/EcuBus-Pro"/>
</a>
</div>
<b style="font-size:20px;margin:10px;display:block">功能强大的汽车ECU开发工具</b>
<i>易于使用、跨平台、多适配器支持、强大的脚本能力、CLI支持</i><br/>
文档: <a href="https://app.whyengineer.com/zh">https://app.whyengineer.com/zh</a> | <a href="https://app.whyengineer.com">English Document</a>
</div>
## 概览
![main](https://ecubus.oss-cn-chengdu.aliyuncs.com/img/main.png)
EcuBus-Pro是商业汽车诊断工具(如`CAN-OE`)的开源替代品。它为ECU开发和测试提供了全面的解决方案,具有以下特点:
- 🆓 开源且免费使用
- 🚀 现代化、直观的用户界面
- 💻 跨平台支持(Windows、Linux、MacOS)- [安装指南](./docs/about/install.md)
- 🔌 多硬件支持
- **[EcuBus-LinCable](https://app.whyengineer.com/zh/docs/um/hardware/lincable.html)**: LIN(支持LIN一致性测试)、[PWM](https://app.whyengineer.com/zh/docs/um/pwm/pwm.html)
- **PEAK**: CAN、CAN-FD、LIN
- **KVASER**: CAN、CAN-FD、LIN
- **ZLG**: CAN、CAN-FD
- **Toomotss**: CAN、CAN-FD、LIN
- **VECTOR**: CAN、CAN-FD、LIN
- **SLCAN**: CAN、CAN-FD [详情](https://app.whyengineer.com/zh/docs/um/can/can.html#slcan-special)
- **GS_USB (CANDLE)**: CAN、CAN-FD [详情](https://app.whyengineer.com/zh/docs/um/can/can.html#gs-usb)
- 🛠️ 全面的诊断功能
- **诊断协议**: CAN/CAN-FD、DoIP、LIN
- 🌐 **SOME/IP**: SOME/IP协议支持 - [详情](https://app.whyengineer.com/zh/docs/um/someip/index.html)
- 📝 **脚本**: 基于TypeScript的高级自动化 - [详情](./docs/um/script.md)
- 🧪 **测试**: HIL测试框架 - [详情](./docs/um/test/test.md)
- 📊 **数据库支持**: LIN LDF(编辑和导出)、CAN DBC(查看) - [详情](./docs/um/database.md)
- 📈 **数据可视化**: 实时信号图表和分析 - [详情](./docs/um/graph/graph.md)
- ⌨️ **命令行**: 功能齐全的CLI,支持自动化和集成 - [详情](./docs/um/cli.md)
- 🎨 **面板**: 拖拽式界面构建器,用于自定义UI - [详情](./docs/um/panel/index.md)
[阅读文档了解更多](https://app.whyengineer.com/zh/)
## 支持与赞助
<div align="center">
<h3 style="padding:20px;font-size:22px">白金赞助商</h3>
<table style="width: 80%; margin: 0 auto; border-collapse: collapse;">
<tbody>
<tr>
<td style="width: 33.33%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">成为赞助商</a>
</td>
<td style="width: 33.33%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">成为赞助商</a>
</td>
<td style="width: 33.33%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">成为赞助商</a>
</td>
</tr>
</tbody>
</table>
<h3 style="padding:20px;font-size:20px">金牌赞助商</h3>
<table style="width: 90%; margin: 0 auto; border-collapse: collapse;">
<tbody>
<tr>
<td style="width: 25%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="http://www.cdkhdz.com" target="_blank">
<img src="./public/logo/KUNHONG-LOGO - re-E1.png" alt="KUNHONG" width="120"/>
</a>
</td>
<td style="width: 25%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">成为赞助商</a>
</td>
<td style="width: 25%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">成为赞助商</a>
</td>
<td style="width: 25%; text-align: center; padding: 20px; border: 1px solid #eee;">
<a href="./docs/about/sponsor">成为赞助商</a>
</td>
</tr>
</tbody>
</table>
</div>
---
考虑[成为赞助商](./docs/about/sponsor)以支持持续开发。赞助商将获得显著的徽标展示位置和网站链接。🙏
## 贡献者
感谢所有帮助塑造EcuBus-Pro的贡献者:
<a href="https://github.com/ecubus/EcuBus-Pro/graphs/contributors" target="_blank"><img src="https://contrib.rocks/image?repo=ecubus/EcuBus-Pro"></a>
我们欢迎贡献!在开始之前,请查看我们的[贡献指南](./.github/contributing.md)。
## 许可证
[Apache License 2.0](./license.zh.txt)
@@ -0,0 +1,35 @@
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import ConditionalCompile from 'vite-plugin-conditional-compiler'
export default defineConfig({
main: {
plugins: [externalizeDepsPlugin(), ConditionalCompile()],
resolve: {
// src
alias: {
src: resolve(__dirname, 'src')
}
},
build: {
target: 'node18',
sourcemap: true,
rollupOptions: {
input: {
index: resolve(__dirname, 'src/cli/index.ts'),
fake: resolve(__dirname, 'src/cli/fake.ts'),
vsomeip: resolve(__dirname, 'src/main/vsomeip/worker.ts')
},
output: {
entryFileNames: (chunk) => {
if (chunk.name === 'vsomeip') return 'vsomeip.js'
if (chunk.name === 'index') return 'ecb_cli.js'
return chunk.name + '.js'
},
format: 'cjs',
dir: resolve(__dirname, 'cli/out/')
}
}
}
}
})
@@ -0,0 +1,11 @@
project_id_env: CROWDIN_PROJECT_ID
api_token_env: CROWDIN_PERSONAL_TOKEN
files:
- source: /docs/en/**/*.md
translation: /docs/%two_letters_code%/**/%original_file_name%
- source: /resources/examples/**/readme.md
translation: /resources/examples/**/readme.%two_letters_code%.md
- source: /.vitepress/en.json
translation: /.vitepress/%two_letters_code%.json
@@ -0,0 +1,3 @@
provider: generic
url: https://example.com/auto-updates
updaterCacheDirName: ecubus-pro-updater
@@ -0,0 +1,17 @@
{
"folders": [
{
"path": "."
},
{
"path": "../ecubus-plugin-template"
},
{
"path": "../ecubus-lang-zh"
}
],
"settings": {
"typescript.tsserver.log": "off",
"typescript.experimental.useTsgo": false
}
}
@@ -0,0 +1,67 @@
appId: EcuBus-Pro
productName: EcuBus-Pro
fileAssociations:
ext: ecb
description: EcuBus-Pro
directories:
buildResources: build
files:
- '!**/.vscode/*'
- '!src/*'
- '!electron.vite.config.{js,ts,mjs,cjs}'
- '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
- '!{.env,.env.*,.npmrc,pnpm-lock.yaml,package-lock.json}'
- '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
asarUnpack:
- resources/**
nodeGypRebuild: false
win:
target:
- target: nsis
arch:
- x64
publish:
provider: generic
url: ''
nsis:
oneClick: false
allowElevation: true
perMachine: false
license: license.txt
allowToChangeInstallationDirectory: true
installerIcon: ./build/icon.ico
uninstallerIcon: ./build/icon.ico
installerHeaderIcon: ./build/icon.ico
installerSidebar: ./build/sidebar.bmp
uninstallerSidebar: ./build/sidebar.bmp
createDesktopShortcut: true
createStartMenuShortcut: true
artifactName: EcuBus-Pro ${version}.exe
guid: 98123fde-012f-5ff3-8b50-881449dac91a
include: build/installer.nsh
mac:
target:
- target: dmg
icon: ./build/icon.icns
entitlements: build/entitlements.mac.plist
entitlementsInherit: build/entitlements.mac.plist
identity: null
artifactName: EcuBus-Pro-${version}-${arch}.dmg
linux:
target:
- target: deb
- target: rpm
icon: ./build/icon.icns
executableName: ecubuspro
description: EcuBus-Pro
category: Development
desktop:
entry:
Name: EcuBus-Pro
Comment: EcuBus-Pro
Terminal: false
Type: Application
Icon: ecubuspro
Categories: Development
maintainer: https://github.com/ecubus/EcuBus-Pro
@@ -0,0 +1,47 @@
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import { nodePolyfills } from 'vite-plugin-node-polyfills'
import ConditionalCompile from 'vite-plugin-conditional-compiler'
export default defineConfig({
main: {
resolve: {
alias: {
src: resolve(__dirname, 'src')
}
},
plugins: [externalizeDepsPlugin(), ConditionalCompile()],
build: {
rollupOptions: {
input: {
index: resolve(__dirname, 'src/main/index.ts'),
vsomeip: resolve(__dirname, 'src/main/vsomeip/worker.ts')
}
}
}
},
preload: {
plugins: [externalizeDepsPlugin()]
},
renderer: {
resolve: {
alias: {
src: resolve(__dirname, 'src'),
'@r': resolve('src/renderer/src'),
nodeCan: resolve(__dirname, 'src/main/share')
}
},
plugins: [
vue(),
vueJsx(),
nodePolyfills({
include: ['buffer'],
globals: {
Buffer: true
}
})
]
}
})
@@ -0,0 +1,11 @@
param(
[Parameter(Mandatory=$false)]
[string]$PackageName
)
Write-Host "Installing $PackageName"
./resources/python/python -m pip install --upgrade $PackageName --no-warn-script-location
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,205 @@
{
"name": "ecubuspro",
"version": "0.8.66",
"description": "EcuBus-Pro",
"main": "./out/main/index.js",
"author": "frankie.zengfu@gmail.com",
"homepage": "https://app.whyengineer.com",
"license": "Apache-2.0",
"ecubusPro": {
"vendor": {
"win32": [
"simulate",
"kvaser",
"peak",
"zlg",
"toomoss",
"vector",
"slcan",
"ecubus",
"candle"
],
"linux": [
"simulate",
"slcan"
],
"darwin": [
"simulate",
"slcan"
]
}
},
"lint-staged": {
"*.{ts,vue}": [
"prettier --write",
"eslint --ext .ts,.vue --fix"
]
},
"scripts": {
"format": "prettier --write .",
"lint": "eslint . --ext .ts,.vue --fix",
"typecheck:node": "tsgo --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false",
"typecheck": "npm run typecheck:node && npm run typecheck:web",
"worker": "cd src/main/worker/secureAccess && npx node-gyp rebuild && cd ../../../.. && npx webpack --config webpack.config.js --mode production",
"worker:js": "npx webpack --config webpack.config.js --mode production",
"start": "electron-vite preview",
"dev": "electron-vite dev",
"test": "vitest --config vitest.config.ts",
"build": "npm run typecheck && electron-vite build",
"build:sdk": "vite build --config vite.sdk.config.ts",
"cli:dev": "electron-vite dev -c cli.vite.ts -w --entry cli/out/ecb_cli2.js",
"cli:build": "electron-vite build -c cli.vite.ts",
"cli:build:win": "electron-vite build -c cli.vite.ts && cd cli && npm run win",
"cli:build:linux": "electron-vite build -c cli.vite.ts && cd cli && npm run linux",
"cli:build:mac": "electron-vite build -c cli.vite.ts && cd cli && npm run mac",
"postinstall": "electron-builder install-app-deps",
"build:unpack": "npm run build && electron-builder --dir",
"build:win": "npm run build && electron-builder --win",
"build:mac": "npm run build && electron-builder --mac",
"build:linux": "npm run build && electron-builder --linux",
"native": "npm run docan && npm run dolin && npm run someip",
"api": "typedoc --tsconfig tsconfig.worker.json --lang en-US",
"docan": "cd src/main/docan && npx node-gyp rebuild",
"dolin": "cd src/main/dolin && npx node-gyp rebuild",
"someip": "cd src/main/vsomeip && npx node-gyp rebuild",
"docs:dev": "vitepress dev",
"docs:build": "vitepress build",
"docs:preview": "vitepress preview",
"prepare": "husky"
},
"dependencies": {
"@electron-toolkit/preload": "^3.0.0",
"@electron-toolkit/utils": "^3.0.0",
"@element-plus/icons-vue": "^2.3.1",
"@form-create/element-ui": "^3.2.22",
"@iconify/icons-grommet-icons": "^1.2.5",
"@iconify/icons-mdi": "^1.2.48",
"@joint/core": "^4.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vueuse/core": "^10.7.2",
"@vxe-ui/plugin-render-element": "^4.0.10",
"@xterm/addon-canvas": "^0.7.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/xterm": "^5.5.0",
"adm-zip": "^0.5.16",
"ajv": "^8.18.0",
"animate.css": "^4.1.1",
"async": "^3.2.6",
"async-validator": "^4.2.5",
"axios": "^1.8.2",
"body-parser": "^2.2.1",
"chevrotain": "^12.0.0",
"codemirror": "^6.65.7",
"colors": "^1.4.0",
"commander": "^12.1.0",
"conf": "^15.1.0",
"dayjs": "^1.11.13",
"e-virt-table": "^1.2.35",
"echarts": "^6.0.0",
"electron-log": "^5.1.2",
"electron-updater": "^6.3.9",
"element-plus": "^2.11.0",
"emittery": "^1.2.0",
"events": "^3.3.0",
"exceljs": "^4.4.0",
"glob": "^11.1.0",
"handlebars": "^4.7.9",
"i18next": "^25.6.2",
"i18next-vue": "^5.3.0",
"iconv-lite": "^0.7.0",
"javascript-obfuscator": "^5.2.0",
"jquery": "^3.7.1",
"jquery-ui": "^1.14.1",
"js-beautify": "^1.15.4",
"json5": "^2.2.3",
"lodash": "^4.17.23",
"marked": "^14.1.3",
"mitt": "^3.0.1",
"path-browserify": "^1.0.1",
"pinia": "^3.0.2",
"pixi.js-legacy": "^5.3.3",
"python-shell": "^5.0.0",
"serialport": "^13.0.0",
"sortablejs": "^1.15.2",
"ts-morph": "^26.0.0",
"uuid": "^14.0.0",
"vite-plugin-string": "^1.2.3",
"vite-raw-plugin": "^1.0.2",
"vitepress-plugin-image-viewer": "^1.1.6",
"vue-grid-layout-v3": "^3.1.2",
"vue-router": "^4.2.5",
"vuedraggable": "4.1.0",
"vxe-pc-ui": "^4.2.53",
"vxe-table": "^4.16.11",
"winston": "^3.17.0",
"wujie-polyfill": "^1.1.3",
"wujie-vue3": "^1.0.29",
"xterm": "^5.3.0"
},
"devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.2",
"@electron-toolkit/eslint-config-ts": "^1.0.1",
"@electron-toolkit/tsconfig": "^1.0.1",
"@electron/rebuild": "^4.0.4",
"@iconify/icons-ep": "^1.2.12",
"@iconify/icons-material-symbols": "^1.2.58",
"@iconify/icons-ph": "^1.2.5",
"@iconify/vue": "^4.1.1",
"@liudonghua123/pkg": "^6.0.1",
"@rollup/plugin-node-resolve": "^15.3.0",
"@rushstack/eslint-patch": "^1.7.1",
"@types/adm-zip": "^0.5.7",
"@types/async": "^3.2.24",
"@types/node": "^22.9.1",
"@types/path-browserify": "^1.0.3",
"@types/uuid": "^10.0.0",
"@typescript/native-preview": "^7.0.0-dev.20260124.1",
"@vitejs/plugin-vue": "^5.0.3",
"@vue/eslint-config-prettier": "^9.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"ali-oss": "^6.21.0",
"dotenv": "^16.4.5",
"electron": "^39.8.6",
"electron-builder": "26.8.1",
"electron-vite": "^2.3.0",
"eslint": "^8.57.1",
"eslint-plugin-vue": "^9.32.0",
"husky": "^9.1.7",
"lint-staged": "^15.4.3",
"mermaid": "^11.12.3",
"node-addon-api": "^8.2.2",
"node-gyp": "^12.3.0",
"node-loader": "^2.1.0",
"prettier": "^3.5.2",
"rollup": "^4.59.0",
"rollup-plugin-dts": "^6.3.0",
"sass": "^1.83.0",
"ts-loader": "^9.5.4",
"typedoc": "^0.27.6",
"typescript": "^5.3.3",
"viewerjs": "^1.11.6",
"vite": "^5.4.21",
"vite-plugin-conditional-compiler": "^0.3.1",
"vite-plugin-dts": "^4.5.4",
"vite-plugin-node-polyfills": "^0.21.0",
"vitepress": "^2.0.0-alpha.16",
"vitepress-mermaid-renderer": "^1.1.11",
"vitest": "^3.2.4",
"vue": "^3.5.13",
"vue-tsc": "^2.1.6",
"webpack": "^5.105.0",
"webpack-cli": "^5.1.4",
"yaml": "^2.6.0"
},
"optionalDependencies": {
"@rollup/rollup-linux-x64-gnu": "4.46.2"
},
"overrides": {
"esbuild": "^0.25.0",
"axios": "^1.8.2",
"@babel/runtime": "^7.26.10",
"tar-fs": "2.1.4",
"node-gyp": "^12.3.0"
}
}
@@ -0,0 +1,199 @@
import { app, ipcMain, net } from 'electron'
import { exec } from 'child_process'
import { readFile } from 'fs'
import { hostname, release } from 'os'
import log from 'electron-log'
export type AptabaseOptions = {
host?: string
}
const SDK_VERSION = 'ecubus-analytics@1.0.0'
const _hosts: Record<string, string> = {
US: 'https://us.aptabase.com',
EU: 'https://eu.aptabase.com',
DEV: 'http://localhost:3000',
SH: ''
}
type EnvironmentInfo = {
appVersion: string
isDebug: boolean
locale: string
osName: string
osVersion: string
engineName: string
engineVersion: string
sdkVersion: string
}
const _sessionId = newSessionId()
let _appKey = ''
let _apiUrl = ''
let _env: EnvironmentInfo | undefined
function newSessionId(): string {
return (process.env.COMPUTERNAME ?? hostname()).trim()
}
async function getOsVersion(): Promise<[string, string]> {
switch (process.platform) {
case 'win32':
return ['Windows', release()]
case 'darwin':
try {
const v = await new Promise<string>((resolve, reject) => {
exec('/usr/bin/sw_vers -productVersion', (err, stdout) => {
if (err) reject(err)
else resolve(stdout.trim())
})
})
return ['macOS', v]
} catch {
return ['macOS', '']
}
default: {
try {
const text = await new Promise<string>((resolve, reject) => {
readFile('/etc/os-release', 'utf8', (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
const lines = text.split('\n')
const map: Record<string, string> = {}
for (const line of lines) {
const [k, ...rest] = line.split('=')
if (k && rest.length) {
map[k] = rest.join('=').replace(/"/g, '')
}
}
const name = map.NAME ?? 'Linux'
const ver = map.VERSION_ID ?? ''
return [name, ver]
} catch {
return ['Linux', '']
}
}
}
}
async function getEnvironmentInfo(): Promise<EnvironmentInfo> {
const [osName, osVersion] = await getOsVersion()
return {
appVersion: app.getVersion(),
isDebug: !app.isPackaged,
locale: app.getLocale(),
osName,
osVersion,
engineName: 'Chromium',
engineVersion: process.versions.chrome ?? '',
sdkVersion: SDK_VERSION
}
}
function getBaseUrl(region: string, options?: AptabaseOptions): string | undefined {
if (region === 'SH') {
if (!options?.host) {
log.warn('Aptabase: Host parameter must be defined when using Self-Hosted App Key.')
return undefined
}
return options.host
}
return _hosts[region]
}
function registerEventHandler(): void {
ipcMain.on(
'aptabase-track-event',
(
_event,
payload: {
eventName?: string
name?: string
props?: Record<string, string | number | boolean>
properties?: Record<string, string | number | boolean>
}
) => {
const eventName = payload.eventName ?? payload.name
if (!eventName) return
const props = payload.props ?? payload.properties
void trackEvent(eventName, props)
}
)
}
export async function initialize(appKey: string, options?: AptabaseOptions): Promise<void> {
const parts = appKey.split('-')
if (parts.length !== 3 || _hosts[parts[1]] === undefined) {
log.warn(`Analytics: App Key "${appKey}" is invalid. Tracking will be disabled.`)
return
}
const baseUrl = getBaseUrl(parts[1], options)
if (!baseUrl) return
_apiUrl = `${baseUrl}/api/v0/event`
_env = await getEnvironmentInfo()
_appKey = appKey
registerEventHandler()
}
export function trackEvent(
eventName: string,
props?: Record<string, string | number | boolean>
): Promise<void> {
if (!_appKey || !_env) {
return Promise.resolve()
}
const now = new Date()
const body = {
timestamp: now.toISOString(),
sessionId: _sessionId,
eventName,
systemProps: {
isDebug: _env.isDebug,
locale: _env.locale,
osName: _env.osName,
osVersion: _env.osVersion,
engineName: _env.engineName,
engineVersion: _env.engineVersion,
appVersion: _env.appVersion,
sdkVersion: _env.sdkVersion
},
props
}
return new Promise((resolve) => {
const onReject = (err: Error) => {
log.error('Analytics: Failed to send event', err)
resolve()
}
const req = net.request({
method: 'POST',
url: _apiUrl,
credentials: 'omit'
})
req.setHeader('Content-Type', 'application/json')
req.setHeader('App-Key', _appKey)
req.on('error', onReject)
req.on('response', (res) => {
if (res.statusCode && res.statusCode >= 300) {
log.warn(
`Analytics: Failed to send event "${eventName}": ${res.statusCode} ${res.statusMessage}`
)
}
resolve()
})
req.write(JSON.stringify(body))
req.end()
})
}
@@ -0,0 +1,121 @@
import { CanDB } from './share/can'
import { execBinary } from './util'
import { getPythonPath } from './python'
import fsP from 'fs/promises'
import path from 'path'
import iconv from 'iconv-lite'
/** Common encodings for DBC comments/units (Chinese etc.). Tried in order when encoding is not specified. */
const DEFAULT_ENCODINGS = ['gbk', 'gb2312', 'gb18030', 'big5', 'cp936'] as const
/**
* Fix strings that were encoded (e.g. GBK/Big5) but misinterpreted as Latin-1 during DBC parse.
* Each byte (0x80-0xFF) becomes U+00XX; convert back to bytes and decode with given encoding(s).
* Only applies when all chars are in 0-255 (indicating misinterpreted bytes).
*/
function fixMisencodedString(
str: string,
encodings: readonly string[] = DEFAULT_ENCODINGS
): string {
if (typeof str !== 'string' || str.length === 0) return str
const chars = [...str]
const allSingleByte = chars.every((c) => (c.codePointAt(0) ?? 0) <= 0xff)
const hasHighBytes = chars.some((c) => {
const cp = c.codePointAt(0) ?? 0
return cp >= 0x80 && cp <= 0xff
})
if (!allSingleByte || !hasHighBytes) return str
const bytes = Buffer.from(chars.map((c) => c.charCodeAt(0) & 0xff))
for (const enc of encodings) {
try {
const decoded = iconv.decode(bytes, enc)
if (!decoded.includes('�')) return decoded
} catch {
/* try next */
}
}
return str
}
function fixMisencodedInValue(val: unknown, encodings: readonly string[]): unknown {
if (typeof val === 'string') return fixMisencodedString(val, encodings)
if (Array.isArray(val)) return val.map((v) => fixMisencodedInValue(v, encodings))
if (val !== null && typeof val === 'object') {
const out: Record<string, unknown> = {}
for (const [k, v] of Object.entries(val)) out[k] = fixMisencodedInValue(v, encodings)
return out
}
return val
}
export interface ParseFileOptions {
/** Encoding(s) to try when fixing misinterpreted strings (e.g. gbk, big5). If omitted, tries gbk, gb2312, gb18030, big5, cp936. */
encoding?: string | string[]
}
export async function parseFile(
filePath: string,
outputJsonPath: string,
options?: ParseFileOptions
): Promise<{ data: CanDB; msg: string }> {
const pythonPath = getPythonPath()
const result = await execBinary(pythonPath, [
'-m',
'canmatrix.cli.convert',
filePath,
outputJsonPath,
'--jsonExportAll'
])
if (result.success) {
if (result.stdout.toLowerCase().includes('error')) {
throw new Error(result.stdout)
}
if (result.stderr.toLowerCase().includes('error')) {
throw new Error(result.stderr)
}
const json = await fsP.readFile(outputJsonPath, 'utf-8')
const encodings =
options?.encoding === undefined
? DEFAULT_ENCODINGS
: Array.isArray(options.encoding)
? options.encoding
: [options.encoding]
const data = fixMisencodedInValue(JSON.parse(json), encodings) as CanDB
return {
data,
msg: result.stdout
}
} else {
throw new Error(result.stderr)
}
}
export async function exportOtherFile(
tmpDir: string,
fileType: string,
candb: CanDB,
outputFilePath: string
): Promise<void> {
//covert candb to json firstly, then conver the json file to the other file
const jsonFilePath = path.join(tmpDir, 'canmatrix.json')
await fsP.writeFile(jsonFilePath, JSON.stringify(candb, null, 2))
const pythonPath = getPythonPath()
// Omit -f to infer format from output path extension; avoids KeyError when a
// format module failed to load (e.g. arxml when lxml is missing)
const result = await execBinary(pythonPath, [
'-m',
'canmatrix.cli.convert',
jsonFilePath,
outputFilePath
])
if (result.success) {
if (result.stdout.toLowerCase().includes('error')) {
throw new Error(result.stdout)
}
if (result.stderr.toLowerCase().includes('error')) {
throw new Error(result.stderr)
}
} else {
throw new Error(result.stderr)
}
}
@@ -0,0 +1,40 @@
/* eslint-disable no-var */
import type { Logger } from 'winston'
import type { EventEmitter } from 'events'
import { LDF } from 'src/renderer/src/database/ldfParse'
import { DBC } from 'src/renderer/src/database/dbc/dbcVisitor'
import { VarItem } from 'src/preload/data'
import { BrowserWindow } from 'electron'
import { IntervalHistogram } from 'node:perf_hooks'
import type { DataSet } from 'src/preload/data'
type VarUpdateItem = {
name: string
value: number | string | number[]
id: string
uuid?: string
}
type VarEvent = {
update: [VarUpdateItem | VarUpdateItem[]]
}
declare global {
var sysLog: Logger
var scriptLog: Logger
var keyEvent: EventEmitter | undefined
var varEvent: EventEmitter<VarEvent> | undefined
var dataSet: DataSet
var startTs: number
var vars: Record<string, VarItem>
var deviceIndexMap: Map<string, number>
var mainWindow: BrowserWindow
var toomossDeviceHandles:
| Map<
number,
{
refCount: number // 引用计数
channels: Set<number> // 当前使用的通道
}
>
| undefined
}
@@ -0,0 +1,209 @@
import i18next from 'i18next'
import { dirname, join, relative } from 'path'
import fs from 'fs/promises'
import { glob } from 'glob'
import localesPath from '../../resources/locales/.gitkeep?asset&asarUnpack'
import log from 'electron-log'
import { getPluginsDirectory } from './ipc/plugin'
// 缓存
const translationsCache = new Map<string, Record<string, any>>()
let supportedLanguagesCache: string[] | null = null
// 获取主应用 locales 目录路径
export const getAppLocalesPath = () => {
// localesPath 会指向 resources/locales/en/translation.json
// 我们需要返回 resources/locales 目录
const localesDir = dirname(localesPath)
return localesDir
}
// 使用 glob 加载指定语言的所有翻译文件并合并为一个大的 JSON
async function loadAllTranslations(lng: string): Promise<Record<string, any>> {
const merged: Record<string, any> = {}
const appLocalesPath = getAppLocalesPath()
try {
// 加载主应用翻译:locales/{lng}/translation.json
const appPattern = join(appLocalesPath, lng, 'translation.json').replace(/\\/g, '/')
const appFiles = await glob(appPattern)
for (const file of appFiles) {
try {
const content = await fs.readFile(file, 'utf-8')
const translations = JSON.parse(content)
Object.assign(merged, translations)
} catch (error) {
log.error(`Failed to load app translation from ${file}:`, error)
}
}
// 加载插件翻译:plugins/*/locales/{lng}/translation.json
const pluginsDir = getPluginsDirectory()
const pluginPattern = join(pluginsDir, '*', 'locales', lng, 'translation.json').replace(
/\\/g,
'/'
)
const pluginFiles = await glob(pluginPattern)
for (const file of pluginFiles) {
try {
const content = await fs.readFile(file, 'utf-8')
const translations = JSON.parse(content)
// 插件翻译直接覆盖主应用翻译
Object.assign(merged, translations)
} catch (error) {
log.error(`Failed to load plugin translation from ${file}:`, error)
}
}
} catch (error) {
log.error(`Failed to load translations for ${lng}:`, error)
}
return merged
}
// 从 i18next 获取已加载的翻译(如果已初始化)
export function getAllTranslationsFromI18next(lng: string): Record<string, any> | null {
if (!i18next.isInitialized) {
return null
}
// 使用 hasResourceBundle 检查该语言是否已加载
if (!i18next.hasResourceBundle(lng, 'translation')) {
return null
}
// 获取 translation 命名空间的翻译(插件翻译已合并到其中)
const bundle = i18next.getResourceBundle(lng, 'translation')
return bundle || null
}
// 获取所有翻译 - 供 IPC 使用
// 优先从 i18next 获取,如果未初始化则读取文件
export async function getAllTranslations(lng: string): Promise<Record<string, any>> {
// 优先从已加载的 i18next 获取
const cachedTranslations = getAllTranslationsFromI18next(lng)
if (cachedTranslations) {
return cachedTranslations
}
// 检查缓存
if (translationsCache.has(lng)) {
return translationsCache.get(lng)!
}
// 如果 i18next 未初始化或该语言未加载,则使用 glob 读取文件
const translations = await loadAllTranslations(lng)
translationsCache.set(lng, translations)
return translations
}
export const initMainI18n = async (lng: string = 'en') => {
// 使用 glob 加载所有翻译并合并为一个大的 JSON
const mergedTranslations = await loadAllTranslations(lng)
// 更新缓存
translationsCache.set(lng, mergedTranslations)
// 构建资源对象
const resources = {
[lng]: {
translation: mergedTranslations
}
}
await i18next.init({
lng,
fallbackLng: 'en',
resources,
ns: ['translation'],
defaultNS: 'translation',
debug: false,
interpolation: {
escapeValue: false
}
})
return i18next
}
// 重新加载翻译(用于语言切换或插件动态加载)
export const reloadTranslations = async (lng: string) => {
// 使用 glob 加载所有翻译并合并为一个大的 JSON
const mergedTranslations = await loadAllTranslations(lng)
// 更新缓存
translationsCache.set(lng, mergedTranslations)
// 添加或更新资源
i18next.addResourceBundle(lng, 'translation', mergedTranslations, true, true)
await i18next.changeLanguage(lng)
}
// 清除翻译缓存(用于插件加载/卸载后)
export function clearTranslationsCache(lng?: string) {
if (lng) {
translationsCache.delete(lng)
} else {
translationsCache.clear()
}
}
// 清除支持语言缓存
export function clearSupportedLanguagesCache() {
supportedLanguagesCache = null
}
// 获取所有支持的语言(使用 glob 从主应用和插件中扫描)
export async function getAllSupportedLanguages(): Promise<string[]> {
// 检查缓存
if (supportedLanguagesCache) {
return supportedLanguagesCache
}
const languageSet = new Set<string>()
const appLocalesPath = getAppLocalesPath()
try {
// 扫描主应用支持的语言:locales/*/translation.json
const appPattern = join(appLocalesPath, '*', 'translation.json').replace(/\\/g, '/')
const appFiles = await glob(appPattern)
for (const file of appFiles) {
const relativePath = relative(appLocalesPath, file)
const lng = relativePath.split(/[/\\]/)[0]
languageSet.add(lng)
}
// 扫描插件支持的语言:plugins/*/locales/*/translation.json
const pluginsDir = getPluginsDirectory()
const pluginPattern = join(pluginsDir, '*', 'locales', '*', 'translation.json').replace(
/\\/g,
'/'
)
const pluginFiles = await glob(pluginPattern)
for (const file of pluginFiles) {
const relativePath = relative(pluginsDir, file)
const parts = relativePath.split(/[/\\]/)
const lng = parts[parts.length - 2] // locales 目录下的子目录名
languageSet.add(lng)
}
} catch (error) {
log.error('Failed to scan supported languages:', error)
}
// 返回排序后的语言列表(确保 en 在前)
const languages = Array.from(languageSet).sort((a, b) => {
if (a === 'en') return -1
if (b === 'en') return 1
return a.localeCompare(b)
})
// 更新缓存
supportedLanguagesCache = languages
return languages
}
@@ -0,0 +1,265 @@
import { app, shell, BrowserWindow, ipcMain, dialog, protocol as eProtocol, net } from 'electron'
import path, { join } from 'path'
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
import icon from '../../resources/icon.png?asset'
import { store } from './store'
import './ipc'
import log from 'electron-log/main'
import { initialize as initAnalytics, trackEvent } from './analytics'
import { createLogs } from './log'
import './update'
import { globalStop } from './ipc/uds'
import { startRpcHost } from './rpcHost'
import Transport from 'winston-transport'
import { initMainI18n } from './i18n'
import { setupCasdoor } from './ipc/casdoor'
import 'src/renderer/src/helper'
import { closeAllWindows, closeWindow, logQ, maximizeWindow, minimizeWindow } from './multiWin'
initAnalytics('A-EU-6409047217')
log.initialize()
// Track app exit once (user closes window / app quits).
let exitTracked = false
app.once('before-quit', async () => {
if (exitTracked) return
exitTracked = true
try {
// Small timeout so we don't block quitting too long.
await Promise.race([
trackEvent('app_exit'),
new Promise<void>((resolve) => setTimeout(resolve, 1500))
])
} catch {
// Ignore tracking errors on shutdown.
}
})
// Register custom protocol as privileged before app is ready
eProtocol.registerSchemesAsPrivileged([
{
scheme: 'local-resource',
privileges: {
secure: true,
supportFetchAPI: true,
bypassCSP: true,
corsEnabled: true
}
}
])
setupCasdoor()
log.info(app.getGPUFeatureStatus())
function registerLocalResourceProtocol() {
eProtocol.handle('local-resource', (request) => {
try {
// Remove protocol prefix (handle both // and /// after protocol)
const url = request.url.replace(/^local-resource:\/\/\/?/, '')
// Decode URL components to handle encoded characters
const decodedUrl = decodeURIComponent(url)
// Normalize path (handle both forward and back slashes)
const normalizedPath = decodedUrl.replace(/\\/g, '/')
// For Windows absolute paths (e.g., D:/path), ensure proper file:/// format
const fileUrl = /^[a-zA-Z]:\//.test(normalizedPath)
? `file:///${normalizedPath}`
: `file://${normalizedPath}`
if (fileUrl.endsWith('.map')) {
//404
return new Response(null, { status: 404 })
}
return net.fetch(fileUrl)
} catch (error) {
log.error('ERROR: registerLocalResourceProtocol:', error)
return new Response(null, { status: 404 })
}
})
}
// process.env.PYTHON_PATH=pythonPath
const isDev = process.env.NODE_ENV === 'development'
ipcMain.on('electron-store-get', async (event, val) => {
event.returnValue = store.get(val)
})
ipcMain.on('electron-store-set', async (event, key, val) => {
store.set(key, val)
})
class ElectronLog extends Transport {
constructor(
private q: typeof logQ,
opts?: Transport.TransportStreamOptions
) {
super(opts)
}
log(info: any, callback: () => void) {
if (!info.message.method) {
info.message = {
method: 'ipc-log-main',
message: info.message
}
}
this.q.list.push(info)
callback()
}
}
function createWindow(): void {
// Get stored window bounds and state
const windowBounds = store.get('windowBounds') as Electron.Rectangle
const isMaximized = store.get('windowMaximized', false)
function getBounds() {
const bounds = global.mainWindow.getBounds()
// bounds.x += 5
// bounds.y += 5
return bounds
}
// Create the browser window.
const mainWindow = new BrowserWindow({
minWidth: 1000,
minHeight: 600,
width: 1000,
height: 600,
frame: false,
show: false,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
backgroundThrottling: false,
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
contextIsolation: true
}
})
if (windowBounds) {
mainWindow.setBounds(windowBounds)
}
global.mainWindow = mainWindow
logQ.addWin(mainWindow, true)
createLogs(
[
() =>
new ElectronLog(logQ, {
level: 'debug'
})
],
[]
)
ipcMain.on('minimize', (event, id) => {
if (id) {
minimizeWindow(id)
} else {
mainWindow?.minimize()
}
})
ipcMain.on('maximize', (event, id) => {
if (id) {
maximizeWindow(id)
} else {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize()
store.set('windowMaximized', false)
} else {
mainWindow.maximize()
store.set('windowMaximized', true)
}
// Save current bounds before maximizing
store.set('windowBounds', getBounds())
}
})
ipcMain.on('close', (event, id) => {
if (id) {
closeWindow(id)
} else {
logQ.stopTimer()
globalStop()
// Only save bounds if window is not maximized
store.set('windowBounds', getBounds())
store.set('windowMaximized', mainWindow.isMaximized())
closeAllWindows()
mainWindow.close()
}
})
mainWindow.on('ready-to-show', () => {
mainWindow.show()
// Restore maximized state
if (isMaximized) {
mainWindow.maximize()
}
if (isDev) {
mainWindow.webContents.openDevTools()
}
})
mainWindow.webContents.setWindowOpenHandler((details) => {
shell.openExternal(details.url)
return { action: 'deny' }
})
// HMR for renderer base on electron-vite cli.
// Load the remote URL for development or the local html file for production.
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
} else {
mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
}
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(async () => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window)
})
registerLocalResourceProtocol()
// 初始化主进程 i18n
try {
const savedLang = store.get('language', 'en') as string
await initMainI18n(savedLang)
log.info(`Main process i18n initialized with language: ${savedLang}`)
} catch (error) {
log.error('Failed to initialize main process i18n:', error)
}
createWindow()
void startRpcHost()
trackEvent('app_open')
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
// In this file you can include the rest of your app"s specific main process
// code. You can also put them in separate files and require them here.
@@ -0,0 +1,934 @@
/* eslint-disable no-var */
import { transport, createLogger, format, Logger, transports } from 'winston'
import type { Format } from 'logform'
import Transport from 'winston-transport'
import { CAN_ERROR_ID, CanAddr, CanMessage, CanMsgType, getTsUs } from './share/can'
import EventEmitter from 'events'
import type { Sequence, ServiceItem } from './share/uds'
import { PayloadType } from './doip'
import type { LinMsg } from './share/lin'
import type { SerialMessage } from './share/serial'
import type { TestEvent } from 'node:test/reporters'
import { setVar as setVarMain, setVarByKey, getVar as getVarMain } from './var'
import { VarItem } from 'src/preload/data'
import { v4 } from 'uuid'
import type { SomeipMessage, VsomeipAvailabilityInfo } from './share/someip'
import type { OsEvent } from './share/osEvent'
import path from 'path'
import dayjs from 'dayjs'
global.deviceIndexMap = new Map<string, number>()
type LogFunc = (...args: any[]) => Transport
export function createLogs(logs: LogFunc[], formats: Format[]) {
global.sysLog = createLogger({
transports: logs.map((t) => t()),
format: format.combine(format.json(), format.label({ label: 'System' }), ...formats)
})
global.scriptLog = createLogger({
transports: logs.map((t) => t()),
format: format.combine(format.json(), format.label({ label: 'Script' }), ...formats)
})
for (const l of logs) {
addTransport(l)
}
for (const f of formats) {
addFormat(f)
}
}
class Base extends Transport {
constructor(opts?: Transport.TransportStreamOptions) {
super(opts)
//
// Consume any custom options here. e.g.:
// - Connection information for databases
// - Authentication information for APIs (e.g. loggly, papertrail,
// logentries, etc.).
//
}
log(info: any, callback: () => void) {
if (process.env.VITEST) {
console.table(info.message)
}
// Perform the writing to the remote service
callback()
}
}
const instanceFormat = format((info, opts: any) => {
info.instance = opts.instance
return info
})
const externalTransport: { id: string; t: () => Transport }[] = []
const deviceTransport: { id: string; devices: string[]; logger: Logger }[] = []
export function addTransport(t: () => Transport): string {
const id = v4()
externalTransport.push({ id, t })
return id
}
export function addDeviceTransport(t: () => Transport): string {
const id = v4()
const transport = t()
const devices = (transport as Transport & { devices?: string[] }).devices ?? []
const logger = createLogger({ transports: [transport] })
deviceTransport.push({ id, devices, logger })
return id
}
export function removeDeviceTransport(id: string) {
const index = deviceTransport.findIndex((t) => t.id == id)
if (index != -1) {
const [target] = deviceTransport.splice(index, 1)
target.logger.close()
}
}
class DeviceTransportRouter extends Transport {
constructor() {
super({ level: 'debug' })
}
log(info: any, callback: () => void) {
const message = typeof info.message === 'object' ? info.message : undefined
const deviceId = message?.deviceId
if (deviceId) {
for (const target of deviceTransport) {
if (target.devices.includes(deviceId)) {
target.logger.log({ level: info.level, message })
}
}
}
callback()
}
}
function getDeviceTransportRouters(): Transport[] {
return deviceTransport.length > 0 ? [new DeviceTransportRouter()] : []
}
export function removeTransport(id: string) {
const index = externalTransport.findIndex((t) => t.id == id)
if (index != -1) {
externalTransport.splice(index, 1)
}
}
const externalFormat: Format[] = []
export function addFormat(f: Format) {
externalFormat.push(f)
}
export function clearFormat() {
externalFormat.splice(0, externalFormat.length)
}
export class CanLOG {
vendor: string
log: Logger
deviceId: string
constructor(
vendor: string,
instance: string,
deviceId: string,
private event: EventEmitter
) {
this.deviceId = deviceId
this.vendor = vendor
const et1 = externalTransport.map((t) => t.t())
const dt1 = getDeviceTransportRouters()
this.log = createLogger({
transports: [new Base(), ...et1, ...dt1],
format: format.combine(
format.json(),
instanceFormat({ instance: instance }),
format.label({ label: `Can-${vendor}` }),
...externalFormat
)
})
}
close() {
this.log.close()
this.event.removeAllListeners()
}
canBase(data: CanMessage) {
this.log.debug({
method: 'canBase',
deviceId: this.deviceId,
data
})
this.event.emit('can-frame', data)
}
setOption(cmd: string, val: any) {
this.log.info({
method: 'setOption',
deviceId: this.deviceId,
data: { cmd, val }
})
}
error(ts: number, msg?: string) {
this.log.error({
method: 'canError',
deviceId: this.deviceId,
data: {
ts: ts,
msg: msg
}
})
}
}
export class UdsLOG {
log: Logger
methodPrefix: string = ''
startTime = Date.now()
constructor(name: string, instance?: string) {
const et = externalTransport.map((t) => t.t())
const formatList = [format.json(), format.label({ label: name })]
if (instance) {
formatList.push(instanceFormat({ instance: instance }))
}
this.log = createLogger({
transports: [new Base(), ...et],
format: format.combine(...formatList, ...externalFormat)
})
}
addTransport(t: Transport) {
this.log.add(t)
}
removeTransport(t: Transport) {
this.log.remove(t)
}
sent(testerid: string, service: ServiceItem, ts: number, recvData?: Buffer, msg?: string) {
this.log.info({
method: this.methodPrefix + 'udsSent',
id: testerid,
data: {
service,
ts,
recvData,
msg
}
})
}
recv(testerid: string, service: ServiceItem, ts: number, recvData?: Buffer, msg?: string) {
this.log.info({
method: this.methodPrefix + 'udsRecv',
id: testerid,
data: {
service,
ts,
recvData,
msg
}
})
}
warning(
testerid: string,
service: ServiceItem,
sequence: Sequence,
seqIndex: number,
index: number,
ts: number,
recvData?: Buffer,
msg?: string
) {
this.log.warn({
method: this.methodPrefix + 'udsWarning',
id: testerid,
data: {
service,
sequence,
index,
seqIndex,
ts,
recvData,
msg
}
})
}
addMethodPrefix(prefix: string) {
this.methodPrefix = prefix
}
scriptMsg(msg: string, ts: number, level: 'info' | 'warn' | 'error' = 'info') {
this.log[level]({
method: this.methodPrefix + 'udsScript',
data: {
msg,
ts
}
})
}
systemMsg(msg: string, ts: number, level: 'info' | 'warn' | 'error' = 'info') {
this.log[level]({
method: this.methodPrefix + 'udsSystem',
data: {
msg,
ts
}
})
}
error(testerid: string, msg: string, ts: number, recvData?: Buffer) {
this.log.error({
method: this.methodPrefix + 'udsError',
id: testerid,
data: {
msg,
ts,
recvData
}
})
}
udsIndex(
testerid: string,
index: number,
serviceName: string,
action: 'start' | 'finished' | 'progress',
percent?: number
) {
const l = action == 'start' ? 'debug' : 'info'
this.log[l]({
method: this.methodPrefix + 'udsIndex',
id: testerid,
data: {
serviceName,
index,
action,
percent
}
})
}
close() {
this.log.close()
}
testInfo(id: string | undefined, event: TestEvent, msg?: string) {
this.log.info({
method: 'testInfo',
id,
data: event,
msg
})
}
}
export class DoipLOG {
vendor: string
log: Logger
deviceId: string
constructor(
vendor: string,
instance: string,
deviceId: string,
private event: EventEmitter,
private ts: number
) {
this.vendor = vendor
this.deviceId = deviceId
const et1 = externalTransport.map((t) => t.t())
const dt1 = getDeviceTransportRouters()
this.log = createLogger({
transports: [new Base(), ...et1, ...dt1],
format: format.combine(
format.json(),
instanceFormat({ instance: instance }),
format.label({ label: `IP-${vendor}` }),
...externalFormat
)
})
}
close() {
this.log.close()
this.event.removeAllListeners()
}
ipBase(
type: 'tcp' | 'udp',
dir: 'OUT' | 'IN',
local: { address?: string; port?: number },
remote: { address?: string; port?: number },
data: Buffer
) {
const ts = getTsUs() - this.ts
if (data.length < 2) {
this.error(ts, `error data lenght, data: ${data.toString('hex')}`)
return ts
}
const payloadType = data.readUint16BE(2)
let name = ''
switch (payloadType) {
case PayloadType.DoIP_HeaderNegativeAcknowledge:
name = 'Generic DoIP header negative acknowledge'
break
case PayloadType.DoIP_VehicleIdentificationRequest:
name = 'Vehicle identification request message'
break
case PayloadType.DoIP_VehicleIdentificationRequestWithVIN:
name = 'Vehicle identification request message with VIN'
break
case PayloadType.DoIP_VehicleIdentificationRequestWithEID:
name = 'Vehicle identification request message with EID'
break
case PayloadType.DoIP_VehicleAnnouncementResponse:
name = 'Vehicle announcement message/vehicle identification response message'
break
case PayloadType.DoIP_RouteActivationRequest:
name = 'Routing activation request'
break
case PayloadType.DoIP_RouteActivationResponse:
name = 'Routing activation response'
break
case PayloadType.DoIP_AliveRequest:
name = 'Alive check request'
break
case PayloadType.DoIP_AliveResponse:
name = 'Alive check response'
break
case PayloadType.DoIP_EntityStateRequest:
name = 'DoIP entity status request'
break
case PayloadType.DoIP_EntityStateResponse:
name = 'DoIP entity status response'
break
case PayloadType.DoIP_PowerModeInfoRequest:
name = 'Diagnostic power mode information request'
break
case PayloadType.DoIP_PowerModeInfoResponse:
name = 'Diagnostic power mode information response'
break
case PayloadType.DoIP_DiagnosticMessage:
name = 'Diagnostic message'
break
case PayloadType.DoIP_DiagnosticMessagePositiveAcknowledge:
name = 'Diagnostic message positive acknowledgement'
break
case PayloadType.DoIP_DiagnosticMessageNegativeAcknowledge:
name = 'Diagnostic message negative acknowledgement'
break
}
const val = {
dir,
type,
local: `${local.address}:${local.port}`,
remote: `${remote.address}:${remote.port}`,
data,
ts: ts,
name: name
}
this.log.info({
method: 'ipBase',
deviceId: this.deviceId,
data: val
})
// this.event.emit('ip-frame', val)
return ts
}
error(ts: number, msg?: string) {
this.log.error({
method: 'ipError',
deviceId: this.deviceId,
data: {
ts: ts,
msg: msg
}
})
}
}
export class LinLOG {
vendor: string
log: Logger
deviceId: string
constructor(
vendor: string,
instance: string,
deviceId: string,
private event: EventEmitter
) {
this.vendor = vendor
this.deviceId = deviceId
const et1 = externalTransport.map((t) => t.t())
const dt1 = getDeviceTransportRouters()
this.log = createLogger({
transports: [new Base(), ...et1, ...dt1],
format: format.combine(
format.json(),
instanceFormat({ instance: instance }),
format.label({ label: `Lin-${vendor}` }),
...externalFormat
)
})
}
close() {
this.log.close()
this.event.removeAllListeners()
}
linBase(data: LinMsg) {
this.log.debug({
method: 'linBase',
data,
deviceId: this.deviceId
})
this.event.emit('lin-frame', data)
}
sendEvent(msg: string, ts: number) {
this.log.info({
method: 'linEvent',
data: {
msg,
ts
},
deviceId: this.deviceId
})
}
error(ts: number, msg?: string, data?: LinMsg) {
this.log.error({
method: 'linError',
data: {
ts,
msg,
data
},
deviceId: this.deviceId
})
}
}
export class SerialLOG {
vendor: string
log: Logger
deviceId: string
constructor(
vendor: string,
instance: string,
deviceId: string,
private event: EventEmitter
) {
this.vendor = vendor
this.deviceId = deviceId
const et1 = externalTransport.map((t) => t.t())
const dt1 = getDeviceTransportRouters()
this.log = createLogger({
transports: [new Base(), ...et1, ...dt1],
format: format.combine(
format.json(),
instanceFormat({ instance: instance }),
format.label({ label: `Serial-${vendor}` }),
...externalFormat
)
})
}
close() {
this.log.close()
this.event.removeAllListeners()
}
serialBase(data: SerialMessage) {
this.log.debug({
method: 'serialBase',
data,
deviceId: this.deviceId
})
this.event.emit('serial-frame', data)
}
error(ts: number, msg?: string) {
this.log.error({
method: 'serialError',
data: {
ts,
msg
},
deviceId: this.deviceId
})
}
}
export class VarLOG {
log: Logger
id?: string
constructor(id?: string) {
this.id = id
const et1 = externalTransport.map((t) => t.t())
this.log = createLogger({
transports: [new Base(), ...et1],
format: format.combine(format.json(), ...externalFormat)
})
}
setVarByKey(key: string, value: number | string | number[], ts: number) {
const { found, target } = setVarByKey(key, value)
if (found && target) {
this.log.info({
method: 'setVar',
data: [{ name: target.name, value, id: target.id, uuid: this.id }],
ts
})
globalThis.varEvent?.emit('update', {
name: target.name,
value,
id: target.id,
uuid: this.id
})
}
}
setVarByKeyBatch(data: { key: string; value: number | string | number[] }[], ts: number) {
const founds: { index: number; var: VarItem }[] = []
for (const [index, item] of data.entries()) {
const found = setVarByKey(item.key, item.value)
if (found) {
founds.push({
index,
var: found.target
})
}
}
if (founds.length > 0) {
this.log.info({
method: 'setVar',
data: founds.map((f) => ({
index: f.index,
name: f.var.name,
value: data[f.index].value,
id: f.var.id,
uuid: this.id
})),
ts
})
globalThis.varEvent?.emit(
'update',
founds.map((f) => ({
name: f.var.name,
value: data[f.index].value,
id: f.var.id,
uuid: this.id
}))
)
}
}
setVar(name: string, value: number | string | number[], ts: number) {
const { found, target } = setVarMain(name, value)
if (found && target) {
this.log.info({
method: 'setVar',
data: [{ name: target.name, value, id: target.id, uuid: this.id }],
ts
})
globalThis.varEvent?.emit('update', {
name: target.name,
value,
id: target.id,
uuid: this.id
})
}
}
getVar(name: string): number | string | number[] {
return getVarMain(name)
}
close() {
this.log.close()
}
}
export class SomeipLOG {
vendor: string
log: Logger
deviceId: string
constructor(
vendor: string,
instance: string,
deviceId: string,
private event: EventEmitter,
private applicationId?: number
) {
this.vendor = vendor
this.deviceId = deviceId
const et1 = externalTransport.map((t) => t.t())
const dt1 = getDeviceTransportRouters()
this.log = createLogger({
transports: [new Base(), ...et1, ...dt1],
format: format.combine(
format.json(),
instanceFormat({ instance: instance }),
format.label({ label: `${vendor}` }),
...externalFormat
)
})
}
close() {
this.log.close()
this.event.removeAllListeners()
}
someipBase(header: Buffer, data: Buffer, ts: number) {
try {
this.log.info({
method: 'someipBase',
deviceId: this.deviceId,
data: {
header,
data,
ts
}
})
} catch (e: any) {
this.log.error({
method: 'someipError',
deviceId: this.deviceId,
data: {
ts: ts,
error: e.toString()
}
})
}
}
someipMessage(message: SomeipMessage, sending: boolean, ts: number) {
const resolvedSending =
typeof (message as any).sending === 'boolean' ? (message as any).sending : sending === true
// if (
// this.applicationId !== undefined &&
// Number.isFinite(this.applicationId) &&
// !resolvedSending &&
// (message.client & 0xffff) === (this.applicationId & 0xffff)
// ) {
// return
// }
message.ts = ts
message.sending = resolvedSending
message.payload = Buffer.from(message.payload)
this.event.emit('someip-frame', message)
setTimeout(() => {
this.log.info({
method: 'someipBase',
deviceId: this.deviceId,
data: message
})
}, 0)
}
someipServiceValid(info: VsomeipAvailabilityInfo, ts: number) {
this.log.info({
method: 'someipServiceValid',
deviceId: this.deviceId,
data: {
info,
ts: ts
}
})
}
error(ts: number, msg?: string) {
this.log.error({
method: 'someipError',
deviceId: this.deviceId,
data: {
ts: ts,
error: msg
}
})
}
}
export class OsTraceLOG {
vendor: string
log: Logger
closeFlag = false
constructor(vendor: string, writerToFile?: string) {
this.vendor = vendor
const et1 = externalTransport.map((t) => t.t())
this.log = createLogger({
transports: [new Base(), ...et1],
format: format.combine(
format.json(),
format.label({ label: `${vendor}` }),
...externalFormat
)
})
if (writerToFile) {
const csvLine = format((info: any, opts: any) => {
const d = info.data || {}
const method = info.message.method
if (method === 'osEvent') {
const d = (info.message.data as OsEvent) || {}
info[Symbol.for('message')] = `${d.ts},${d.type},${d.id},${d.status},${d.coreId}`
return info
}
return false
})
// 获取当前时间作为时间戳后缀 (格式: YYYYMMDDHHmmss)
const timestamp = dayjs().format('YYYYMMDDHHmmss')
const parsedPath = path.parse(writerToFile)
const fileWithSuffix = path.format({
dir: parsedPath.dir,
name: parsedPath.name + '_' + timestamp,
ext: parsedPath.ext
})
const fileTransport = new transports.File({
filename: fileWithSuffix,
level: 'info',
options: {
options: { flags: 'w' }
},
format: format.combine(csvLine())
})
this.log.add(fileTransport)
}
}
close() {
this.closeFlag = true
this.log.close()
}
osEvent(ts: number, event: OsEvent) {
if (this.closeFlag) {
return
}
this.log.info({
method: 'osEvent',
data: event,
ts: ts
})
}
error(ts: number, msg?: string) {
if (this.closeFlag) {
return
}
this.log.error({
method: 'osError',
error: msg,
ts: ts
})
}
}
export class PluginLOG {
log: Logger
constructor(public pluginId: string) {
const et1 = externalTransport.map((t) => t.t())
this.log = createLogger({
transports: [new Base(), ...et1],
format: format.combine(format.json(), ...externalFormat)
})
}
pluginEvent(event: string, data: any) {
this.log.info({
method: 'pluginEvent',
id: this.pluginId,
event: event,
data: data
})
}
error(msg: string, data?: any) {
this.log.error({
method: 'pluginError',
id: this.pluginId,
msg: msg,
data: data
})
}
close(): void {
this.log.close()
}
}
export class ReplayLOG {
log: Logger
closeFlag = false
replayId: string
constructor(replayId: string, instance?: string) {
this.replayId = replayId
const et1 = externalTransport.map((t) => t.t())
const formatList = [format.json(), format.label({ label: 'Replay' })]
if (instance) {
formatList.push(instanceFormat({ instance: instance }))
}
this.log = createLogger({
transports: [new Base(), ...et1],
format: format.combine(...formatList, ...externalFormat)
})
}
close() {
this.closeFlag = true
this.log.close()
}
start(filePath: string, fileFormat: string) {
if (this.closeFlag) return
this.log.info({
method: 'replayStart',
replayId: this.replayId,
data: { filePath, format: fileFormat }
})
}
stop(reason?: string) {
if (this.closeFlag) return
this.log.info({
method: 'replayStop',
replayId: this.replayId,
data: { reason }
})
}
pause() {
if (this.closeFlag) return
this.log.info({
method: 'replayPause',
replayId: this.replayId,
data: {}
})
}
resume() {
if (this.closeFlag) return
this.log.info({
method: 'replayResume',
replayId: this.replayId,
data: {}
})
}
progress(current: number, total: number, percent: number, repeat: number) {
if (this.closeFlag) return
this.log.info({
method: 'replayProgress',
replayId: this.replayId,
data: { current, total, percent, repeat }
})
}
error(msg: string) {
if (this.closeFlag) return
this.log.error({
method: 'replayError',
replayId: this.replayId,
data: { msg }
})
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,174 @@
import { is } from '@electron-toolkit/utils'
import {
app,
shell,
BrowserWindow,
ipcMain,
dialog,
protocol as eProtocol,
net,
MessageChannelMain,
MessagePortMain
} from 'electron'
import path, { join } from 'path'
import icon from '../../resources/icon.png?asset'
let port: MessagePortMain | null = null
ipcMain.on('ipc-get-port', (event, id: string) => {
if (port) {
port.close()
}
const { port1, port2 } = new MessageChannelMain()
port = port1
event.sender.postMessage('port', id, [port2])
// port2.start()
})
class LogQueue {
private static instance: LogQueue | null = null
list: any[] = []
timer: any
mainWin: BrowserWindow | undefined
private constructor(
public win: BrowserWindow[] = [],
private period = 100
) {
this.startTimer()
}
static getInstance(): LogQueue {
if (LogQueue.instance === null) {
LogQueue.instance = new LogQueue()
}
return LogQueue.instance
}
addWin(win: BrowserWindow, isMain: boolean) {
this.win.push(win)
if (isMain) {
this.mainWin = win
}
}
protected startTimer() {
this.timer = setInterval(() => {
if (this.list.length) {
port?.postMessage(this.list)
this.list = []
}
}, this.period)
}
stopTimer() {
clearInterval(this.timer)
this.list = []
}
removeWin(win: BrowserWindow) {
this.win = this.win.filter((w) => w !== win)
}
}
// Export the singleton instance
export const logQ = LogQueue.getInstance()
// Export a function to get the instance (alternative access method)
export const getLogQueue = () => LogQueue.getInstance()
// Export the class for type annotations if needed
export type { LogQueue }
const winMap = new Map<string, BrowserWindow>()
const winPosMap = new Map<string, { x: number; y: number; width: number; height: number }>()
ipcMain.on('ipc-open-window', (event, arg) => {
if (winMap.has(arg.id)) {
winMap.get(arg.id)?.show()
} else {
const pos = winPosMap.get(arg.id)
const win = new BrowserWindow({
width: pos?.width || arg.w || 800,
height: pos?.height || arg.h || 600,
x: pos?.x || undefined,
y: pos?.y || undefined,
...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
contextIsolation: true,
backgroundThrottling: false
},
frame: false,
show: false
})
winMap.set(arg.id, win)
logQ.addWin(win, false)
win.on('closed', () => {
winMap.delete(arg.id)
logQ.mainWin?.webContents.send('ipc-close-window', arg.id)
logQ.removeWin(win)
})
win.on('ready-to-show', () => {
win.show()
if (is.dev) {
win.webContents.openDevTools()
}
})
if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
const url = new URL(process.env['ELECTRON_RENDERER_URL'])
Object.entries(arg).forEach(([key, value]) => {
url.searchParams.set(key, String(value))
})
win.loadURL(url.toString())
} else {
const filePath = join(__dirname, '../renderer/index.html')
const searchParams = new URLSearchParams()
Object.entries(arg).forEach(([key, value]) => {
searchParams.set(key, String(value))
})
win.loadFile(filePath, {
search: searchParams.toString()
})
}
}
})
ipcMain.on('ipc-close-others-windows', (event, arg) => {
closeAllWindows()
})
export function closeAllWindows() {
winMap.forEach((win, key) => {
//store pos
const pos = win.getBounds()
winPosMap.set(key, {
x: pos?.x,
y: pos?.y,
width: pos?.width,
height: pos?.height
})
win.close()
})
}
export function closeWindow(id: string) {
const win = winMap.get(id)
if (win) {
//store pos
const pos = win.getBounds()
winPosMap.set(id, {
x: pos?.x,
y: pos?.y,
width: pos?.width,
height: pos?.height
})
win.close()
}
}
export function minimizeWindow(id: string) {
winMap.get(id)?.minimize()
}
export function maximizeWindow(id: string) {
if (winMap.get(id)?.isMaximized()) {
winMap.get(id)?.unmaximize()
} else {
winMap.get(id)?.maximize()
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,55 @@
import { PluginLOG } from './log'
import path from 'path'
import { error, log } from 'electron-log'
import { NodeClass } from './nodeItem'
export default class PluginClient {
nodeItem: NodeClass
worker: any
selfStop = false
log: PluginLOG
constructor(
public name: string,
private id: string,
jsFilePath: string,
nodeItem?: NodeClass
) {
this.log = new PluginLOG(this.id)
if (nodeItem) {
this.nodeItem = nodeItem
} else {
const pluginPath = path.dirname(jsFilePath)
this.nodeItem = new NodeClass(
{
id: this.id,
name: this.name,
channel: [],
script: jsFilePath
},
pluginPath,
name
)
this.nodeItem.pool?.registerHandler('pluginEvent', this.eventHandler.bind(this))
}
}
eventHandler(payload: { name: string; data: any }) {
const name = payload.name
const data = payload.data
this.log.pluginEvent(name, data)
}
async exec(method: string, ...params: any[]): Promise<any> {
return this.nodeItem.pool?.exec(`plugin.${method}`, params)
}
stop() {
this.nodeItem.pool?.clearHandlers()
this.nodeItem.pool?.registerHandler('pluginEvent', this.eventHandler.bind(this))
this.nodeItem.pool?.stopEmit()
}
close() {
this.log.close()
this.nodeItem.close()
}
}
@@ -0,0 +1,15 @@
import path from 'path'
import { PythonShell, Options, PythonShellError } from 'python-shell'
import fs from 'fs'
import pythonRequirements from '../../resources/requirements.txt?asset&asarUnpack'
export function getPythonPath(): string {
const baseDir = path.dirname(pythonRequirements)
const pythonDir = path.join(baseDir, 'python')
if (process.platform === 'win32') {
return path.join(pythonDir, 'python.exe')
}
// darwin (macOS) and linux: python-build-standalone uses bin/python3
return path.join(pythonDir, 'bin', 'python3')
}

Some files were not shown because too many files have changed in this diff Show More