{"id":50613063,"url":"https://github.com/cinderblock/electronics-workbench-decoder","last_synced_at":"2026-06-06T05:30:52.531Z","repository":{"id":143531422,"uuid":"393805843","full_name":"cinderblock/electronics-workbench-decoder","owner":"cinderblock","description":"Electronics Workbench (MultiSIM/Ultiboard) proprietary file format decoder","archived":false,"fork":false,"pushed_at":"2026-05-29T00:27:54.000Z","size":442,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-05-29T01:21:50.127Z","etag":null,"topics":["multisim","national-instruments","ultiboard"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cinderblock.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-08-07T22:28:46.000Z","updated_at":"2026-05-29T00:27:58.000Z","dependencies_parsed_at":"2023-03-27T14:23:30.383Z","dependency_job_id":null,"html_url":"https://github.com/cinderblock/electronics-workbench-decoder","commit_stats":null,"previous_names":["cinderblock/electronics-workbench-decoder","cinderblock/ewd"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/cinderblock/electronics-workbench-decoder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cinderblock%2Felectronics-workbench-decoder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cinderblock%2Felectronics-workbench-decoder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cinderblock%2Felectronics-workbench-decoder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cinderblock%2Felectronics-workbench-decoder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cinderblock","download_url":"https://codeload.github.com/cinderblock/electronics-workbench-decoder/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cinderblock%2Felectronics-workbench-decoder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33971106,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-06T02:00:07.033Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["multisim","national-instruments","ultiboard"],"created_at":"2026-06-06T05:30:50.959Z","updated_at":"2026-06-06T05:30:52.525Z","avatar_url":"https://github.com/cinderblock.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Electronics Workbench Decoder and Encoder\n\nInterop tools for Electronics Workbench / Multisim / Ultiboard binary\nformats. The goal: get designs and part databases out of EW into\nmainstream representations (and back, where the format permits).\n\n- **`ewd`** — decode an EW file into JSON or XML, picking the right\n  decoder by reading the file's magic bytes.\n- **`ewe`** — encode the text-form back into an EW binary the original\n  software can re-open. Currently supports compressed-XML; `.prj` Jet\n  database encode is deferred (see below).\n\n## Supported formats\n\n| Key        | Extension(s)       | Kind             | Decode output    |\n| ---------- | ------------------ | ---------------- | ---------------- |\n| `ewprj`    | `.ewprj`           | `compressed-xml` | `\u003cfile\u003e.xml`     |\n| `multisim` | `.ms10` … `.ms19`  | `compressed-xml` | `\u003cfile\u003e.xml`     |\n| `mdb`      | `.prj`, `.usr`     | `mdb`            | `\u003cfile\u003e.json`    |\n\n### compressed-xml (`ewprj`, `multisim`)\n\nPKWare DCL Implode (ASCII literal mode, large dictionary) wrapped in a\nsmall container: a magic-string header, a 64-bit LE total decompressed\nlength, then a sequence of `(decompressed_length: u32,\ncompressed_length: u32, pkware_implode_block)` sections. Multisim chunks\nbig files into 900 000-decompressed-byte sections; `ewe` mirrors that.\n\nDecode and encode are both supported. A decode/encode/decode round-trip\non every sample tested produces byte-identical XML, and the resulting\nfiles are accepted by `ewd`.\n\n### mdb (`.prj`, `.usr`)\n\nNI ships part libraries and project component databases as Microsoft\nAccess Jet 3 / Jet 4 files (despite the `.prj` / `.usr` extensions).\n`ewd` reads them via [`mdb-reader`](https://www.npmjs.com/package/mdb-reader)\nand emits a single JSON document with every table:\n\n```json\n{\n  \"format\": \"mdb\",\n  \"source\": \"Stocked.prj\",\n  \"tables\": {\n    \"SYS_COMPONENT\": {\n      \"columns\": [{ \"name\": \"Component_ID\", \"type\": \"long\" }, ...],\n      \"rows\": [{ \"Component_ID\": 17, \"Component_Name\": \"BD9763FVM\", ... }, ...]\n    },\n    ...\n  }\n}\n```\n\nDates become ISO 8601 strings; binary blob fields (`ole` columns) become\n`{ \"_bytes\": \"base64\", \"value\": \"...\" }` envelopes so the data survives a\nJSON round-trip.\n\n**Encode is a deliberately narrow byte-patch path, not a real Jet writer.**\nReimplementing Access's storage engine (page splits, B-tree index\nmaintenance, free-space tracking, `MSysObjects` invariants) is out of\nscope for this project — `ewd`/`ewe` exist to move data **out** of\nElectronics Workbench, not to clone Microsoft's database engine.\n\nWhat `ewe` does on an `mdb` input today:\n\n1. You decode with `ewd` to produce `\u003cfile\u003e.prj.json`.\n2. You edit the JSON in your text editor (or with a script).\n3. You run `ewe edited.json`. It diffs the edited JSON against the\n   original `.prj`, then for each changed cell where:\n   - the column is `text` or `memo`,\n   - the new value has the **same byte length** as the old,\n   - the old value appears **exactly once** in the file,\n\n   it overwrites the bytes in place. Anything that violates those\n   constraints is skipped, and `--verbose` prints the reason per cell.\n\nThis is enough for the typical vendor / price / MPN editing workflow\nbecause those values are usually unique and you usually substitute one\nsame-length identifier for another. It is **not** a general-purpose\neditor and we don't intend to grow it into one.\n\n### If you want general-purpose `.prj` edit support\n\nNot planned in this repo. The clean way to add it is a separate,\noptional, Windows-only tool that drives Microsoft's own Access engine:\n\n- **ACE OLE DB Provider** (`Microsoft.ACE.OLEDB.12.0` / `16.0`) — free\n  Redistributable from Microsoft, reads \u0026 writes both `.mdb` and `.accdb`.\n- **Access ODBC driver** — same engine, ODBC interface. Pair with\n  [`node-odbc`](https://github.com/markdirish/node-odbc) (N-API native\n  addon) and issue plain SQL: `UPDATE SYS_USER_DATA SET USER_DEFINED_7 =\n  ? WHERE Component_ID = ?`. The diff logic we already have for\n  edited-JSON → changed-cells would generate those statements directly.\n- **Jet OLE DB 4.0** — older, **32-bit only**, still works for Jet 3\n  `.mdb` files like NI's `.prj`.\n\nThat path requires Windows + a driver install and so deliberately lives\noutside this cross-platform repo's CI. The current byte-patch encode\ncovers the vendor/price editing case; for anything harder, route through\nAccess (or the ACE driver, or DAO/ADO) instead of expecting `ewe` to\ngrow into a full Jet writer.\n\n## Format detection\n\n`ewd` identifies the format by reading the magic bytes at the start of\nthe file, so the filename extension can be anything (renamed files, no\nextension, `.dat`, etc.). It then dispatches to the right decoder.\n\n`ewe` infers the format from the **output** filename's extension; pass\n`--format \u003ckey\u003e` to override for non-standard extensions. Out-of-range\nMultisim versions (e.g. `.ms9` or future `.ms20+`) need an explicit\n`--format multisim`.\n\n## Install\n\n### As a CLI (from npm)\n\n```bash\nnpm i -g electronics-workbench-decoder   # puts `ewd` and `ewe` on your PATH\n# …or run without installing:\nnpx -p electronics-workbench-decoder ewd MyDesign.ms14\n```\n\nThe published package is a self-contained bundle that runs on Node ≥ 18 —\nyou don't need bun to use it. Installed this way, invoke the tools directly\nas `ewd` / `ewe` (the `bun run ewd` form below is for working from source).\n\n### For development (from source)\n\nRequires [bun](https://bun.sh) (≥ 1.0).\n\n```bash\nbun install\n```\n\n## Decode\n\n```bash\nbun run ewd --verbose ./samples/Temp.ewprj ./samples/Design1.ms14\nbun run ewd --verbose ./samples/Stocked.prj          # writes Stocked.prj.json\n```\n\nFor each input, writes `\u003cfilename\u003e.xml` (compressed-xml) or\n`\u003cfilename\u003e.json` (mdb) next to it.\n\nOptions:\n\n- `-v`, `--verbose` — log per-section progress\n- `-c`, `--concurrent` — decode multiple files in parallel\n- positional args — files to decode\n\n## Encode\n\n### Compressed-XML (greenfield encode from `.xml`)\n\n```bash\nbun run ewe --verbose ./samples/Temp.ewprj.xml\n# or with explicit output:\nbun run ewe --output ./out.ewprj ./samples/Temp.ewprj.xml\n# or force a format on a non-standard extension:\nbun run ewe --format multisim --output ./out.dat ./samples/Design1.ms14.xml\n```\n\nBy default, strips a trailing `.xml` from each input to derive the output\npath. Format is inferred from the output extension (see the table above).\n\n### MDB in-place edits from `.json`\n\n```bash\n# Decode first to produce the editable JSON.\nbun run ewd ./samples/Stocked.prj\n# Edit ./samples/Stocked.prj.json in your editor / script.\n# Then re-encode: ewe diffs vs the original and writes a patched copy.\nbun run ewe --verbose ./samples/Stocked.prj.json\n# -\u003e samples/Stocked.prj.patched.prj\n```\n\n`ewe` recognizes `.json` inputs and routes them through the in-place\nedit path. The original `.prj` location is taken from the JSON's\n`source` field (or `--source \u003cpath\u003e` to override). The patched copy\ngoes to `\u003csource\u003e.patched.prj` (or `--output \u003cpath\u003e` to override).\n\nEach `--verbose` run prints `N applied, M skipped` and lists every\nskipped change with its reason. Skipped changes leave the file\nuntouched at that cell.\n\n### Options\n\n- `-v`, `--verbose` — log progress\n- `-c`, `--concurrent` — encode multiple files in parallel\n- `-o`, `--output \u003cpath\u003e` — explicit output path (single-input only)\n- `-s`, `--source \u003cpath\u003e` — for MDB JSON inputs: override the source `.prj`\n- `-f`, `--format \u003ckey\u003e` — for compressed-XML: force a format (`ewprj` or `multisim`)\n- positional args — XML or JSON files to encode\n\n### Compression ratio caveat\n\n`ewe` uses [`node-pkware`](https://github.com/cinderblock/node-pkware)'s\n`implode` for compression. The repetition search isn't quite as\nsophisticated as the original PKWare DCL implementation Multisim itself\nuses — re-encoded files are roughly **2× the size** of the originals\n(e.g. 9 KB `.ewprj` round-trips to 20 KB). The output is still valid\nPKWare DCL Implode: round-trips are byte-identical on every sample\ntested, and the resulting files are accepted by `ewd`.\n\nThis is a substantial improvement over the previous baseline (7-10×\nbloat) thanks to fixes in the `v5-fix` branch of the\n`cinderblock/node-pkware` fork — see `plans/jet-in-place-edit.md`\ncontext and the fork's `v5-fix` branch for the specific bugs that\nwere patched.\n\n## Programmatic API\n\nThe package also ships a library API (ESM + CJS, with TypeScript types) — the\nsame functions the CLIs are built on.\n\n```ts\nimport {\n  decodeFile,\n  decodeBuffer,\n  encodeBuffer,\n  decodeMdbBuffer,\n  formatByKey,\n} from 'electronics-workbench-decoder';\n\n// Filename → decoded JS values (format detected from the file's magic bytes)\nconst result = await decodeFile('Design1.ms14');\nif (result.kind === 'compressed-xml') {\n  console.log(result.xml.toString()); // the decoded XML\n} else {\n  console.log(result.data.tables.SYS_COMPONENT.rows); // Jet DB as a JS object\n}\n\n// Buffer in, buffer out — no file I/O\nconst { xml } = decodeBuffer(ewprjBytes);\nconst ewprj = encodeBuffer(xml, formatByKey('ewprj')!);\nconst db = decodeMdbBuffer(prjBytes); // → { format, source, tables: {...} }\n```\n\nExposed: `decodeFile`, `decodeBuffer`, `encodeBuffer`, `decodeMdbBuffer`, the\nformat registry (`FORMATS`, `formatByKey`, `formatForExtension`,\n`detectFormatByHeader`, `detectFileFormat`), and the supporting types\n(`EwbFormat`, `MdbJson`, `DecodeResult`, `DecodedXml`, …).\n\nDecoding covers every supported format. Encoding via the API covers the\ncompressed-xml containers (`encodeBuffer`); `.prj` in-place editing remains\nCLI-only (`ewe`).\n\n### Memory\n\nEverything operates in memory — a file is read whole, decoded/encoded whole,\nthen written. Peak RAM is on the order of the input plus the decoded output.\nThis is fine for the file sizes Electronics Workbench produces (KB to a few\nMB). Streaming wouldn't help the `.prj`/`.usr` databases anyway (Jet is a\nrandom-access format that `mdb-reader` must load fully), and for the\ncompressed-xml containers it would only cap RAM at one ~900 KB section — a\nmarginal win not worth the added complexity at these sizes.\n\n## Tests\n\n```bash\nbun test\n```\n\nCovers the format registry (extension matching, header detection\nincluding the Jet magic prefix with leading NUL bytes, on-disk\ndetection), encoder filename helpers, `mdb` value normalization\n(`Date` → ISO, OLE blobs → base64), the public library API (buffer\nround-trips, `decodeFile` against a temp file), and compressed-xml\nround-trip integration (tiny `.ewprj`, tiny `.ms14`, multi-block\npayload that spans more than one PKWare section, and an empty payload).\n\n## Development\n\n```bash\nbun run dev:ewd --verbose ./samples/Temp.ewprj\nbun run dev:ewe --verbose ./samples/Temp.ewprj.xml\n```\n\nBoth use `bun --watch` for fast reload on source changes.\n\n## Lint, format, and typecheck\n\n```bash\nbun run check        # biome lint + format check (no changes)\nbun run check:fix    # apply all safe fixes\nbun run format       # format only\nbun run typecheck    # tsc --noEmit\n```\n\nCI on push/PR runs `check`, `typecheck`, and `test` on Ubuntu and Windows.\n\n## Releasing\n\nPublishing to npm is automated by GitHub Actions\n(`.github/workflows/publish.yml`) using npm Trusted Publishing (OIDC):\nno stored `NPM_TOKEN`, and each release gets a signed provenance\nattestation.\n\nTo cut a release:\n\n```bash\nnpm version patch        # or `minor` / `major`; bumps package.json, commits, tags vX.Y.Z\ngit push --follow-tags   # pushing the tag triggers the publish workflow\n```\n\nThe `vX.Y.Z` tag triggers the workflow, which verifies the tag matches\n`package.json`, runs the full gate (`check` + `typecheck` + `test` +\nlicense collection + `build`), then `npm publish --provenance`.\n\nNotes for maintainers:\n\n- The npm package has a **Trusted Publisher** configured for this repo and\n  the `publish.yml` workflow. If you rename the workflow file or the repo,\n  update that entry on npmjs.com to match — otherwise the OIDC identity\n  won't match and publishing fails with a 404/permission error.\n- The published artifact is a self-contained bundle. `dist/` and\n  `THIRD-PARTY-LICENSES.md` are generated at publish time (by the\n  `prepublishOnly` script) and are not committed.\n- Runtime dependencies are bundled into `dist/`, so the published package\n  declares none. `THIRD-PARTY-LICENSES.md` carries their license texts.\n\n## See also\n\n- [barncastle's `MultiSimConverter`](https://gist.github.com/barncastle/4277ac44aa47bf8c4389c7df0d160e56)\n  — a separate, independent C# implementation that handles the same\n  compressed-xml container. It round-trips the `.ms14` / `.ewprj` container\n  to and from plain XML by\n  P/Invoking the original PKWare `IMPLODE.DLL` (so its compression matches\n  Multisim's exactly, avoiding the [ratio caveat](#compression-ratio-caveat)\n  above). It dispatches on the same magic strings `ewd` reads\n  (`MSMCompressedElectronicsWorkbenchXML`, `CompressedElectronicsWorkbenchXML`)\n  and uses the same 900 000-byte block size. It covers only the compressed-xml\n  container — not the Jet (`.prj` / `.usr`) part databases, and it doesn't\n  convert designs to other EDA formats.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcinderblock%2Felectronics-workbench-decoder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcinderblock%2Felectronics-workbench-decoder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcinderblock%2Felectronics-workbench-decoder/lists"}