├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── funding.yml └── workflows │ ├── api-docs.yml │ ├── codeql-analysis.yml │ ├── npm-build.yml │ ├── publish-package.yml │ └── release.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc.js ├── LICENSE ├── README.md ├── jest.config.js ├── lint-staged.config.js ├── package-lock.json ├── package.json ├── samples ├── README.md ├── js │ └── aircraftPosition.mjs └── typescript │ ├── aircraftInformation.ts │ ├── clientEvents.ts │ ├── facilities.ts │ ├── facilitiesMenu.ts │ ├── inputEvents.ts │ ├── pmdgCduScreen.ts │ ├── reconnect.ts │ ├── showMetar.ts │ ├── simulationVariablesRead.ts │ ├── simulationVariablesReadTagged.ts │ ├── simulationVariablesWrite.ts │ ├── systemEvents.ts │ ├── trafficRadar │ ├── index.html │ ├── trafficRadar.ts │ └── websocketServer.ts │ └── wxradar │ ├── index.html │ └── index.ts ├── src ├── RawBuffer.ts ├── SimConnectConnection.ts ├── SimConnectConstants.ts ├── SimConnectPacketBuilder.ts ├── SimConnectSocket.ts ├── Types.ts ├── connectionParameters.ts ├── datastructures │ ├── ControllerItem.ts │ ├── FacilityAirport.ts │ ├── FacilityMinimal.ts │ ├── FacilityNDB.ts │ ├── FacilityVOR.ts │ ├── FacilityWaypoint.ts │ ├── InputEventDescriptor.ts │ ├── JetwayData.ts │ ├── VersionBaseType.ts │ └── index.ts ├── dto │ ├── InitPosition.ts │ ├── LatLonAlt.ts │ ├── MarkerState.ts │ ├── PBH.ts │ ├── SimConnectData.ts │ ├── Waypoint.ts │ ├── XYZ.ts │ ├── bufferHelpers.ts │ ├── icao.ts │ └── index.ts ├── enums │ ├── ClientDataPeriod.ts │ ├── FacilityDataType.ts │ ├── FacilityListType.ts │ ├── InputEventType.ts │ ├── JetwayStatus.ts │ ├── NotificationPriority.ts │ ├── Protocol.ts │ ├── SimConnectDataType.ts │ ├── SimConnectException.ts │ ├── SimConnectPeriod.ts │ ├── SimObjectType.ts │ ├── TextResult.ts │ ├── TextType.ts │ ├── WeatherMode.ts │ └── index.ts ├── flags │ ├── ClientDataRequestFlag.ts │ ├── DataRequestFlag.ts │ ├── DataSetFlag.ts │ ├── EventFlag.ts │ └── index.ts ├── index.ts ├── recv │ ├── RecvActionCallback.ts │ ├── RecvAirportList.ts │ ├── RecvAssignedObjectID.ts │ ├── RecvCloudState.ts │ ├── RecvControllersList.ts │ ├── RecvCustomAction.ts │ ├── RecvEnumerateInputEventParams.ts │ ├── RecvEnumerateInputEvents.ts │ ├── RecvEvent.ts │ ├── RecvEventAddRemove.ts │ ├── RecvEventEx1.ts │ ├── RecvEventFilename.ts │ ├── RecvEventFrame.ts │ ├── RecvEventRaceEnd.ts │ ├── RecvEventRaceLap.ts │ ├── RecvEventWeatherMode.ts │ ├── RecvException.ts │ ├── RecvFacilitiesList.ts │ ├── RecvFacilityData.ts │ ├── RecvFacilityDataEnd.ts │ ├── RecvFacilityMinimalList.ts │ ├── RecvGetInputEvent.ts │ ├── RecvJetwayData.ts │ ├── RecvListTemplate.ts │ ├── RecvNDBList.ts │ ├── RecvOpen.ts │ ├── RecvReservedKey.ts │ ├── RecvSimObjectData.ts │ ├── RecvSubscribeInputEvent.ts │ ├── RecvSystemState.ts │ ├── RecvVORList.ts │ ├── RecvWaypointList.ts │ ├── RecvWeatherObservation.ts │ └── index.ts └── utils │ ├── ini.ts │ ├── network.ts │ └── registry.ts ├── tests ├── flightLoad.test.ts └── utils.ts ├── tsconfig.build.json ├── tsconfig.json └── utils └── packetInspectorProxy.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | indent_style = space 5 | indent_size = 4 6 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | samples 3 | .eslintrc.js 4 | jest.config.js 5 | lint-staged.config.js 6 | node_modules 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2020, 5 | sourceType: 'module', 6 | project: './tsconfig.json', 7 | }, 8 | env: { 9 | browser: false, 10 | node: true, 11 | es6: true, 12 | }, 13 | settings: { 14 | 'import/resolver': { 15 | node: { 16 | extensions: ['.ts'], 17 | }, 18 | }, 19 | }, 20 | plugins: ['@typescript-eslint', 'eslint-plugin-tsdoc'], 21 | extends: [ 22 | 'plugin:@typescript-eslint/recommended', 23 | 'airbnb-base', 24 | 'airbnb-typescript/base', 25 | 'prettier', 26 | 'plugin:prettier/recommended', 27 | ], 28 | rules: { 29 | '@typescript-eslint/switch-exhaustiveness-check': 'error', 30 | '@typescript-eslint/no-unused-vars': 'error', 31 | '@typescript-eslint/no-explicit-any': 'error', 32 | 'import/extensions': [ 33 | 'error', 34 | 'ignorePackages', 35 | { 36 | js: 'never', 37 | ts: 'never', 38 | }, 39 | ], 40 | 'import/prefer-default-export': 'off', 41 | 'no-plusplus': 'off', 42 | 'import/no-import-module-exports': 'off', 43 | '@typescript-eslint/no-use-before-define': 'off', 44 | 'no-bitwise': 'off', 45 | 'no-underscore-dangle': 'off', 46 | '@typescript-eslint/ban-ts-comment': 'off', 47 | 'no-inline-comments': 'off', 48 | 'default-case': 'warn', 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /.github/funding.yml: -------------------------------------------------------------------------------- 1 | github: EvenAR 2 | buy_me_a_coffee: evenar 3 | -------------------------------------------------------------------------------- /.github/workflows/api-docs.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy API docs to pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v4 36 | - name: Build docs 37 | run: npm install && npm run generate-api-reference 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v2 40 | with: 41 | path: './docs' # Upload entire repository 42 | - name: Deploy to GitHub Pages 43 | id: deployment 44 | uses: actions/deploy-pages@v3 45 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '19 15 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | with: 74 | category: "/language:${{matrix.language}}" 75 | -------------------------------------------------------------------------------- /.github/workflows/npm-build.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | matrix: 13 | node-version: [latest] 14 | os: [windows-latest, macos-latest, ubuntu-latest] 15 | runs-on: ${{ matrix.os }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Use Node.js ${{ matrix.node-version }} 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: ${{ matrix.node-version }} 22 | - run: npm ci 23 | - run: npm run build --if-present 24 | - run: npm test 25 | -------------------------------------------------------------------------------- /.github/workflows/publish-package.yml: -------------------------------------------------------------------------------- 1 | name: Publish new release to npm 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version: 20.x 15 | - run: npm ci 16 | - run: npm test 17 | 18 | publish-gpr: 19 | needs: build 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | packages: write 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: '20.x' 29 | registry-url: 'https://npm.pkg.github.com' 30 | - run: npm ci 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 34 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create a release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | type: choice 8 | description: 'Version to bump' 9 | required: true 10 | options: 11 | - 'patch' 12 | - 'minor' 13 | - 'major' 14 | tag: 15 | type: choice 16 | description: 'Tag' 17 | required: true 18 | options: 19 | - 'alpha' 20 | - 'beta' 21 | - 'latest' 22 | 23 | jobs: 24 | release: 25 | name: Release new version 26 | runs-on: ubuntu-latest 27 | outputs: 28 | new_tag: ${{ steps.bump_version.outputs.NEW_TAG }} 29 | env: 30 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: actions/setup-node@v4 34 | with: 35 | node-version: '20.x' 36 | registry-url: 'https://registry.npmjs.org' 37 | - run: npm ci 38 | - run: npm run build 39 | - run: | 40 | git config user.name "github-actions[bot]" 41 | git config user.email "41898282+github-actions[bot]@users.noreply.github" 42 | - id: bump_version 43 | run: echo "NEW_TAG=$(npm version ${{ inputs.version }})" >> $GITHUB_OUTPUT 44 | - run: git push --follow-tags 45 | github_release: 46 | name: Create a release 47 | runs-on: ubuntu-latest 48 | needs: release 49 | env: 50 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | steps: 52 | - uses: actions/checkout@v4 53 | - run: gh release create ${{ needs.release.outputs.new_tag }} --draft --verify-tag 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | dist 4 | .DS_Store 5 | temp 6 | doc 7 | docs 8 | build 9 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | exit_with_warning_message(){ 5 | echo "\e[0;33m$1\e[m" 6 | exit 1 7 | } 8 | 9 | echo "Formatting and linting staged files ✨" 10 | ./node_modules/.bin/lint-staged || exit_with_warning_message "\nCode contains linting errors.\n" 11 | echo "Successfully formatted and linted staged files ✨" 12 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 4, 3 | printWidth: 100, 4 | endOfLine: 'lf', 5 | arrowParens: 'avoid', 6 | trailingComma: 'es5', 7 | semi: true, 8 | useTabs: false, 9 | singleQuote: true, 10 | bracketSpacing: true, 11 | overrides: [ 12 | { 13 | files: '*.json', 14 | options: { 15 | tabWidth: 2, 16 | }, 17 | }, 18 | ], 19 | }; 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-simconnect 2 | 3 | [![npm version](https://badge.fury.io/js/node-simconnect.svg)](https://badge.fury.io/js/node-simconnect) 4 | [![Strict TypeScript Checked](https://badgen.net/badge/TS/Strict 'Strict TypeScript Checked')](https://www.typescriptlang.org) 5 | 6 | A non-official SimConnect client library written in TypeScript. Lets you write Node.js applications that communicates directly with Microsoft Flight Simulator, FSX and Prepar3D without need for additional SDK files. Runs on Windows, Linux and Mac. 7 | 8 | This project is a port of the Java client library 9 | [jsimconnect](https://github.com/mharj/jsimconnect), originally written by 10 | [lc0277](https://www.fsdeveloper.com/forum/members/lc0277.1581). Details about the protocol can be found on [lc0277's old website](http://web.archive.org/web/20090620063532/http://lc0277.nerim.net/jsimconnect/doc/flightsim/simconnect/package-summary.html#package_description). A huge thanks to everyone involved in that project! :pray: 11 | 12 | ## Installation and use 13 | 14 | > :bulb: Tip: check out the [msfs-simconnect-api-wrapper](https://www.npmjs.com/package/msfs-simconnect-api-wrapper) which provides a more user-friendly wrapper around some of the `node-simconnect` APIs. 15 | 16 | 1. `npm install node-simconnect` 17 | 2. Check out the [/samples](https://github.com/EvenAR/node-simconnect/tree/master/samples) folder for example scripts. 18 | 3. Refer to the [official SimConnect documentation](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/SimConnect_API_Reference.htm) for comprehensive details on SimConnect APIs and usage. 19 | 20 | There are also [auto generated API-docs](https://evenar.github.io/node-simconnect/). 21 | 22 | ### Getting started 23 | 24 | You always start by calling [`open(...)`](https://evenar.github.io/node-simconnect/functions/open.html) which will attempt to open a connection with the SimConnect server (your flight simulator). If this succeeds you will get access to: 25 | 26 | - [`recvOpen`](https://evenar.github.io/node-simconnect/classes/RecvOpen.html): contains simulator information 27 | - [`handle`](https://evenar.github.io/node-simconnect/classes/SimConnectConnection.html): used for accessing the SimConnect APIs 28 | 29 | Example: 30 | 31 | ```js 32 | import { open, Protocol } from 'node-simconnect'; 33 | 34 | const EVENT_ID_PAUSE = 1; 35 | 36 | open('My SimConnect client', Protocol.FSX_SP2) 37 | .then(function ({ recvOpen, handle }) { 38 | console.log('Connected to', recvOpen.applicationName); 39 | 40 | handle.on('event', function (recvEvent) { 41 | switch (recvEvent.clientEventId) { 42 | case EVENT_ID_PAUSE: 43 | console.log(recvEvent.data === 1 ? 'Sim paused' : 'Sim unpaused'); 44 | break; 45 | } 46 | }); 47 | handle.on('exception', function (recvException) { 48 | console.log(recvException); 49 | }); 50 | handle.on('quit', function () { 51 | console.log('Simulator quit'); 52 | }); 53 | handle.on('close', function () { 54 | console.log('Connection closed unexpectedly (simulator CTD?)'); 55 | }); 56 | 57 | handle.subscribeToSystemEvent(EVENT_ID_PAUSE, 'Pause'); 58 | }) 59 | .catch(function (error) { 60 | console.log('Connection failed:', error); 61 | }); 62 | ``` 63 | 64 | ## node-simconnect vs the official API 65 | 66 | ### Supported APIs 67 | 68 | Most of the APIs described in the [official SimConnect documentation](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/SimConnect_API_Reference.htm) are implemented in `node-simconnect`. For information on how each feature works, please refer to the official documentation. 69 | 70 | Several new features have been added to the SimConnect API after the new Microsoft Flight Simulator was released, and more features are likely to come. Most of these will only be implemented on request. If you are missing any features in `node-simconnect` feel free to [open a new issue](https://github.com/EvenAR/node-simconnect/issues) or create a pull request. 71 | 72 | Prepar3D support and Prepar3D-only-features will not be prioritized. 73 | 74 | For a complete list of available API methods, please refer to the [`SimConnectConnection`](https://evenar.github.io/node-simconnect/classes/SimConnectConnection.html) class. 75 | 76 | ### Data unwrapping 77 | 78 | A major feature used by C/C++/C# implementation of SimConnect client libraries is the ability to directly cast a memory block to a user-defined structure. This is technically impossible to do in JavaScript or TypeScript since memory layout of classes and types are not accessible. Consequently, the wrapping/unwrapping steps must be done by the user. 79 | 80 | Example using the official SimConnect SDK (C++): 81 | 82 | ```C++ 83 | // C++ code //////////////////// 84 | 85 | struct Struct1 { 86 | double kohlsmann; 87 | double altitude; 88 | double latitude; 89 | double longitude; 90 | int verticalSpeed; 91 | }; 92 | 93 | // .... 94 | hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "Kohlsman setting hg", "inHg"); 95 | hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "Indicated Altitude", "feet"); 96 | hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "Plane Latitude", "degrees"); 97 | hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "Plane Longitude", "degrees"); 98 | hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "VERTICAL SPEED", "Feet per second", SimConnectDataType.INT32); 99 | 100 | SimConnect_RequestDataOnSimObject(hSimConnect, REQUEST_1, DEFINITION_1, SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_SECOND); 101 | // .... 102 | 103 | void CALLBACK MyDispatchProc(SIMCONNECT_RECV* pData, DWORD cbData) { 104 | switch(pData->dwID) { 105 | case SIMCONNECT_RECV_ID_SIMOBJECT_DATA: { 106 | SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData = (SIMCONNECT_RECV_SIMOBJECT_DATA*) pData; 107 | switch(pObjData->dwRequestID) { 108 | case REQUEST_1: 109 | Struct1 *pS = (Struct1*)&pObjData->dwData; 110 | break; 111 | } 112 | break; 113 | } 114 | } 115 | } 116 | ``` 117 | 118 | The code below demonstrates how the same is achieved with `node-simconnect`: 119 | 120 | ```ts 121 | // TypeScript code //////////////////// 122 | 123 | const REQUEST_1 = 0; 124 | const DEFINITION_1 = 0; 125 | // .... 126 | handle.addToDataDefinition(DEFINITION_1, 'Kohlsman setting hg', 'inHg', SimConnectDataType.FLOAT64); 127 | handle.addToDataDefinition(DEFINITION_1, 'Indicated Altitude', 'feet', SimConnectDataType.FLOAT64); 128 | handle.addToDataDefinition(DEFINITION_1, 'Plane Latitude', 'degrees', SimConnectDataType.FLOAT64); 129 | handle.addToDataDefinition(DEFINITION_1, 'Plane Longitude', 'degrees', SimConnectDataType.FLOAT64); 130 | handle.addToDataDefinition(DEFINITION_1, 'VERTICAL SPEED', 'Feet per second', SimConnectDataType.INT32); 131 | 132 | handle.requestDataOnSimObject(REQUEST_1, DEFINITION_1, SimConnectConstants.OBJECT_ID_USER, SimConnectPeriod.SIM_FRAME); 133 | 134 | // .... 135 | handle.on('simObjectData', recvSimObjectData => { 136 | switch (recvSimObjectData.requestID) { 137 | case REQUEST_1: { 138 | const receivedData = { 139 | // Read order is important! 140 | kohlsmann: recvSimObjectData.data.readFloat64(), 141 | altitude: recvSimObjectData.data.readFloat64(), 142 | latitude: recvSimObjectData.data.readFloat64(), 143 | longitude: recvSimObjectData.data.readFloat64(), 144 | verticalSpeed: recvSimObjectData.data.readInt32(), 145 | } 146 | break; 147 | } 148 | } 149 | }); 150 | ``` 151 | 152 | When the `simObjectData` callback is triggered, the `recvSimObjectData.data` is used to extract the requested simulation variables. These values are stored as a single continuous binary data chunk/buffer, maintaining the order in which the simulation variables were added to the data definition. In this case, the buffer is 288 bits long (64 + 64 + 64 + 64 + 32), or 36 bytes. 153 | 154 | The `read...()` functions are used to extract each value individually. When the correct function is used, the reading "cursor" (offset) automatically moves after each read, positioning it at the beginning of the next value in the buffer. Consequently, it is crucial to ensure that the values are read in the same order and using the same data type as initially requested. 155 | 156 | ## Running over network? 157 | 158 | If the Node.js application runs on the same computer as the flight simulator you don't need to worry about this part. 159 | 160 | To connect from an external computer you must configure SimConnect to accept connections from other hosts. This procedure is also described in the official docs, but here is the short version: 161 | 162 | 1. Open `SimConnect.xml`. 163 | 164 | - FSX: `X:\Users\\AppData\Roaming\Microsoft\FSX` 165 | - MSFS: `X:\Users\\AppData\Local\Packages\Microsoft.FlightSimulator_**********\LocalCache`. 166 | 167 | 1. Set property `
0.0.0.0
`. Example of a working SimConnect.xml file: 168 | 169 | ```xml 170 | 171 | 172 | SimConnect.xml 173 | 174 | IPv4 175 | local 176 | 5111 177 | 64 178 | 41088 179 |
0.0.0.0
180 |
181 |
182 | ``` 183 | 184 | Connecting from a remote script can be done by providing the IP address of the flight simulator PC and the port number when calling `open`: 185 | 186 | ```js 187 | const options = { remote: { host: 'localhost', port: 5111 } }; 188 | 189 | open('My SimConnect client', Protocol.FSX_SP2, options).then(/* ... */).catch(/* try again? */); 190 | ``` 191 | 192 | Note that if no connection options are specified, `node-simconnect` will auto-discover connection details in the following order: 193 | 194 | 1. Look for a [`SimConnect.cfg`](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/SimConnect_CFG_Definition.htm) in the folder where Node.js is located. If the script is running in Electron, this will be the folder where the Electron executable is installed. 195 | 1. Look for a [`SimConnect.cfg`](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/SimConnect_CFG_Definition.htm) in the user's home directory (`%USERPROFILE%`, eg. `C:\Users\`) 196 | 1. Look for a named pipe in the Windows registry, automatically set by the simulator 197 | 1. Look for a port number in the Windows registry, automatically set by the simulator. node-simconnect will then connect to `localhost:`. 198 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '**/*.ts': () => 'tsc --noEmit', 3 | '**/*.(ts|js)': (filenames) => [ 4 | `eslint --fix ${filenames.join(' ')}`, 5 | `prettier --write ${filenames.join(' ')}`, 6 | ], 7 | '**/*.(md|json)': (filenames) => `prettier --write ${filenames.join(' ')}`, 8 | }; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-simconnect", 3 | "version": "4.0.0", 4 | "description": "A SimConnect client library for Node.JS.", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "type": "commonjs", 8 | "scripts": { 9 | "build": "npx rimraf dist && tsc -p tsconfig.build.json", 10 | "watch": "tsc --watch", 11 | "test": "jest", 12 | "link": "npm run build && cd dist && npm link", 13 | "precommit": "pretty-quick --staged", 14 | "prepublishOnly": "npm run build", 15 | "prepare": "husky install", 16 | "generate-api-reference": "typedoc src/index.ts" 17 | }, 18 | "author": "Even Arneberg Rognlien", 19 | "license": "LGPL-3.0-or-later", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/EvenAR/node-simconnect.git" 23 | }, 24 | "keywords": [ 25 | "FSX", 26 | "P3D", 27 | "SDK", 28 | "SimConnect", 29 | "Prepar3D", 30 | "FlightSimulator", 31 | "Simulator" 32 | ], 33 | "bugs": { 34 | "url": "https://github.com/EvenAR/node-simconnect/issues" 35 | }, 36 | "homepage": "https://github.com/EvenAR/node-simconnect#readme", 37 | "devDependencies": { 38 | "@types/bytebuffer": "^5.0.44", 39 | "@types/debug": "^4.1.7", 40 | "@types/ini": "^1.3.30", 41 | "@types/jest": "^29.2.4", 42 | "@typescript-eslint/eslint-plugin": "^5.26.0", 43 | "@typescript-eslint/parser": "^5.26.0", 44 | "eslint": "^8.16.0", 45 | "eslint-config-airbnb-base": "^15.0.0", 46 | "eslint-config-airbnb-typescript": "^17.0.0", 47 | "eslint-config-prettier": "^8.5.0", 48 | "eslint-plugin-import": "^2.26.0", 49 | "eslint-plugin-prettier": "^4.0.0", 50 | "eslint-plugin-simple-import-sort": "^7.0.0", 51 | "eslint-plugin-tsdoc": "^0.2.17", 52 | "husky": "^8.0.1", 53 | "jest": "^29.3.1", 54 | "lint-staged": "^12.4.2", 55 | "prettier": "^2.6.2", 56 | "pretty-quick": "^3.1.0", 57 | "ts-jest": "^29.0.3", 58 | "typedoc": "^0.25.4", 59 | "typescript": "^4.9.4" 60 | }, 61 | "dependencies": { 62 | "@types/node": "*", 63 | "bytebuffer": "^5.0.1", 64 | "debug": "^4.3.4", 65 | "ini": "^2.0.0", 66 | "regedit": "^5.1.1" 67 | }, 68 | "husky": { 69 | "hooks": { 70 | "pre-commit": "npm run precommit" 71 | } 72 | }, 73 | "files": [ 74 | "/dist", 75 | "/samples", 76 | "./package.json", 77 | "./package-lock.json", 78 | "./README.md", 79 | "./LICENCE" 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | # node-simconnect samples 2 | 3 | In order to run the samples you first need to build the node-simconnect project (from the root folder): 4 | 5 | ``` 6 | npm install 7 | npm run build 8 | ``` 9 | 10 | Then (still from the node-simconnect root folder): 11 | 12 | - TypeScript: `npx ts-node .\\samples\\typescript\\.ts` 13 | - JavaScript: `node .\\samples\\js\\.mjs` 14 | -------------------------------------------------------------------------------- /samples/js/aircraftPosition.mjs: -------------------------------------------------------------------------------- 1 | import { 2 | open, 3 | Protocol, 4 | SimConnectDataType, 5 | SimConnectPeriod, 6 | SimConnectConstants, 7 | } from '../../dist/index.js'; 8 | 9 | const POSITION_DATA = 1; 10 | const REQUEST_ID_POSITION_DATA = 1; 11 | const EVENT_ID_PAUSE = 1; 12 | 13 | open('My app', Protocol.FSX_SP2) 14 | .then(function ({ recvOpen, handle }) { 15 | console.log('Connected to', recvOpen.applicationName); 16 | 17 | handle.addToDataDefinition( 18 | POSITION_DATA, 19 | 'Plane Latitude', 20 | 'degrees', 21 | SimConnectDataType.FLOAT64, 22 | 0.0, 23 | SimConnectConstants.UNUSED 24 | ); 25 | handle.addToDataDefinition( 26 | POSITION_DATA, 27 | 'Plane Longitude', 28 | 'degrees', 29 | SimConnectDataType.FLOAT64, 30 | 0.0, 31 | SimConnectConstants.UNUSED 32 | ); 33 | handle.requestDataOnSimObject( 34 | REQUEST_ID_POSITION_DATA, 35 | POSITION_DATA, 36 | SimConnectConstants.OBJECT_ID_USER, 37 | SimConnectPeriod.SECOND, 38 | 0, 39 | 0, 40 | 0, 41 | 0 42 | ); 43 | handle.subscribeToSystemEvent(EVENT_ID_PAUSE, 'Pause'); 44 | 45 | handle.on('event', function (recvEvent) { 46 | switch (recvEvent.eventID) { 47 | case EVENT_ID_PAUSE: 48 | console.log( 49 | recvEvent.data === 1 ? 'Sim paused' : 'Sim unpaused' 50 | ); 51 | break; 52 | } 53 | }); 54 | 55 | handle.on('simObjectData', function (recvSimObjectData) { 56 | switch (recvSimObjectData.requestID) { 57 | case POSITION_DATA: 58 | console.log({ 59 | latitude: recvSimObjectData.data.readFloat64(), 60 | longitude: recvSimObjectData.data.readFloat64(), 61 | }); 62 | break; 63 | } 64 | }); 65 | handle.on('quit', function () { 66 | console.log('Quit'); 67 | }); 68 | }) 69 | .catch(function (error) { 70 | console.log('Connection failed:', error); 71 | }); 72 | -------------------------------------------------------------------------------- /samples/typescript/aircraftInformation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | open, 3 | Protocol, 4 | SimConnectConstants, 5 | SimConnectDataType, 6 | SimConnectPeriod, 7 | } from '../../dist'; 8 | 9 | const AIRCRAFT_DATA_REQUEST = 0; 10 | const AIRCRAFT_DATA_DEFINITION = 0; 11 | 12 | open('My app', Protocol.FSX_SP2).then(recv => { 13 | console.log('Connected to', recv.recvOpen.applicationName); 14 | 15 | recv.handle.addToDataDefinition( 16 | AIRCRAFT_DATA_DEFINITION, 17 | 'Plane Latitude', 18 | 'degrees', 19 | SimConnectDataType.FLOAT64, 20 | 0, 21 | SimConnectConstants.UNUSED 22 | ); 23 | recv.handle.addToDataDefinition( 24 | AIRCRAFT_DATA_DEFINITION, 25 | 'Plane Longitude', 26 | 'degrees', 27 | SimConnectDataType.FLOAT64, 28 | 0, 29 | SimConnectConstants.UNUSED 30 | ); 31 | recv.handle.addToDataDefinition( 32 | AIRCRAFT_DATA_DEFINITION, 33 | 'CATEGORY', 34 | null, 35 | SimConnectDataType.STRING32, 36 | 0, 37 | SimConnectConstants.UNUSED 38 | ); 39 | recv.handle.addToDataDefinition( 40 | AIRCRAFT_DATA_DEFINITION, 41 | 'TITLE', 42 | null, 43 | SimConnectDataType.STRING128, 44 | 0, 45 | SimConnectConstants.UNUSED 46 | ); 47 | recv.handle.addToDataDefinition( 48 | AIRCRAFT_DATA_DEFINITION, 49 | 'ATC ID', 50 | null, 51 | SimConnectDataType.STRING32, 52 | 0, 53 | SimConnectConstants.UNUSED 54 | ); 55 | 56 | recv.handle.requestDataOnSimObject( 57 | AIRCRAFT_DATA_REQUEST, 58 | AIRCRAFT_DATA_DEFINITION, 59 | SimConnectConstants.OBJECT_ID_USER, 60 | SimConnectPeriod.ONCE, 61 | 0, 62 | 0, 63 | 0, 64 | 0 65 | ); 66 | 67 | recv.handle.on('simObjectData', recvSimObjectData => { 68 | console.log(`Lat: ${recvSimObjectData.data.readFloat64()}`); 69 | console.log(`Lng: ${recvSimObjectData.data.readFloat64()}`); 70 | console.log(`Type: "${recvSimObjectData.data.readString32()}"`); 71 | console.log(`Title: "${recvSimObjectData.data.readString128()}"`); 72 | console.log(`ATC ID: "${recvSimObjectData.data.readString32()}"`); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /samples/typescript/clientEvents.ts: -------------------------------------------------------------------------------- 1 | import { EventFlag, open, Protocol, SimConnectConstants } from '../../dist'; 2 | 3 | /** 4 | * Demonstrates how to use client events to interact with the simulator 5 | */ 6 | 7 | enum ClientEventID { 8 | INCREMENT, 9 | DECREMENT, 10 | } 11 | 12 | open('My app', Protocol.FSX_SP2) 13 | .then(({ recvOpen, handle }) => { 14 | console.log('Connected: ', recvOpen); 15 | 16 | handle.mapClientEventToSimEvent(ClientEventID.INCREMENT, 'AP_ALT_VAR_INC'); 17 | handle.mapClientEventToSimEvent(ClientEventID.DECREMENT, 'AP_ALT_VAR_DEC'); 18 | 19 | let step = 0; 20 | let rise = true; 21 | 22 | // Increment and decrement the altitude every 50ms 23 | setInterval(() => { 24 | step++; 25 | handle.transmitClientEvent( 26 | SimConnectConstants.OBJECT_ID_USER, 27 | rise ? ClientEventID.INCREMENT : ClientEventID.DECREMENT, 28 | 0, 29 | 1, 30 | EventFlag.EVENT_FLAG_GROUPID_IS_PRIORITY 31 | ); 32 | 33 | if (step === 100) { 34 | rise = !rise; // Change direction 35 | step = 0; 36 | } 37 | }, 50); 38 | 39 | handle.on('error', error => { 40 | console.log('Error:', error); 41 | }); 42 | 43 | handle.on('exception', recvException => { 44 | console.log('recvException:', recvException); 45 | }); 46 | }) 47 | .catch(error => { 48 | console.log('Failed to connect', error); 49 | }); 50 | -------------------------------------------------------------------------------- /samples/typescript/facilities.ts: -------------------------------------------------------------------------------- 1 | import { Protocol, open, FacilityListType } from '../../dist'; 2 | import { FacilityDataType } from '../../dist/enums/FacilityDataType'; 3 | 4 | /** 5 | * Reading navigational data from an airport (KittyHawk only) 6 | */ 7 | 8 | const AIRPORT_ICAO = 'ENGM'; 9 | 10 | const enum DefinitionID { 11 | FACILITY_AIRPORT = 1000, 12 | FACILITY_VOR = 2000, 13 | } 14 | 15 | const enum REQUEST_ID { 16 | NEW_AIRPORTS = 42, 17 | OLD_AIRPORTS = 43, 18 | } 19 | 20 | let requestId = 100; 21 | 22 | const DESIGNATOR_VALUE = ['', 'L', 'R', 'C', 'WATER', 'A', 'B', 'LAST']; 23 | 24 | open('SimConnect sample client', Protocol.KittyHawk) 25 | .then(({ recvOpen, handle }) => { 26 | console.log('Connected to sim!'); 27 | 28 | // Airport 29 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'OPEN AIRPORT'); // Open 30 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'LATITUDE'); 31 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'LONGITUDE'); 32 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'ALTITUDE'); 33 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'MAGVAR'); 34 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'NAME'); 35 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'N_RUNWAYS'); 36 | // Runway 37 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'OPEN RUNWAY'); // Open 38 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'PRIMARY_NUMBER'); 39 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'PRIMARY_DESIGNATOR'); 40 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'PRIMARY_ILS_ICAO'); 41 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'PRIMARY_ILS_REGION'); 42 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'SECONDARY_NUMBER'); 43 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'SECONDARY_DESIGNATOR'); 44 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'SECONDARY_ILS_ICAO'); 45 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'SECONDARY_ILS_REGION'); 46 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'HEADING'); 47 | handle.addToFacilityDefinition( 48 | DefinitionID.FACILITY_AIRPORT, 49 | 'OPEN PRIMARY_APPROACH_LIGHTS' 50 | ); 51 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'SYSTEM'); 52 | handle.addToFacilityDefinition( 53 | DefinitionID.FACILITY_AIRPORT, 54 | 'CLOSE PRIMARY_APPROACH_LIGHTS' 55 | ); 56 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'OPEN PRIMARY_LEFT_VASI'); 57 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'TYPE'); 58 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'CLOSE PRIMARY_LEFT_VASI'); 59 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'OPEN PRIMARY_RIGHT_VASI'); 60 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'TYPE'); 61 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'CLOSE PRIMARY_RIGHT_VASI'); 62 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'OPEN PRIMARY_OVERRUN'); 63 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'LENGTH'); 64 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'CLOSE PRIMARY_OVERRUN'); 65 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'CLOSE RUNWAY'); // Close 66 | // VOR 67 | handle.addToFacilityDefinition(DefinitionID.FACILITY_VOR, 'OPEN VOR'); // Open 68 | handle.addToFacilityDefinition(DefinitionID.FACILITY_VOR, 'NAME'); 69 | handle.addToFacilityDefinition(DefinitionID.FACILITY_VOR, 'FREQUENCY'); 70 | handle.addToFacilityDefinition(DefinitionID.FACILITY_VOR, 'LOCALIZER'); 71 | handle.addToFacilityDefinition(DefinitionID.FACILITY_VOR, 'CLOSE VOR'); // Close 72 | 73 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'CLOSE AIRPORT'); // Close 74 | 75 | // Request data 76 | handle.requestFacilityData(DefinitionID.FACILITY_AIRPORT, requestId++, AIRPORT_ICAO); 77 | handle.subscribeToFacilitiesEx1( 78 | FacilityListType.AIRPORT, 79 | REQUEST_ID.NEW_AIRPORTS, 80 | REQUEST_ID.OLD_AIRPORTS 81 | ); 82 | 83 | handle.on('facilityData', recvFacilityData => { 84 | switch (recvFacilityData.type) { 85 | case FacilityDataType.AIRPORT: 86 | // Reading order is important! 87 | const lat = recvFacilityData.data.readFloat64(); 88 | const lng = recvFacilityData.data.readFloat64(); 89 | const alt = recvFacilityData.data.readFloat64(); 90 | const magvar = recvFacilityData.data.readFloat32(); 91 | const name = recvFacilityData.data.readString32(); 92 | const nRunways = recvFacilityData.data.readInt32(); 93 | console.log({ lat, lng, alt, magvar, name, nRunways }); 94 | break; 95 | case FacilityDataType.RUNWAY: 96 | // Reading order is important! 97 | const number1 = recvFacilityData.data.readInt32(); 98 | const designator1 = recvFacilityData.data.readInt32(); 99 | const ilsIcao1 = recvFacilityData.data.readString8(); 100 | const ilsRegion1 = recvFacilityData.data.readString8(); 101 | const number2 = recvFacilityData.data.readInt32(); 102 | const designator2 = recvFacilityData.data.readInt32(); 103 | const ilsIcao2 = recvFacilityData.data.readString8(); 104 | const ilsRegion2 = recvFacilityData.data.readString8(); 105 | 106 | const heading = recvFacilityData.data.readFloat32(); 107 | 108 | console.log({ 109 | runway: 110 | number1 + 111 | DESIGNATOR_VALUE[designator1] + 112 | '/' + 113 | number2 + 114 | DESIGNATOR_VALUE[designator2], 115 | heading, 116 | ilsIcao1, 117 | ilsIcao2, 118 | ilsRegion1, 119 | ilsRegion2, 120 | }); 121 | 122 | handle.requestFacilityData( 123 | DefinitionID.FACILITY_VOR, 124 | requestId++, 125 | ilsIcao1, 126 | ilsRegion1, 127 | 'V' 128 | ); 129 | handle.requestFacilityData( 130 | DefinitionID.FACILITY_VOR, 131 | requestId++, 132 | ilsIcao2, 133 | ilsRegion2, 134 | 'V' 135 | ); 136 | break; 137 | case FacilityDataType.VOR: 138 | const vorName = recvFacilityData.data.readString64(); 139 | const frequency = recvFacilityData.data.readInt32() / 1_000_000; 140 | const localizer = recvFacilityData.data.readFloat32(); 141 | console.log({ 142 | vorName, 143 | frequency, 144 | localizer, 145 | }); 146 | break; 147 | // Note that you'll need to keep track of the order of these as they will arrive 148 | // in the same order as they were requested 149 | case FacilityDataType.VASI: 150 | console.log('VASI', recvFacilityData.data.readInt32()); 151 | break; 152 | case FacilityDataType.APPROACHLIGHTS: 153 | console.log('APPROACHLIGHTS', recvFacilityData.data.readInt32()); 154 | break; 155 | case FacilityDataType.PAVEMENT: 156 | console.log('PAVEMENT', recvFacilityData.data.readFloat32()); 157 | break; 158 | } 159 | }); 160 | 161 | handle.on('facilityMinimalList', recvFacilityMinimalList => { 162 | console.log(recvFacilityMinimalList); 163 | }); 164 | 165 | handle.on('facilityDataEnd', recvFacilityDataEnd => { 166 | console.log( 167 | `Finished reading data for request ID: ` + recvFacilityDataEnd.userRequestId 168 | ); 169 | }); 170 | 171 | handle.on('airportList', recvWaypointList => { 172 | switch (recvWaypointList.requestID) { 173 | case REQUEST_ID.NEW_AIRPORTS: 174 | console.log('These airports appeared', recvWaypointList.airports); 175 | break; 176 | case REQUEST_ID.OLD_AIRPORTS: 177 | console.log('These airports disappeared', recvWaypointList.airports); 178 | break; 179 | } 180 | }); 181 | 182 | handle.on('exception', exception => { 183 | console.log(exception); 184 | }); 185 | }) 186 | .catch(error => { 187 | console.log('Failed to connect', error); 188 | }); 189 | -------------------------------------------------------------------------------- /samples/typescript/facilitiesMenu.ts: -------------------------------------------------------------------------------- 1 | import { 2 | open, 3 | Protocol, 4 | RecvEvent, 5 | TextType, 6 | TextResult, 7 | FacilityListType, 8 | NotificationPriority, 9 | } from '../../dist'; 10 | 11 | /** 12 | * SimConnect FaciliitesData sample 13 | * 14 | * Description: 15 | * Ctrl F1 displays the Get Facilities menu on the screen 16 | * Ctrl F2 displays the Subscribe to Faciliites menu on the screen 17 | * 18 | */ 19 | 20 | enum NotificationGroupID { 21 | GROUP0, 22 | } 23 | 24 | enum EventID { 25 | OPEN_MENU_1, 26 | OPEN_MENU_2, 27 | EVENT_MENU_1, 28 | EVENT_MENU_2, 29 | } 30 | 31 | enum InputGroupID { 32 | INPUT0, 33 | } 34 | 35 | enum RequestID { 36 | REQUEST_0, 37 | REQUEST_1, 38 | } 39 | 40 | const GET_FACILITIES_MENU_OPTIONS: string[] = [ 41 | 'Get airport facilities', 42 | 'Get waypoints', 43 | 'Get NDB', 44 | 'Get VOR', 45 | 'Close menu', 46 | ]; 47 | 48 | const SUBSCRIBE_FACILITIES_MENU_OPTIONS: string[] = [ 49 | 'Subscribe to airports', 50 | 'Subscribe to waypoints', 51 | 'Subscribe to NDB', 52 | 'Subscribe to VOR', 53 | 'Unsubscribe to airports', 54 | 'Unsubscribe to waypoints', 55 | 'Unsubscribe to NDB', 56 | 'Unsubscribe to VOR', 57 | 'Close menu', 58 | ]; 59 | 60 | open('My app', Protocol.FSX_SP2) 61 | .then(({ recvOpen, handle }) => { 62 | console.log('Connected: ', recvOpen); 63 | 64 | handle.mapClientEventToSimEvent(EventID.OPEN_MENU_1); 65 | handle.mapClientEventToSimEvent(EventID.OPEN_MENU_2); 66 | handle.addClientEventToNotificationGroup( 67 | NotificationGroupID.GROUP0, 68 | EventID.OPEN_MENU_1, 69 | true 70 | ); 71 | handle.addClientEventToNotificationGroup( 72 | NotificationGroupID.GROUP0, 73 | EventID.OPEN_MENU_2, 74 | true 75 | ); 76 | handle.setNotificationGroupPriority( 77 | NotificationGroupID.GROUP0, 78 | NotificationPriority.HIGHEST 79 | ); 80 | handle.mapInputEventToClientEvent( 81 | InputGroupID.INPUT0, 82 | 'Ctrl+F1', 83 | EventID.OPEN_MENU_1 84 | ); 85 | handle.mapInputEventToClientEvent( 86 | InputGroupID.INPUT0, 87 | 'Ctrl+F2', 88 | EventID.OPEN_MENU_2 89 | ); 90 | handle.setInputGroupState(InputGroupID.INPUT0, true); 91 | 92 | handle.text(TextType.PRINT_RED, 15, 3, 'Facilities Data'); 93 | handle.text( 94 | TextType.PRINT_RED, 95 | 15, 96 | 3, 97 | 'Press Ctrl-F1 for Get Facilities, Ctrl-F2 for Subscribe to Facilities' 98 | ); 99 | 100 | handle.on('event', ({ clientEventId, data }: RecvEvent) => { 101 | switch (clientEventId) { 102 | case EventID.OPEN_MENU_1: 103 | openMenu(GET_FACILITIES_MENU_OPTIONS); 104 | break; 105 | case EventID.OPEN_MENU_2: 106 | openMenu(SUBSCRIBE_FACILITIES_MENU_OPTIONS); 107 | break; 108 | case EventID.EVENT_MENU_1: 109 | { 110 | if (data > TextResult.MENU_SELECT_10) return; 111 | if (data < FacilityListType.COUNT) { 112 | handle.requestFacilitiesList( 113 | data as FacilityListType, 114 | RequestID.REQUEST_0 115 | ); 116 | } 117 | } 118 | break; 119 | case EventID.EVENT_MENU_2: 120 | { 121 | if (data > TextResult.MENU_SELECT_10) return; 122 | if (data < FacilityListType.COUNT) { 123 | handle.subscribeToFacilities( 124 | data as FacilityListType, 125 | RequestID.REQUEST_1 126 | ); 127 | } else if (data < 2 * FacilityListType.COUNT) { 128 | handle.unSubscribeToFacilities( 129 | (data - 130 | FacilityListType.COUNT) as FacilityListType 131 | ); 132 | } 133 | } 134 | break; 135 | } 136 | }); 137 | 138 | function openMenu(items: string[]) { 139 | handle.menu( 140 | 0.0, 141 | EventID.EVENT_MENU_1, 142 | 'SimConnect Facilities Test', 143 | 'Choose which item:', 144 | ...items 145 | ); 146 | } 147 | 148 | handle.on('airportList', (recvAirportList) => { 149 | console.log(recvAirportList.airports.map((ap) => ap.icao).join(',')); 150 | }); 151 | 152 | handle.on('ndbList', (recvNDBList) => { 153 | console.log(recvNDBList.ndbs.map((ndb) => ndb.icao).join(',')); 154 | }); 155 | 156 | handle.on('vorList', (recvVORList) => { 157 | console.log(recvVORList.vors.map((vor) => vor.icao).join(',')); 158 | }); 159 | 160 | handle.on('waypointList', (recvWaypointList) => { 161 | console.log( 162 | recvWaypointList.waypoints.map((wp) => wp.icao).join(',') 163 | ); 164 | }); 165 | }) 166 | .catch((error) => { 167 | console.log('Failed to connect', error); 168 | }); 169 | -------------------------------------------------------------------------------- /samples/typescript/inputEvents.ts: -------------------------------------------------------------------------------- 1 | import { open, Protocol } from '../../dist'; 2 | 3 | /** 4 | * Collects information about all available input events for the aircraft and prints them 5 | */ 6 | open('My app', Protocol.KittyHawk) 7 | .then(({ recvOpen, handle }) => { 8 | console.log('Connected: ', recvOpen); 9 | 10 | let allInputEvents: { 11 | inputEventName: string; 12 | inputEventIdHash: bigint; 13 | params?: string; 14 | }[] = []; 15 | 16 | handle.on('inputEventsList', recvEnumerateInputEvents => { 17 | recvEnumerateInputEvents.inputEventDescriptors.forEach(e => { 18 | allInputEvents.push({ 19 | inputEventName: e.name, 20 | inputEventIdHash: e.inputEventIdHash, 21 | }); 22 | handle.enumerateInputEventParams(e.inputEventIdHash); 23 | }); 24 | }); 25 | 26 | handle.on('enumerateInputEventParams', recvEnumerateInputEventParams => { 27 | // Update the list with the received value 28 | allInputEvents = allInputEvents.map(inputEvent => { 29 | if ( 30 | inputEvent.inputEventIdHash === recvEnumerateInputEventParams.inputEventIdHash 31 | ) { 32 | return { ...inputEvent, params: recvEnumerateInputEventParams.value }; 33 | } else { 34 | return inputEvent; 35 | } 36 | }); 37 | 38 | // Finally, when no input events are missing the params value, print the list 39 | if (allInputEvents.filter(ev => ev.params === undefined).length === 0) { 40 | console.log(allInputEvents); 41 | } 42 | }); 43 | 44 | handle.on('exception', ex => console.log('Exception', ex)); 45 | 46 | handle.enumerateInputEvents(0 /* request-id */); 47 | }) 48 | .catch(error => { 49 | console.log('Failed to connect', error); 50 | }); 51 | -------------------------------------------------------------------------------- /samples/typescript/pmdgCduScreen.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints the text that is currently displayed on the CDU of the PMDG 737 3 | * (captain side), and types "ABC" into the scratchpad using direct event 4 | * triggering. 5 | * 6 | * Assumes that CDU data broadcast is enabled in the PMDG 737 config. 7 | */ 8 | 9 | import { 10 | ClientDataPeriod, 11 | ClientDataRequestFlag, 12 | EventFlag, 13 | open, 14 | Protocol, 15 | RawBuffer, 16 | SimConnectConstants, 17 | } from '../../dist'; 18 | 19 | // These consts were found in PMDG_NG3_SDK.h 20 | const PMDG_NG3_CDU_0_NAME = 'PMDG_NG3_CDU_0'; 21 | const PMDG_NG3_CDU_0_ID = 0x4e473335; 22 | const PMDG_NG3_CDU_0_DEFINITION = 0x4e473338; 23 | const CDU_COLUMNS = 24; 24 | const CDU_ROWS = 14; 25 | const THIRD_PARTY_EVENT_ID_MIN = 0x00011000; 26 | 27 | // Based on the PMDG_NG3_CDU_Screen struct found in PMDG_NG3_SDK.h. There are 3 bytes per character + 1 byte which tells if the CDU is powered. 28 | const SCREEN_STATE_SIZE = CDU_COLUMNS * CDU_ROWS * (1 + 1 + 1) + 1; 29 | 30 | // These event IDs are found in PMDG_NG3_SDK.h 31 | enum PmdgEventID { 32 | CDU_A = THIRD_PARTY_EVENT_ID_MIN + 573, 33 | CDU_B = THIRD_PARTY_EVENT_ID_MIN + 574, 34 | CDU_C = THIRD_PARTY_EVENT_ID_MIN + 575, 35 | } 36 | 37 | enum MyEventID { 38 | CDU_A, 39 | CDU_B, 40 | CDU_C, 41 | } 42 | 43 | const enum RequestID { 44 | CDU_SCREEN_DATA_REQUEST, 45 | } 46 | 47 | (async function readCduScreen() { 48 | const { handle } = await open('My app', Protocol.KittyHawk); 49 | console.log('Connected!'); 50 | 51 | handle.mapClientDataNameToID(PMDG_NG3_CDU_0_NAME, PMDG_NG3_CDU_0_ID); 52 | handle.addToClientDataDefinition(PMDG_NG3_CDU_0_DEFINITION, 0, SCREEN_STATE_SIZE); 53 | handle.requestClientData( 54 | PMDG_NG3_CDU_0_ID, 55 | RequestID.CDU_SCREEN_DATA_REQUEST, 56 | PMDG_NG3_CDU_0_DEFINITION, 57 | ClientDataPeriod.ON_SET, 58 | ClientDataRequestFlag.CLIENT_DATA_REQUEST_FLAG_CHANGED 59 | ); 60 | 61 | handle.on('exception', ex => console.log(ex)); 62 | 63 | handle.on('clientData', recvSimObjectData => { 64 | if (recvSimObjectData.requestID === RequestID.CDU_SCREEN_DATA_REQUEST) { 65 | const { powered, lines } = extractCduScreenState(recvSimObjectData.data); 66 | if (powered) { 67 | console.log(lines.join('\r\n')); 68 | } else { 69 | console.log('Not powered'); 70 | } 71 | } 72 | }); 73 | 74 | // Write "ABC" to the scratchpad 75 | handle.mapClientEventToSimEvent(MyEventID.CDU_A, '#' + PmdgEventID.CDU_A); 76 | handle.mapClientEventToSimEvent(MyEventID.CDU_B, '#' + PmdgEventID.CDU_B); 77 | handle.mapClientEventToSimEvent(MyEventID.CDU_C, '#' + PmdgEventID.CDU_C); 78 | handle.transmitClientEvent( 79 | SimConnectConstants.OBJECT_ID_USER, 80 | MyEventID.CDU_A, 81 | 1, 82 | 0, 83 | EventFlag.EVENT_FLAG_GROUPID_IS_PRIORITY 84 | ); 85 | handle.transmitClientEvent( 86 | SimConnectConstants.OBJECT_ID_USER, 87 | MyEventID.CDU_B, 88 | 1, 89 | 0, 90 | EventFlag.EVENT_FLAG_GROUPID_IS_PRIORITY 91 | ); 92 | handle.transmitClientEvent( 93 | SimConnectConstants.OBJECT_ID_USER, 94 | MyEventID.CDU_C, 95 | 1, 96 | 0, 97 | EventFlag.EVENT_FLAG_GROUPID_IS_PRIORITY 98 | ); 99 | })(); 100 | 101 | function extractCduScreenState(rawBuffer: RawBuffer): { lines: string[]; powered: boolean } { 102 | const screenText: string[] = Array(CDU_ROWS).fill(''); 103 | 104 | for (let col = 0; col < CDU_COLUMNS; col++) { 105 | for (let row = 0; row < CDU_ROWS; row++) { 106 | // I tried readString(1) but that only works with zero-terminated strings, which doesn't seem to be used here 107 | const symbol = rawBuffer.readBytes(1).toString('utf-8'); 108 | // These values are not used in this example 109 | const color = rawBuffer.readBytes(1)[0]; 110 | const flags = rawBuffer.readBytes(1)[0]; 111 | 112 | screenText[row] += symbol; 113 | } 114 | } 115 | const cduIsPowered = rawBuffer.readBytes(1)[0] === 1; 116 | 117 | return { lines: screenText, powered: cduIsPowered }; 118 | } 119 | -------------------------------------------------------------------------------- /samples/typescript/reconnect.ts: -------------------------------------------------------------------------------- 1 | import { open, Protocol } from '../../dist'; 2 | 3 | connectToSim(); 4 | 5 | function connectToSim() { 6 | open('My app', Protocol.FSX_SP2) 7 | .then(({ recvOpen, handle }) => { 8 | console.log('Connected to', recvOpen.applicationName); 9 | 10 | let simIsQuitting = false; 11 | 12 | handle.on('quit', () => { 13 | console.log('The simulator quit. Will try to reconnect.'); 14 | handle.close(); 15 | connectToSim(); 16 | simIsQuitting = true; 17 | }); 18 | handle.on('close', () => { 19 | if (!simIsQuitting) { 20 | // if we are not trying to reconnect already 21 | console.log('Connection closed unexpectedly. Will try to reconnect.'); 22 | handle.close(); 23 | connectToSim(); 24 | } 25 | }); 26 | }) 27 | .catch(error => { 28 | console.log('Failed to connect. Will try again in 5 seconds. Details:', error.message); 29 | setTimeout(connectToSim, 5000); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /samples/typescript/showMetar.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Protocol, 3 | SimConnectDataType, 4 | SimConnectConstants, 5 | SimConnectPeriod, 6 | RecvSimObjectData, 7 | SimConnectException, 8 | RecvException, 9 | RecvWeatherObservation, 10 | open, 11 | } from '../../dist'; 12 | 13 | /** 14 | * A little app to show METAR at current position 15 | */ 16 | 17 | const DEF_ID_POSITION = 0; 18 | const REQ_ID_POSITION = 0; 19 | const REQ_ID_METAR = 1; 20 | 21 | open('My app', Protocol.FSX_SP2) 22 | .then(({ recvOpen, handle }) => { 23 | console.log('Connected: ', recvOpen); 24 | handle.addToDataDefinition( 25 | DEF_ID_POSITION, 26 | 'Plane longitude', 27 | 'degrees', 28 | SimConnectDataType.FLOAT32 29 | ); 30 | handle.addToDataDefinition( 31 | DEF_ID_POSITION, 32 | 'Plane latitude', 33 | 'degrees', 34 | SimConnectDataType.FLOAT32 35 | ); 36 | handle.requestDataOnSimObject( 37 | REQ_ID_POSITION, 38 | DEF_ID_POSITION, 39 | SimConnectConstants.OBJECT_ID_USER, 40 | SimConnectPeriod.ONCE 41 | ); 42 | 43 | handle.on('simObjectData', (recvSimObjectData: RecvSimObjectData) => { 44 | const lon = recvSimObjectData.data.readFloat(); 45 | const lat = recvSimObjectData.data.readFloat(); 46 | console.log(`Requesting METAR at ${lat}, ${lon}`); 47 | handle.weatherRequestObservationAtNearestStation(REQ_ID_METAR, lat, lon); 48 | }); 49 | 50 | handle.on('weatherObservation', (recvWeatherObservation: RecvWeatherObservation) => { 51 | console.log(recvWeatherObservation.metar); 52 | }); 53 | 54 | handle.on('exception', (recvException: RecvException) => { 55 | if (recvException.exception === SimConnectException.WEATHER_UNABLE_TO_GET_OBSERVATION) { 56 | console.log('Unable to get weather observation'); 57 | } else { 58 | console.log(`Unexpected exception: ${recvException.exception}`); 59 | } 60 | }); 61 | }) 62 | .catch(error => { 63 | console.log('Failed to connect', error); 64 | }); 65 | -------------------------------------------------------------------------------- /samples/typescript/simulationVariablesRead.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Protocol, 3 | SimConnectConstants, 4 | SimConnectDataType, 5 | SimConnectPeriod, 6 | open, 7 | readLatLonAlt, 8 | } from '../../dist'; 9 | 10 | /** 11 | * Demonstrates a few system events 12 | */ 13 | 14 | const enum EventID { 15 | PAUSE, 16 | } 17 | 18 | const enum DefinitionID { 19 | LIVE_DATA, 20 | } 21 | 22 | const enum RequestID { 23 | LIVE_DATA, 24 | } 25 | 26 | open('My app', Protocol.FSX_SP2) 27 | .then(({ recvOpen, handle }) => { 28 | console.log('Connected:', recvOpen); 29 | 30 | handle.subscribeToSystemEvent(EventID.PAUSE, 'Pause'); 31 | 32 | handle.addToDataDefinition( 33 | DefinitionID.LIVE_DATA, 34 | 'STRUCT LATLONALT', 35 | null, 36 | SimConnectDataType.LATLONALT 37 | ); 38 | 39 | handle.addToDataDefinition( 40 | DefinitionID.LIVE_DATA, 41 | 'AIRSPEED INDICATED', 42 | 'knots', 43 | SimConnectDataType.INT32 44 | ); 45 | 46 | handle.addToDataDefinition( 47 | DefinitionID.LIVE_DATA, 48 | 'VERTICAL SPEED', 49 | 'Feet per second', 50 | SimConnectDataType.INT32 51 | ); 52 | 53 | handle.addToDataDefinition( 54 | DefinitionID.LIVE_DATA, 55 | 'PLANE HEADING DEGREES TRUE', 56 | 'Degrees', 57 | SimConnectDataType.INT32 58 | ); 59 | 60 | handle.addToDataDefinition( 61 | DefinitionID.LIVE_DATA, 62 | 'LIGHT LANDING', 63 | 'bool', 64 | SimConnectDataType.INT32 65 | ); 66 | 67 | handle.requestDataOnSimObject( 68 | RequestID.LIVE_DATA, 69 | DefinitionID.LIVE_DATA, 70 | SimConnectConstants.OBJECT_ID_USER, 71 | SimConnectPeriod.SIM_FRAME 72 | ); 73 | 74 | handle.on('simObjectData', recvSimObjectData => { 75 | if (recvSimObjectData.requestID === RequestID.LIVE_DATA) { 76 | console.log({ 77 | // Read order is important 78 | position: readLatLonAlt(recvSimObjectData.data), 79 | airspeed: recvSimObjectData.data.readInt32(), 80 | verticalSpeed: recvSimObjectData.data.readInt32(), 81 | heading: recvSimObjectData.data.readInt32(), 82 | landingLight: recvSimObjectData.data.readInt32() === 1, 83 | }); 84 | } 85 | }); 86 | 87 | handle.on('event', recvEvent => { 88 | if (recvEvent.clientEventId === EventID.PAUSE) { 89 | console.log(recvEvent.data === 1 ? 'Paused' : 'Unpaused'); 90 | } 91 | }); 92 | }) 93 | .catch(error => { 94 | console.log('Failed to connect', error); 95 | }); 96 | -------------------------------------------------------------------------------- /samples/typescript/simulationVariablesReadTagged.ts: -------------------------------------------------------------------------------- 1 | import { 2 | open, 3 | Protocol, 4 | readLatLonAlt, 5 | SimConnectConstants, 6 | SimConnectDataType, 7 | SimConnectPeriod, 8 | } from '../../dist'; 9 | import { DataRequestFlag } from '../../dist'; 10 | 11 | const enum DefinitionID { 12 | LIVE_DATA, 13 | } 14 | 15 | const enum RequestID { 16 | LIVE_DATA, 17 | } 18 | 19 | const enum Tag { 20 | LAT_LON_ALT, 21 | AIRSPEED, 22 | VERTICAL_SPEED, 23 | HEADING, 24 | LANDING_LIGHT, 25 | } 26 | 27 | open('My app', Protocol.FSX_SP2) 28 | .then(({ recvOpen, handle }) => { 29 | console.log('Connected:', recvOpen); 30 | 31 | handle.addToDataDefinition( 32 | DefinitionID.LIVE_DATA, 33 | 'STRUCT LATLONALT', 34 | null, 35 | SimConnectDataType.LATLONALT, 36 | 0, 37 | Tag.LAT_LON_ALT 38 | ); 39 | 40 | handle.addToDataDefinition( 41 | DefinitionID.LIVE_DATA, 42 | 'AIRSPEED INDICATED', 43 | 'knots', 44 | SimConnectDataType.INT32, 45 | 0, 46 | Tag.AIRSPEED 47 | ); 48 | 49 | handle.addToDataDefinition( 50 | DefinitionID.LIVE_DATA, 51 | 'VERTICAL SPEED', 52 | 'Feet per second', 53 | SimConnectDataType.INT32, 54 | 0, 55 | Tag.VERTICAL_SPEED 56 | ); 57 | 58 | handle.addToDataDefinition( 59 | DefinitionID.LIVE_DATA, 60 | 'PLANE HEADING DEGREES TRUE', 61 | 'Degrees', 62 | SimConnectDataType.INT32, 63 | 0, 64 | Tag.HEADING 65 | ); 66 | 67 | handle.addToDataDefinition( 68 | DefinitionID.LIVE_DATA, 69 | 'LIGHT LANDING', 70 | 'bool', 71 | SimConnectDataType.INT32, 72 | 0, 73 | Tag.LANDING_LIGHT 74 | ); 75 | 76 | handle.requestDataOnSimObject( 77 | RequestID.LIVE_DATA, 78 | DefinitionID.LIVE_DATA, 79 | SimConnectConstants.OBJECT_ID_USER, 80 | SimConnectPeriod.SIM_FRAME, 81 | DataRequestFlag.DATA_REQUEST_FLAG_CHANGED | DataRequestFlag.DATA_REQUEST_FLAG_TAGGED 82 | ); 83 | 84 | handle.on('simObjectData', recvSimObjectData => { 85 | if (recvSimObjectData.requestID === RequestID.LIVE_DATA) { 86 | let counter = 0; 87 | 88 | while (counter < recvSimObjectData.defineCount) { 89 | /** 90 | * Read all datums in the message. The values are separated by their tag number. 91 | * Important: if not all data are read you might end up with corrupt data. 92 | */ 93 | const datumId = recvSimObjectData.data.readInt32(); 94 | 95 | switch (datumId) { 96 | case Tag.LAT_LON_ALT: 97 | { 98 | const value = readLatLonAlt(recvSimObjectData.data); 99 | console.log('Position', value); 100 | } 101 | break; 102 | case Tag.AIRSPEED: 103 | { 104 | const value = recvSimObjectData.data.readInt32(); 105 | console.log('Airspeed', value); 106 | } 107 | break; 108 | case Tag.HEADING: 109 | { 110 | const value = recvSimObjectData.data.readInt32(); 111 | console.log('Heading', value); 112 | } 113 | break; 114 | case Tag.VERTICAL_SPEED: 115 | { 116 | const value = recvSimObjectData.data.readInt32(); 117 | console.log('Vertical speed', value); 118 | } 119 | break; 120 | case Tag.LANDING_LIGHT: 121 | { 122 | const value = recvSimObjectData.data.readInt32(); 123 | console.log('Landing light on', value); 124 | } 125 | break; 126 | default: { 127 | throw Error(`Unknown datum ID: $datumId`); 128 | } 129 | } 130 | counter++; 131 | } 132 | } 133 | }); 134 | }) 135 | .catch(error => { 136 | console.log('Failed to connect', error); 137 | }); 138 | -------------------------------------------------------------------------------- /samples/typescript/simulationVariablesWrite.ts: -------------------------------------------------------------------------------- 1 | import { 2 | open, 3 | Protocol, 4 | SimConnectConstants, 5 | SimConnectDataType, 6 | SimConnectPeriod, 7 | RawBuffer, 8 | } from '../../dist'; 9 | 10 | const enum DefinitionID { 11 | LIGHTS, 12 | } 13 | 14 | open('Flick lights', Protocol.FSX_SP2) 15 | .then(({ recvOpen, handle }) => { 16 | console.log('Connected:', recvOpen); 17 | 18 | const lights = ['LIGHT LANDING', 'LIGHT LOGO', 'LIGHT TAXI', 'LIGHT WING', 'LIGHT NAV']; 19 | 20 | lights.forEach(lightName => { 21 | handle.addToDataDefinition( 22 | DefinitionID.LIGHTS, 23 | lightName, 24 | 'Bool', 25 | SimConnectDataType.INT32 26 | ); 27 | }); 28 | 29 | let lightsOn = false; 30 | const dataToSet = new RawBuffer(100); 31 | 32 | // Toggle all lights on/off every second 33 | setInterval(() => { 34 | lightsOn = !lightsOn; 35 | 36 | dataToSet.clear(); 37 | lights.forEach(() => { 38 | dataToSet.writeInt32(lightsOn ? 1 : 0); 39 | }); 40 | 41 | handle.setDataOnSimObject(DefinitionID.LIGHTS, SimConnectConstants.OBJECT_ID_USER, { 42 | buffer: dataToSet, 43 | arrayCount: 0, 44 | tagged: false, 45 | }); 46 | }, 1000); 47 | 48 | handle.on('exception', recvException => { 49 | console.log(recvException); 50 | }); 51 | }) 52 | .catch(error => { 53 | console.log('Failed to connect', error); 54 | }); 55 | -------------------------------------------------------------------------------- /samples/typescript/systemEvents.ts: -------------------------------------------------------------------------------- 1 | import { open, Protocol } from '../../dist'; 2 | /** 3 | * Demonstrates a few system events 4 | */ 5 | 6 | const enum EVENT_ID { 7 | PAUSE = 0, 8 | AIRCRAFT_LOADED = 1, 9 | FRAME = 2, 10 | NEW_WEATHER_MODE = 3, 11 | } 12 | 13 | open('My app', Protocol.FSX_SP2) 14 | .then(({ recvOpen, handle }) => { 15 | console.log('Connected: ', recvOpen); 16 | 17 | handle.subscribeToSystemEvent(EVENT_ID.PAUSE, 'Pause'); 18 | handle.subscribeToSystemEvent( 19 | EVENT_ID.AIRCRAFT_LOADED, 20 | 'AircraftLoaded' 21 | ); 22 | handle.subscribeToSystemEvent(EVENT_ID.FRAME, 'Frame'); 23 | handle.subscribeToSystemEvent( 24 | EVENT_ID.NEW_WEATHER_MODE, 25 | 'WeatherModeChanged' 26 | ); 27 | 28 | handle.on('event', (recvEvent) => { 29 | switch (recvEvent.clientEventId) { 30 | case EVENT_ID.PAUSE: 31 | console.log(recvEvent.data === 1 ? 'Paused' : 'Unpaused'); 32 | break; 33 | case EVENT_ID.AIRCRAFT_LOADED: 34 | break; 35 | } 36 | }); 37 | 38 | handle.on('eventFilename', (recvEventFilename) => { 39 | console.log('New aircraft:', recvEventFilename.fileName); 40 | }); 41 | 42 | handle.on('eventWeatherMode', (recvWeatherMode) => { 43 | console.log('New weather mode:', recvWeatherMode.mode); 44 | }); 45 | 46 | handle.on('eventFrame', (recvEventFrame) => { 47 | // console.log('Framerate:', recvEventFrame.frameRate); 48 | }); 49 | }) 50 | .catch((error) => { 51 | console.log('Failed to connect', error); 52 | }); 53 | -------------------------------------------------------------------------------- /samples/typescript/trafficRadar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Clouds

8 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /samples/typescript/trafficRadar/trafficRadar.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConnectionHandle, 3 | open, 4 | Protocol, 5 | RawBuffer, 6 | SimConnectConstants, 7 | SimConnectDataType, 8 | SimConnectPeriod, 9 | SimObjectType, 10 | } from '../../../dist'; 11 | import { client } from './websocketServer'; 12 | 13 | /** 14 | * Lists all aircraft within a 10 km radius 15 | */ 16 | 17 | const enum DefinitionID { 18 | AIRCRAFT_DETAILS, 19 | } 20 | 21 | const enum RequestID { 22 | NEARBY_AIRCRAFT, 23 | MY_POSITION, 24 | } 25 | 26 | open('My app', Protocol.FSX_SP2) 27 | .then(({ recvOpen, handle }) => { 28 | console.log('Connected:', recvOpen); 29 | 30 | registerAircraftDetailsDefinition(handle); 31 | handle.requestDataOnSimObject( 32 | RequestID.MY_POSITION, 33 | DefinitionID.AIRCRAFT_DETAILS, 34 | SimConnectConstants.OBJECT_ID_USER, 35 | SimConnectPeriod.SECOND 36 | ); 37 | 38 | handle.on('simObjectData', recvSimObjectData => { 39 | if (recvSimObjectData.requestID === RequestID.MY_POSITION) { 40 | const aircraftDetails = readAircraftPosition(recvSimObjectData.data); 41 | client?.send( 42 | JSON.stringify({ 43 | type: 'myPosition', 44 | data: aircraftDetails, 45 | }) 46 | ); 47 | 48 | // Request info about all aircraft in a 10 NM radius 49 | handle.requestDataOnSimObjectType( 50 | RequestID.NEARBY_AIRCRAFT, 51 | DefinitionID.AIRCRAFT_DETAILS, 52 | 10000, 53 | SimObjectType.AIRCRAFT 54 | ); 55 | } 56 | }); 57 | 58 | handle.on('simObjectDataByType', recvSimObjectData => { 59 | if (recvSimObjectData.requestID === RequestID.NEARBY_AIRCRAFT) { 60 | if (recvSimObjectData.outOf === 0) return; // There are no sim objects nearby 61 | 62 | const aircraftDetails = readAircraftPosition(recvSimObjectData.data); 63 | client?.send( 64 | JSON.stringify({ 65 | type: 'traffic', 66 | data: aircraftDetails, 67 | }) 68 | ); 69 | } 70 | }); 71 | }) 72 | .catch(error => { 73 | console.log('Failed to connect', error); 74 | }); 75 | 76 | function registerAircraftDetailsDefinition(handle: ConnectionHandle) { 77 | handle.addToDataDefinition( 78 | DefinitionID.AIRCRAFT_DETAILS, 79 | 'Plane Latitude', 80 | 'degrees', 81 | SimConnectDataType.FLOAT64 82 | ); 83 | handle.addToDataDefinition( 84 | DefinitionID.AIRCRAFT_DETAILS, 85 | 'Plane Longitude', 86 | 'degrees', 87 | SimConnectDataType.FLOAT64 88 | ); 89 | handle.addToDataDefinition( 90 | DefinitionID.AIRCRAFT_DETAILS, 91 | 'Indicated Altitude', 92 | 'feet', 93 | SimConnectDataType.INT32 94 | ); 95 | handle.addToDataDefinition( 96 | DefinitionID.AIRCRAFT_DETAILS, 97 | 'TITLE', 98 | null, 99 | SimConnectDataType.STRING128 100 | ); 101 | } 102 | 103 | function readAircraftPosition(rawBuffer: RawBuffer) { 104 | return { 105 | // Read order is important 106 | lat: rawBuffer.readFloat64(), 107 | lng: rawBuffer.readFloat64(), 108 | altitude: rawBuffer.readInt32(), 109 | title: rawBuffer.readString(128), 110 | }; 111 | } 112 | -------------------------------------------------------------------------------- /samples/typescript/trafficRadar/websocketServer.ts: -------------------------------------------------------------------------------- 1 | import childProcess = require('child_process'); 2 | 3 | import WebSocketServer = require('ws'); 4 | /** 5 | * Starts a websocket server and opens the default browser 6 | */ 7 | 8 | const wss = new WebSocketServer({ 9 | port: 8080, 10 | }); 11 | 12 | let client: WebSocket | undefined; 13 | 14 | wss.on('connection', (ws: WebSocket) => { 15 | console.log('WebSocket client connected'); 16 | client = ws; 17 | }); 18 | 19 | const command = 20 | process.platform === 'darwin' 21 | ? 'open' 22 | : process.platform === 'win32' 23 | ? 'start' 24 | : 'xdg-open'; 25 | 26 | childProcess.exec(`${command} file:///${__dirname}/index.html`); 27 | 28 | export { client }; 29 | -------------------------------------------------------------------------------- /samples/typescript/wxradar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Clouds

8 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /samples/typescript/wxradar/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | open, 3 | Protocol, 4 | readLatLonAlt, 5 | RecvCloudState, 6 | SimConnectConstants, 7 | SimConnectDataType, 8 | SimConnectPeriod, 9 | } from '../../../dist'; 10 | 11 | const WebSocketServer = require('ws').Server; 12 | 13 | /** 14 | * Starts a websocket server, opens a browser and renders 15 | * surrounding clouds on a canvas. 16 | */ 17 | 18 | const wss = new WebSocketServer({ 19 | port: 8080, 20 | }); 21 | 22 | let client: WebSocket | undefined; 23 | 24 | wss.on('connection', (ws: WebSocket) => { 25 | console.log('WebSocket client connected'); 26 | client = ws; 27 | }); 28 | 29 | const startCommand = 30 | process.platform === 'darwin' 31 | ? 'open' 32 | : process.platform === 'win32' 33 | ? 'start' 34 | : 'xdg-open'; 35 | 36 | require('child_process').exec( 37 | `${startCommand} file:///${__dirname}/index.html` 38 | ); 39 | 40 | // SimConnect client //////////////////// 41 | 42 | enum DefinitionID { 43 | POSITION, 44 | } 45 | 46 | enum RequestID { 47 | POSITION, 48 | CLOUD_STATE, 49 | } 50 | 51 | const RANGE = 0.5; // Degrees of lat/lng 52 | const ALT_RANGE = 1000; // Feet 53 | 54 | open('Clouds example', Protocol.FSX_SP2) 55 | .then(({ recvOpen, handle }) => { 56 | console.log(recvOpen); 57 | handle.addToDataDefinition( 58 | DefinitionID.POSITION, 59 | 'STRUCT LATLONALT', 60 | null, 61 | SimConnectDataType.LATLONALT 62 | ); 63 | handle.requestDataOnSimObject( 64 | RequestID.POSITION, 65 | DefinitionID.POSITION, 66 | SimConnectConstants.OBJECT_ID_USER, 67 | SimConnectPeriod.SECOND 68 | ); 69 | 70 | handle.on('cloudState', (cloudState: RecvCloudState) => { 71 | client?.send(JSON.stringify(cloudState.data)); 72 | }); 73 | 74 | handle.on('simObjectData', (data) => { 75 | if (data.requestID === RequestID.POSITION) { 76 | const pos = readLatLonAlt(data.data); 77 | console.log(pos); 78 | const altFt = pos.altitude * 3.2808; 79 | 80 | handle.weatherRequestCloudState( 81 | RequestID.CLOUD_STATE, 82 | pos.latitude - RANGE, 83 | pos.longitude - RANGE, 84 | altFt - ALT_RANGE, 85 | pos.latitude + RANGE, 86 | pos.longitude + RANGE, 87 | altFt + ALT_RANGE 88 | ); 89 | } 90 | }); 91 | }) 92 | .catch((error) => { 93 | console.log('Failed to connect', error); 94 | }); 95 | -------------------------------------------------------------------------------- /src/RawBuffer.ts: -------------------------------------------------------------------------------- 1 | import ByteBuffer = require('bytebuffer'); 2 | 3 | class RawBuffer { 4 | private readonly buffer: ByteBuffer; 5 | 6 | constructor(b: Buffer | number) { 7 | if (typeof b === 'number') { 8 | this.buffer = ByteBuffer.allocate(b).LE(true); 9 | } else { 10 | this.buffer = ByteBuffer.wrap(b).LE(true); 11 | } 12 | } 13 | 14 | clear() { 15 | this.buffer.clear(); 16 | } 17 | 18 | setOffset(offset: number) { 19 | this.buffer.offset = offset; 20 | } 21 | 22 | getOffset(): number { 23 | return this.buffer.offset; 24 | } 25 | 26 | getBuffer(): Buffer { 27 | this.buffer.flip(); 28 | return this.buffer.toBuffer(true); 29 | } 30 | 31 | write(bytes: Buffer) { 32 | this.buffer.append(bytes); 33 | } 34 | 35 | writeByte(byte: number) { 36 | this.buffer.writeByte(byte); 37 | } 38 | 39 | readBytes(length: number): Buffer { 40 | const bytes = this.buffer.readBytes(length).copy(); 41 | return bytes.toBuffer(); 42 | } 43 | 44 | readInt16(): number { 45 | return this.buffer.readInt16(); 46 | } 47 | 48 | readInt32(): number { 49 | return this.buffer.readInt32(); 50 | } 51 | 52 | readUint32(): number { 53 | return this.buffer.readUint32(); 54 | } 55 | 56 | writeInt16(value: number, offset?: number) { 57 | this.buffer.writeInt16(value, offset); 58 | } 59 | 60 | /** @deprecated use readInt32() instead */ 61 | readInt = this.readInt32; 62 | 63 | writeInt32(value: number, offset?: number) { 64 | this.buffer.writeInt32(value, offset); 65 | } 66 | 67 | /** @deprecated use writeInt32() instead */ 68 | writeInt = this.writeInt32; 69 | 70 | readInt64(): number { 71 | return this.buffer.readInt64().toNumber(); 72 | } 73 | 74 | readUint64(): bigint { 75 | return BigInt(this.buffer.readUint64().toString(10)); 76 | } 77 | 78 | /** @deprecated use readInt64() instead */ 79 | readLong = this.readInt64; 80 | 81 | writeInt64(value: number) { 82 | this.buffer.writeInt64(value); 83 | } 84 | 85 | writeUint32(value: number, offset?: number) { 86 | this.buffer.writeUint64(value, offset); 87 | } 88 | 89 | writeUint64(value: bigint, offset?: number) { 90 | const buffer = Buffer.alloc(8); 91 | buffer.writeBigUint64LE(value); 92 | this.buffer.append(buffer, undefined, offset); 93 | } 94 | 95 | /** @deprecated use writeInt64() instead */ 96 | writeLong = this.writeInt64; 97 | 98 | readFloat32(): number { 99 | return this.buffer.readFloat32(); 100 | } 101 | 102 | /** @deprecated use readFloat32() instead */ 103 | readFloat = this.readFloat32; 104 | 105 | writeFloat32(value: number) { 106 | this.buffer.writeFloat32(value); 107 | } 108 | 109 | /** @deprecated use writeFloat32() instead */ 110 | writeFloat = this.writeFloat32; 111 | 112 | readFloat64() { 113 | return this.buffer.readFloat64(); 114 | } 115 | 116 | /** @deprecated use readFloat64() instead */ 117 | readDouble = this.readFloat64; 118 | 119 | writeFloat64(value: number) { 120 | this.buffer.writeFloat64(value); 121 | } 122 | 123 | /** @deprecated use writeFloat64() instead */ 124 | writeDouble = this.writeFloat64; 125 | 126 | writeString(value: string, fixedLength?: number) { 127 | putString(this.buffer, value, fixedLength || value.length); 128 | } 129 | 130 | readString8() { 131 | return makeString(this.buffer, 8); 132 | } 133 | 134 | writeString8(value: string) { 135 | putString(this.buffer, value, 8); 136 | } 137 | 138 | writeString30(value: string) { 139 | putString(this.buffer, value, 30); 140 | } 141 | 142 | readString32() { 143 | return makeString(this.buffer, 32); 144 | } 145 | 146 | writeString32(value: string) { 147 | putString(this.buffer, value, 32); 148 | } 149 | 150 | readString64() { 151 | return makeString(this.buffer, 64); 152 | } 153 | 154 | writeString64(value: string) { 155 | putString(this.buffer, value, 64); 156 | } 157 | 158 | readString128() { 159 | return makeString(this.buffer, 128); 160 | } 161 | 162 | writeString128(value: string) { 163 | putString(this.buffer, value, 128); 164 | } 165 | 166 | readString256() { 167 | return makeString(this.buffer, 256); 168 | } 169 | 170 | writeString256(value: string | null) { 171 | putString(this.buffer, value, 256); 172 | } 173 | 174 | readString260() { 175 | return makeString(this.buffer, 260); 176 | } 177 | 178 | writeString260(value: string) { 179 | putString(this.buffer, value, 260); 180 | } 181 | 182 | readStringV() { 183 | let bytesRead = 0; 184 | let strLen = 0; 185 | let endFound = false; 186 | while (this.buffer.offset < this.buffer.limit) { 187 | const currentByte = this.buffer.readByte(); 188 | bytesRead++; 189 | if (endFound && currentByte !== 0) { 190 | break; // Reached beginning of new value 191 | } else if (currentByte === 0) { 192 | endFound = true; 193 | } 194 | strLen++; 195 | } 196 | // Reset offset so we can read the same bytes later 197 | this.buffer.offset -= bytesRead; 198 | return makeString(this.buffer, strLen); 199 | } 200 | 201 | readString(length: number) { 202 | return makeString(this.buffer, length); 203 | } 204 | 205 | remaining() { 206 | return this.buffer.remaining(); 207 | } 208 | } 209 | 210 | function makeString(bf: ByteBuffer, expectedLength: number) { 211 | const content = bf.readCString(bf.offset); 212 | bf.skip(expectedLength); 213 | return content.string; 214 | } 215 | 216 | function putString(bf: ByteBuffer, s: string | null, fixed: number) { 217 | const value = s === null ? '' : s; 218 | const bytes = Buffer.from(value, 'utf-8'); 219 | bf.append(bytes); 220 | if (bytes.length < fixed) { 221 | for (let i = 0; i < fixed - bytes.length; i++) { 222 | bf.writeByte(0x00); 223 | } 224 | } 225 | } 226 | 227 | export { RawBuffer }; 228 | -------------------------------------------------------------------------------- /src/SimConnectConstants.ts: -------------------------------------------------------------------------------- 1 | // Import the necessary type 2 | import { ObjectId } from './Types'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-namespace 5 | export namespace SimConnectConstants { 6 | /** Specify the user aircraft in {@link SimConnectRecvEvents#simObjectDataByType} and {@link SimConnectConnection.requestDataOnSimObject} */ 7 | export const OBJECT_ID_USER: ObjectId = 0 as ObjectId; 8 | 9 | export const UNUSED = 0xffffffff; 10 | 11 | /** current (and max) protocol version supported by this implementation of jsimconnect */ 12 | export const PROTO_VERSION = 4; 13 | 14 | export const RECEIVE_SIZE = 65536; 15 | 16 | /** indicates that the value for the camera should be taken unmodified from the reference point. */ 17 | // TODO: CAMERA_IGNORE_FIELD : Float.MAX_VALUE, //Used to tell the Camera API to NOT modify the value in this part of the argument. 18 | 19 | // Weather observations Metar strings 20 | export const MAX_METAR_LENGTH = 2000; 21 | 22 | // Maximum thermal size is 100 km. 23 | export const MAX_THERMAL_SIZE = 100000; 24 | export const MAX_THERMAL_RATE = 1000; 25 | 26 | // SIMCONNECT_DATA_INITPOSITION.Airspeed 27 | /** The aircraft's design cruising speed. */ 28 | export const INITPOSITION_AIRSPEED_CRUISE = -1; 29 | /** Maintain the current airspeed. */ 30 | export const INITPOSITION_AIRSPEED_KEEP = -2; 31 | 32 | /** a MS Windows constant */ 33 | export const MAX_PATH = 260; 34 | 35 | /** Specifies requested speed is valid. */ 36 | export const WAYPOINT_SPEED_REQUESTED = 0x04; 37 | /** Specifies requested throttle percentage is valid. */ 38 | export const WAYPOINT_THROTTLE_REQUESTED = 0x08; 39 | /** Specifies that the vertical should be calculated to reach the required speed when crossing the waypoint. */ 40 | export const WAYPOINT_COMPUTE_VERTICAL_SPEED = 0x10; 41 | /** Specifies the altitude specified is AGL (above ground level). */ 42 | export const WAYPOINT_ALTITUDE_IS_AGL = 0x20; 43 | /** Specifies the waypoint should be on the ground. Make sure this flag is set if the aircraft is to taxi to this point. */ 44 | export const WAYPOINT_ON_GROUND = 0x100000; 45 | /** Specifies that the aircraft should back up to this waypoint. This is only valid on the first waypoint. */ 46 | export const WAYPOINT_REVERSE = 0x200000; 47 | /** Specifies that the next waypoint is the first waypoint. This is only valid on the last waypoint. */ 48 | export const WAYPOINT_WRAP_TO_FIRST = 0x400000; 49 | 50 | /** When subscribed to event MissionCompleted */ 51 | export const MISSION_FAILED = 0; 52 | /** When subscribed to event MissionCompleted */ 53 | export const MISSION_CRASHED = 1; 54 | /** When subscribed to event MissionCompleted */ 55 | export const MISSION_SUCCEEDED = 2; 56 | 57 | /** When subscribed to event View, 2D Panels in cockpit view */ 58 | export const VIEW_SYSTEM_EVENT_DATA_COCKPIT_2D = 0x00000001; 59 | /** When subscribed to event View, Virtual (3D) panels in cockpit view */ 60 | export const VIEW_SYSTEM_EVENT_DATA_COCKPIT_VIRTUAL = 0x00000002; 61 | /** When subscribed to event View, Orthogonal (Map) view */ 62 | export const VIEW_SYSTEM_EVENT_DATA_ORTHOGONAL = 0x00000004; 63 | 64 | /** When subsribed to event Sound */ 65 | export const SOUND_SYSTEM_EVENT_DATA_MASTER = 1; 66 | 67 | /** unknow group received */ 68 | export const UNKNOWN_GROUP = 0xffffffff; 69 | 70 | /** automatically compute offset of the ClientData variable */ 71 | export const CLIENTDATAOFFSET_AUTO = -1; 72 | 73 | /** Specifies that the user has selected the menu item. */ 74 | export const TEXT_RESULT_MENU_SELECT_1 = 0; 75 | /** Specifies that the user has selected the menu item. */ 76 | export const TEXT_RESULT_MENU_SELECT_2 = 1; 77 | /** Specifies that the user has selected the menu item. */ 78 | export const TEXT_RESULT_MENU_SELECT_3 = 2; 79 | /** Specifies that the user has selected the menu item. */ 80 | export const TEXT_RESULT_MENU_SELECT_4 = 3; 81 | /** Specifies that the user has selected the menu item. */ 82 | export const TEXT_RESULT_MENU_SELECT_5 = 4; 83 | /** Specifies that the user has selected the menu item. */ 84 | export const TEXT_RESULT_MENU_SELECT_6 = 5; 85 | /** Specifies that the user has selected the menu item. */ 86 | export const TEXT_RESULT_MENU_SELECT_7 = 6; 87 | /** Specifies that the user has selected the menu item. */ 88 | export const TEXT_RESULT_MENU_SELECT_8 = 7; 89 | /** Specifies that the user has selected the menu item. */ 90 | export const TEXT_RESULT_MENU_SELECT_9 = 8; 91 | /** Specifies that the user has selected the menu item. */ 92 | export const TEXT_RESULT_MENU_SELECT_10 = 9; 93 | /** Specifies that the menu or text identified by the EventID is now on display. */ 94 | export const TEXT_RESULT_DISPLAYED = 0x00010000; 95 | /** Specifies that the menu or text identified by the EventID is waiting in a queue. */ 96 | export const TEXT_RESULT_QUEUED = 0x00010001; 97 | /** Specifies that the menu or text identified by the EventID has been removed from the queue. */ 98 | export const TEXT_RESULT_REMOVED = 0x00010002; 99 | /** Specifies that the menu or text identified by the EventID has been replaced in the queue. */ 100 | export const TEXT_RESULT_REPLACED = 0x00010003; 101 | /** Specifies that the menu or text identified by the EventID has timed-out and is no longer on display. */ 102 | export const TEXT_RESULT_TIMEOUT = 0x00010004; 103 | 104 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */ 105 | export const CLIENT_DATA_TYPE_INT8 = -1; 106 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */ 107 | export const CLIENT_DATA_TYPE_INT16 = -2; 108 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */ 109 | export const CLIENT_DATA_TYPE_INT32 = -3; 110 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */ 111 | export const CLIENT_DATA_TYPE_INT64 = -4; 112 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */ 113 | export const CLIENT_DATA_TYPE_FLOAT32 = -5; 114 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */ 115 | export const CLIENT_DATA_TYPE_FLOAT64 = -6; 116 | } 117 | 118 | module.exports = { 119 | SimConnectConstants, 120 | }; 121 | -------------------------------------------------------------------------------- /src/SimConnectPacketBuilder.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from './RawBuffer'; 2 | import { Protocol } from './enums/Protocol'; 3 | 4 | export class SimConnectPacketBuilder { 5 | private readonly packetContent: RawBuffer; 6 | 7 | constructor(packetTypeId: number, protocol: Protocol, packetDataBuffer?: RawBuffer) { 8 | packetDataBuffer?.clear(); // Prepare for new message 9 | /** 10 | * Packet header content (16 bytes): 11 | * 0-3 packet size (set later) 12 | * 4-7 protocol 13 | * 8-11 packet type / SimConnect function 14 | * 12-15 packet id (set later) 15 | */ 16 | this.packetContent = packetDataBuffer || new RawBuffer(256); 17 | this.packetContent.writeInt32(protocol, 4); 18 | this.packetContent.writeInt32(0xf0000000 | packetTypeId, 8); 19 | this.packetContent.setOffset(16); 20 | } 21 | 22 | putFloat32(value: number) { 23 | this.packetContent.writeFloat32(value); 24 | return this; 25 | } 26 | 27 | putFloat64(value: number) { 28 | this.packetContent.writeFloat64(value); 29 | return this; 30 | } 31 | 32 | putString(value: string, fixedLength?: number) { 33 | this.packetContent.writeString(value, fixedLength); 34 | return this; 35 | } 36 | 37 | putString256(value: string | null) { 38 | this.packetContent.writeString256(value); 39 | return this; 40 | } 41 | 42 | putInt16(value: number, offset?: number) { 43 | this.packetContent.writeInt16(value, offset); 44 | return this; 45 | } 46 | 47 | putInt32(value: number, offset?: number) { 48 | this.packetContent.writeInt32(value, offset); 49 | return this; 50 | } 51 | 52 | putUint32(value: number, offset?: number) { 53 | this.packetContent.writeUint32(value, offset); 54 | return this; 55 | } 56 | 57 | putUint64(value: bigint, offset?: number) { 58 | this.packetContent.writeUint64(value, offset); 59 | return this; 60 | } 61 | 62 | putByte(value: number) { 63 | this.packetContent.writeByte(value); 64 | return this; 65 | } 66 | 67 | putBytes(value: Buffer) { 68 | this.packetContent.write(value); 69 | return this; 70 | } 71 | 72 | getRawBuffer(): RawBuffer { 73 | return this.packetContent; 74 | } 75 | 76 | /** 77 | * Finalize 78 | * @param sendId - of packet (can be used to identify packet when exception event occurs) 79 | */ 80 | build(sendId: number): Buffer { 81 | const packetSize = this.packetContent.getOffset(); 82 | 83 | // Finish packet header 84 | this.packetContent.writeInt32(packetSize, 0); 85 | this.packetContent.writeInt32(sendId, 12); 86 | 87 | return this.packetContent.getBuffer(); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/SimConnectSocket.ts: -------------------------------------------------------------------------------- 1 | import { Socket } from 'net'; 2 | import { Duplex } from 'stream'; 3 | import { RawBuffer } from './RawBuffer'; 4 | import { ConnectionParameters } from './connectionParameters'; 5 | 6 | const HEADER_LENGTH = 4; 7 | 8 | enum RecvID { 9 | ID_NULL, 10 | ID_EXCEPTION, 11 | ID_OPEN, 12 | ID_QUIT, 13 | ID_EVENT, 14 | ID_EVENT_OBJECT_ADDREMOVE, 15 | ID_EVENT_FILENAME, 16 | ID_EVENT_FRAME, 17 | ID_SIMOBJECT_DATA, 18 | ID_SIMOBJECT_DATA_BYTYPE, 19 | ID_WEATHER_OBSERVATION, 20 | ID_CLOUD_STATE, 21 | ID_ASSIGNED_OBJECT_ID, 22 | ID_RESERVED_KEY, 23 | ID_CUSTOM_ACTION, 24 | ID_SYSTEM_STATE, 25 | ID_CLIENT_DATA, 26 | ID_EVENT_WEATHER_MODE, 27 | ID_AIRPORT_LIST, 28 | ID_VOR_LIST, 29 | ID_NDB_LIST, 30 | ID_WAYPOINT_LIST, 31 | ID_EVENT_MULTIPLAYER_SERVER_STARTED, 32 | ID_EVENT_MULTIPLAYER_CLIENT_STARTED, 33 | ID_EVENT_MULTIPLAYER_SESSION_ENDED, 34 | ID_EVENT_RACE_END, 35 | ID_EVENT_RACE_LAP, 36 | // KittyHawk: 37 | ID_EVENT_EX1, 38 | ID_FACILITY_DATA, 39 | ID_FACILITY_DATA_END, 40 | ID_FACILITY_MINIMAL_LIST, 41 | ID_JETWAY_DATA, 42 | ID_CONTROLLERS_LIST, 43 | ID_ACTION_CALLBACK, 44 | ID_ENUMERATE_INPUT_EVENTS, 45 | ID_GET_INPUT_EVENT, 46 | ID_SUBSCRIBE_INPUT_EVENT, 47 | ID_ENUMERATE_INPUT_EVENT_PARAMS, 48 | } 49 | 50 | interface SimConnectMessage { 51 | protocolVersion: number; 52 | packetTypeId: RecvID; 53 | data: RawBuffer; 54 | } 55 | 56 | /** 57 | * For connecting, reading and writing to the SimConnect server. 58 | * The emitted "data"-event contains a SimConnectMessage-object. 59 | * Inspired by https://www.derpturkey.com/extending-tcp-socket-in-node-js/ 60 | */ 61 | class SimConnectSocket extends Duplex { 62 | private readonly _socket: Socket; 63 | 64 | private _dataBuffer: Buffer; 65 | 66 | private _readingPaused: boolean; 67 | 68 | constructor() { 69 | super({ objectMode: true }); 70 | this._dataBuffer = Buffer.from([]); 71 | this._readingPaused = false; 72 | this._socket = new Socket(); 73 | this._socket.setNoDelay(false); 74 | this._wrapSocket(); 75 | } 76 | 77 | connect(address: ConnectionParameters) { 78 | switch (address.type) { 79 | case 'pipe': 80 | this._socket.connect(address.address); 81 | break; 82 | case 'ipv4': 83 | this._socket.connect(address.port, address.host); 84 | break; 85 | default: 86 | throw Error('Unsupported address type. Must be "ipv4" or "pipe"'); 87 | } 88 | } 89 | 90 | close() { 91 | this._socket.destroy(); 92 | } 93 | 94 | private _wrapSocket() { 95 | this._socket.on('close', hadError => this.emit('close', hadError)); 96 | this._socket.on('connect', () => this.emit('connect')); 97 | this._socket.on('drain', () => this.emit('drain')); 98 | this._socket.on('end', () => this.emit('end')); 99 | this._socket.on('error', err => this.emit('error', err)); 100 | this._socket.on('lookup', (err, address, family, host) => this.emit('lookup', err, address, family, host)); // prettier-ignore 101 | this._socket.on('ready', () => this.emit('ready')); 102 | this._socket.on('timeout', () => this.emit('timeout')); 103 | 104 | this._socket.on('readable', this._onReadable.bind(this)); 105 | } 106 | 107 | private _onReadable() { 108 | while (!this._readingPaused) { 109 | const chunk: Buffer | null = this._socket.read(); 110 | if (chunk === null) break; 111 | 112 | this._dataBuffer = Buffer.concat([this._dataBuffer, chunk]); 113 | 114 | while (this._dataBuffer.length >= HEADER_LENGTH) { 115 | const totalMessageSize: number = this._dataBuffer.readInt32LE(0); 116 | 117 | if (this._dataBuffer.length >= totalMessageSize) { 118 | const messageBody: Buffer = this._dataBuffer.slice( 119 | HEADER_LENGTH, 120 | totalMessageSize 121 | ); 122 | 123 | const simConnectMessage: SimConnectMessage = { 124 | protocolVersion: messageBody.readInt32LE(0), 125 | packetTypeId: messageBody.readInt32LE(4), 126 | data: new RawBuffer(messageBody.slice(8)), 127 | }; 128 | 129 | const pushOk = this.push(simConnectMessage); 130 | 131 | if (!pushOk) { 132 | this._readingPaused = true; 133 | break; // Pause reading if consumer is slow 134 | } 135 | 136 | this._dataBuffer = this._dataBuffer.slice(totalMessageSize); // Remove processed message from the buffer 137 | } else { 138 | break; // Not enough data for a complete SimConnect message, break out of the loop 139 | } 140 | } 141 | } 142 | } 143 | 144 | _read() { 145 | this._readingPaused = false; 146 | setImmediate(this._onReadable.bind(this)); 147 | } 148 | 149 | _write(data: Buffer, encoding: BufferEncoding, cb: (error?: Error | null) => void) { 150 | this._socket.write(data, encoding, cb); 151 | } 152 | } 153 | 154 | export { SimConnectSocket, RecvID, SimConnectMessage, ConnectionParameters }; 155 | -------------------------------------------------------------------------------- /src/Types.ts: -------------------------------------------------------------------------------- 1 | export type DataRequestId = number; 2 | export type DataDefinitionId = number; 3 | export type ClientDataDefinitionId = number; 4 | export type ObjectId = number; 5 | export type ClientEventId = number; 6 | export type NotificationGroupId = number; 7 | export type InputGroupId = number; 8 | export type ClientDataId = number; 9 | -------------------------------------------------------------------------------- /src/connectionParameters.ts: -------------------------------------------------------------------------------- 1 | import * as Path from 'path'; 2 | import * as os from 'os'; 3 | import debug from 'debug'; 4 | import { readIniFile } from './utils/ini'; 5 | import { readRegistryValue } from './utils/registry'; 6 | import { checkIfNamedPipeExist } from './utils/network'; 7 | 8 | const logger = debug('node-simconnect'); 9 | 10 | async function findSimConnectPortIPv4(): Promise { 11 | try { 12 | const port = await readRegistryValue( 13 | 'HKCU\\Software\\Microsoft\\Microsoft Games\\Flight Simulator', 14 | 'SimConnect_Port_IPv4' 15 | ); 16 | if (!port) { 17 | throw new Error('Could not find SimConnect_Port_IPv4 in the Windows registry'); 18 | } 19 | return parseInt(port, 10); 20 | } catch { 21 | return 2048; 22 | } 23 | } 24 | 25 | export type ConnectionParameters = 26 | | { type: 'pipe'; address: string } 27 | | { type: 'ipv4'; host: string; port: number }; 28 | 29 | async function readNetworkConfigFromSimConnectCfg( 30 | folderPath: string, 31 | index?: number 32 | ): Promise { 33 | const filePath = Path.join(folderPath, 'SimConnect.cfg'); 34 | 35 | let fullCfg; 36 | try { 37 | // SimConnect.cfg uses the INI fileformat 38 | fullCfg = await readIniFile(filePath); 39 | } catch (e) { 40 | logger('Could not read SimConnect.cfg due to to the following error:', e); 41 | return undefined; 42 | } 43 | 44 | if (fullCfg.SimConnect === undefined) { 45 | throw new Error(`Invalid SimConnect.cfg file: ${filePath}`); 46 | } 47 | 48 | const indexStr = index !== undefined ? index.toString(10) : '0'; 49 | const cfg = fullCfg.SimConnect[indexStr] ? fullCfg.SimConnect[indexStr] : fullCfg.SimConnect; 50 | 51 | if (cfg.Protocol === undefined || cfg.Address === undefined || cfg.Port === undefined) { 52 | throw new Error(`The loaded SimConnect.cfg (${filePath}) is missing required parameters.`); 53 | } else if (cfg.Protocol && cfg.Protocol.toUpperCase() !== 'IPV4') { 54 | throw new Error('Only the Ipv4 protocol is supported at the moment'); 55 | } 56 | 57 | return { 58 | type: 'ipv4', 59 | host: cfg.Address, 60 | port: parseInt(cfg.Port, 10), 61 | }; 62 | } 63 | 64 | async function autodetectServerAddress(cfgIndex?: number): Promise { 65 | // Check for SimConnect.cfg in current dir 66 | const localConfig = await readNetworkConfigFromSimConnectCfg(process.cwd(), cfgIndex); 67 | if (localConfig) return localConfig; 68 | 69 | const homeConfig = await readNetworkConfigFromSimConnectCfg(os.homedir(), cfgIndex); 70 | if (homeConfig) return homeConfig; 71 | 72 | if (cfgIndex !== undefined) { 73 | throw new Error( 74 | `No SimConnect.cfg file containing the given config index ${cfgIndex} was found` 75 | ); 76 | } 77 | 78 | // Check if named pipe exist 79 | const PIPE = '\\\\.\\pipe\\Microsoft Flight Simulator\\SimConnect'; 80 | const msfsSimconnectPipeOk = await checkIfNamedPipeExist(PIPE); 81 | if (msfsSimconnectPipeOk) { 82 | return { type: 'pipe', address: PIPE }; 83 | } 84 | 85 | // Read port number from Windows registry 86 | const ipv4port = await findSimConnectPortIPv4(); 87 | return { type: 'ipv4', host: 'localhost', port: ipv4port }; 88 | } 89 | 90 | export { autodetectServerAddress }; 91 | -------------------------------------------------------------------------------- /src/datastructures/ControllerItem.ts: -------------------------------------------------------------------------------- 1 | import { VersionBaseType } from '../dto'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | 4 | export class ControllerItem { 5 | deviceName: string; 6 | 7 | deviceId: number; 8 | 9 | productId: number; 10 | 11 | compositeId: number; 12 | 13 | hardwareVersion: VersionBaseType; 14 | 15 | constructor(data: RawBuffer) { 16 | this.deviceName = data.readString256(); 17 | this.deviceId = data.readInt32(); 18 | this.productId = data.readInt32(); 19 | this.compositeId = data.readInt32(); 20 | this.hardwareVersion = new VersionBaseType(data); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/datastructures/FacilityAirport.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | 3 | export class FacilityAirport { 4 | icao: string; 5 | 6 | region: string; 7 | 8 | latitude: number; 9 | 10 | longitude: number; 11 | 12 | altitude: number; 13 | 14 | constructor(data: RawBuffer) { 15 | this.icao = data.readString(6); 16 | this.region = data.readString(3); 17 | this.latitude = data.readFloat64(); 18 | this.longitude = data.readFloat64(); 19 | this.altitude = data.readFloat64(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/datastructures/FacilityMinimal.ts: -------------------------------------------------------------------------------- 1 | import { Icao, LatLonAlt, readLatLonAlt } from '../dto'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | 4 | export class FacilityMinimal { 5 | icao: Icao; 6 | 7 | latLonAlt: LatLonAlt; 8 | 9 | constructor(data: RawBuffer) { 10 | this.icao = new Icao(data); 11 | this.latLonAlt = readLatLonAlt(data); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/datastructures/FacilityNDB.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { FacilityWaypoint } from './FacilityWaypoint'; 3 | 4 | export class FacilityNDB extends FacilityWaypoint { 5 | frequency: number; 6 | 7 | constructor(data: RawBuffer) { 8 | super(data); 9 | this.frequency = data.readInt32(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/datastructures/FacilityVOR.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { FacilityNDB } from './FacilityNDB'; 3 | 4 | export class FacilityVOR extends FacilityNDB { 5 | public static HAS_NAV_SIGNAL = 0x00000001; 6 | 7 | public static HAS_LOCALIZER = 0x00000002; 8 | 9 | public static HAS_GLIDE_SLOPE = 0x00000004; 10 | 11 | public static HAS_DME = 0x00000008; 12 | 13 | flags: number; 14 | 15 | localizer: number; 16 | 17 | glideLat: number; 18 | 19 | glideLon: number; 20 | 21 | glideAlt: number; 22 | 23 | glideSlipeAngle: number; 24 | 25 | constructor(data: RawBuffer) { 26 | super(data); 27 | this.flags = data.readInt32(); 28 | this.localizer = data.readFloat32(); 29 | this.glideLat = data.readFloat64(); 30 | this.glideLon = data.readFloat64(); 31 | this.glideAlt = data.readFloat64(); 32 | this.glideSlipeAngle = data.readFloat32(); 33 | } 34 | 35 | hasFlag(flag: number): boolean { 36 | return (this.flags & flag) !== 0; 37 | } 38 | 39 | hasNavSignal(): boolean { 40 | return this.hasFlag(FacilityVOR.HAS_NAV_SIGNAL); 41 | } 42 | 43 | hasLocalizer(): boolean { 44 | return this.hasFlag(FacilityVOR.HAS_LOCALIZER); 45 | } 46 | 47 | hasGlideSlope(): boolean { 48 | return this.hasFlag(FacilityVOR.HAS_GLIDE_SLOPE); 49 | } 50 | 51 | hasDME(): boolean { 52 | return this.hasFlag(FacilityVOR.HAS_DME); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/datastructures/FacilityWaypoint.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { FacilityAirport } from './FacilityAirport'; 3 | 4 | export class FacilityWaypoint extends FacilityAirport { 5 | magVar: number; 6 | 7 | constructor(data: RawBuffer) { 8 | super(data); 9 | this.magVar = data.readFloat32(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/datastructures/InputEventDescriptor.ts: -------------------------------------------------------------------------------- 1 | import { InputEventType } from '../enums/InputEventType'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | 4 | export class InputEventDescriptor { 5 | name: string; 6 | 7 | inputEventIdHash: bigint; 8 | 9 | type: InputEventType; 10 | 11 | constructor(data: RawBuffer) { 12 | this.name = data.readString64(); 13 | this.inputEventIdHash = data.readUint64(); 14 | this.type = data.readUint32() as InputEventType; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/datastructures/JetwayData.ts: -------------------------------------------------------------------------------- 1 | import { LatLonAlt } from '../dto/LatLonAlt'; 2 | import { PBH } from '../dto/PBH'; 3 | import { JetwayStatus } from '../enums/JetwayStatus'; 4 | import { XYZ } from '../dto/XYZ'; 5 | import { ObjectId } from '../Types'; 6 | import { RawBuffer } from '../RawBuffer'; 7 | import { readLatLonAlt, readPBH, readXYZ } from '../dto/bufferHelpers'; 8 | 9 | export class JetwayData { 10 | airportIcao: string; 11 | 12 | parkingIndex: number; 13 | 14 | latLngAlt: LatLonAlt; 15 | 16 | pbh: PBH; 17 | 18 | status: JetwayStatus; 19 | 20 | door: number; 21 | 22 | exitDoorRelativePos: XYZ; 23 | 24 | mainHandlePos: XYZ; 25 | 26 | secondaryHandle: XYZ; 27 | 28 | wheelGroundLock: XYZ; 29 | 30 | jetwayObjectId: ObjectId; 31 | 32 | attachedObjectId: ObjectId; 33 | 34 | constructor(data: RawBuffer) { 35 | this.airportIcao = data.readString(8); 36 | this.parkingIndex = data.readInt32(); 37 | this.latLngAlt = readLatLonAlt(data); 38 | this.pbh = readPBH(data); 39 | this.status = data.readInt32(); 40 | this.door = data.readInt32(); 41 | this.exitDoorRelativePos = readXYZ(data); 42 | this.mainHandlePos = readXYZ(data); 43 | this.secondaryHandle = readXYZ(data); 44 | this.wheelGroundLock = readXYZ(data); 45 | this.jetwayObjectId = data.readUint32() as ObjectId; 46 | this.attachedObjectId = data.readUint32() as ObjectId; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/datastructures/VersionBaseType.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | 3 | export class VersionBaseType { 4 | major: number; 5 | 6 | minor: number; 7 | 8 | revision: number; 9 | 10 | build: number; 11 | 12 | constructor(data: RawBuffer) { 13 | this.major = data.readInt16(); 14 | this.minor = data.readInt16(); 15 | this.revision = data.readInt16(); 16 | this.build = data.readInt16(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/datastructures/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FacilityAirport'; 2 | export * from './FacilityNDB'; 3 | export * from './FacilityMinimal'; 4 | export * from './FacilityVOR'; 5 | export * from './FacilityWaypoint'; 6 | export * from './ControllerItem'; 7 | export * from './InputEventDescriptor'; 8 | export * from './JetwayData'; 9 | -------------------------------------------------------------------------------- /src/dto/InitPosition.ts: -------------------------------------------------------------------------------- 1 | import { SimConnectData } from './SimConnectData'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder'; 4 | 5 | enum Airspeed { 6 | Keep = -2, 7 | Cruise = -1, 8 | } 9 | 10 | class InitPosition implements SimConnectData { 11 | latitude = 0; 12 | 13 | longitude = 0; 14 | 15 | altitude = 0; 16 | 17 | pitch = 0; 18 | 19 | bank = 0; 20 | 21 | heading = 0; 22 | 23 | onGround = false; 24 | 25 | airspeed: Airspeed | number = 0; 26 | 27 | readFrom(buffer: RawBuffer) { 28 | this.latitude = buffer.readFloat64(); 29 | this.longitude = buffer.readFloat64(); 30 | this.altitude = buffer.readFloat64(); 31 | this.pitch = buffer.readFloat64(); 32 | this.bank = buffer.readFloat64(); 33 | this.heading = buffer.readFloat64(); 34 | this.onGround = buffer.readInt32() !== 0; 35 | this.airspeed = buffer.readInt32(); 36 | } 37 | 38 | writeTo(packetBuilder: SimConnectPacketBuilder) { 39 | packetBuilder 40 | .putFloat64(this.latitude) 41 | .putFloat64(this.longitude) 42 | .putFloat64(this.altitude) 43 | .putFloat64(this.pitch) 44 | .putFloat64(this.bank) 45 | .putFloat64(this.heading) 46 | .putInt32(this.onGround ? 1 : 0) 47 | .putInt32(this.airspeed); 48 | } 49 | } 50 | 51 | export { InitPosition, Airspeed }; 52 | -------------------------------------------------------------------------------- /src/dto/LatLonAlt.ts: -------------------------------------------------------------------------------- 1 | import { SimConnectData } from './SimConnectData'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder'; 4 | 5 | class LatLonAlt implements SimConnectData { 6 | latitude = 0; 7 | 8 | longitude = 0; 9 | 10 | altitude = 0; 11 | 12 | readFrom(buffer: RawBuffer) { 13 | this.latitude = buffer.readFloat64(); 14 | this.longitude = buffer.readFloat64(); 15 | this.altitude = buffer.readFloat64(); 16 | } 17 | 18 | writeTo(packetBuilder: SimConnectPacketBuilder) { 19 | packetBuilder 20 | .putFloat64(this.latitude) 21 | .putFloat64(this.longitude) 22 | .putFloat64(this.altitude); 23 | } 24 | } 25 | 26 | export { LatLonAlt }; 27 | -------------------------------------------------------------------------------- /src/dto/MarkerState.ts: -------------------------------------------------------------------------------- 1 | import { SimConnectData } from './SimConnectData'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder'; 4 | 5 | class MarkerState implements SimConnectData { 6 | markerName = ''; 7 | 8 | markerState = false; 9 | 10 | readFrom(buffer: RawBuffer) { 11 | this.markerName = buffer.readString64(); 12 | this.markerState = buffer.readInt32() !== 0; 13 | } 14 | 15 | writeTo(packetBuilder: SimConnectPacketBuilder) { 16 | packetBuilder // 17 | .putString(this.markerName, 64) 18 | .putInt32(this.markerState ? 1 : 0); 19 | } 20 | } 21 | 22 | export { MarkerState }; 23 | -------------------------------------------------------------------------------- /src/dto/PBH.ts: -------------------------------------------------------------------------------- 1 | import { SimConnectData } from './SimConnectData'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder'; 4 | 5 | class PBH implements SimConnectData { 6 | pitch = 0; 7 | 8 | bank = 0; 9 | 10 | heading = 0; 11 | 12 | readFrom(buffer: RawBuffer) { 13 | this.pitch = buffer.readFloat32(); 14 | this.bank = buffer.readFloat32(); 15 | this.heading = buffer.readFloat32(); 16 | } 17 | 18 | writeTo(packetBuilder: SimConnectPacketBuilder) { 19 | packetBuilder // 20 | .putFloat32(this.pitch) 21 | .putFloat32(this.bank) 22 | .putFloat32(this.heading); 23 | } 24 | } 25 | 26 | export { PBH }; 27 | -------------------------------------------------------------------------------- /src/dto/SimConnectData.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder'; 3 | 4 | interface SimConnectData { 5 | readonly readFrom: (buffer: RawBuffer) => void; 6 | readonly writeTo: (packetBuilder: SimConnectPacketBuilder) => void; 7 | } 8 | 9 | export { SimConnectData }; 10 | -------------------------------------------------------------------------------- /src/dto/Waypoint.ts: -------------------------------------------------------------------------------- 1 | import { SimConnectData } from './SimConnectData'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder'; 4 | 5 | class Waypoint implements SimConnectData { 6 | /** Latitude of waypoint, in degrees */ 7 | latitude = 0; 8 | 9 | /** Longitude of waypoint, in degrees */ 10 | longitude = 0; 11 | 12 | /** Altitude of waypoint, in feet */ 13 | altitude = 0; 14 | 15 | /** flags of waypoints 16 | * @see 17 | * - {@link SimConnectConstants.WAYPOINT_ON_GROUND} 18 | * - {@link SimConnectConstants.WAYPOINT_REVERSE} 19 | * - {@link SimConnectConstants.WAYPOINT_ALTITUDE_IS_AGL} 20 | * - {@link SimConnectConstants.WAYPOINT_COMPUTE_VERTICAL_SPEED} 21 | * - {@link SimConnectConstants.WAYPOINT_SPEED_REQUESTED} 22 | * - {@link SimConnectConstants.WAYPOINT_THROTTLE_REQUESTED} 23 | */ 24 | flags = 0; 25 | 26 | /** Speed, in kots. {@link SimConnectConstants.WAYPOINT_SPEED_REQUESTED} must be on */ 27 | speed = 0; 28 | 29 | /** Throttle, in percent {@link SimConnectConstants.WAYPOINT_THROTTLE_REQUESTED} must be on */ 30 | throttle = 0; 31 | 32 | readFrom(buffer: RawBuffer) { 33 | this.latitude = buffer.readFloat64(); 34 | this.longitude = buffer.readFloat64(); 35 | this.altitude = buffer.readFloat64(); 36 | this.flags = buffer.readInt32(); 37 | this.speed = buffer.readFloat64(); 38 | this.throttle = buffer.readFloat64(); 39 | } 40 | 41 | writeTo(packetBuilder: SimConnectPacketBuilder) { 42 | packetBuilder 43 | .putFloat64(this.latitude) 44 | .putFloat64(this.longitude) 45 | .putFloat64(this.altitude) 46 | .putInt32(this.flags) 47 | .putFloat64(this.speed) 48 | .putFloat64(this.throttle); 49 | } 50 | } 51 | 52 | export { Waypoint }; 53 | -------------------------------------------------------------------------------- /src/dto/XYZ.ts: -------------------------------------------------------------------------------- 1 | import { SimConnectData } from './SimConnectData'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder'; 4 | 5 | class XYZ implements SimConnectData { 6 | x = 0; 7 | 8 | y = 0; 9 | 10 | z = 0; 11 | 12 | readFrom(buffer: RawBuffer) { 13 | this.x = buffer.readFloat64(); 14 | this.y = buffer.readFloat64(); 15 | this.z = buffer.readFloat64(); 16 | } 17 | 18 | writeTo(packetBuilder: SimConnectPacketBuilder) { 19 | packetBuilder // 20 | .putFloat64(this.x) 21 | .putFloat64(this.y) 22 | .putFloat64(this.z); 23 | } 24 | } 25 | 26 | export { XYZ }; 27 | -------------------------------------------------------------------------------- /src/dto/bufferHelpers.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { InitPosition } from './InitPosition'; 3 | import { MarkerState } from './MarkerState'; 4 | import { Waypoint } from './Waypoint'; 5 | import { LatLonAlt } from './LatLonAlt'; 6 | import { XYZ } from './XYZ'; 7 | import { SimConnectData } from './SimConnectData'; 8 | import { PBH } from './PBH'; 9 | 10 | function readInitPosition(dataWrapper: RawBuffer): InitPosition { 11 | return readData(dataWrapper, new InitPosition()); 12 | } 13 | 14 | function readMarkerState(dataWrapper: RawBuffer): MarkerState { 15 | return readData(dataWrapper, new MarkerState()); 16 | } 17 | 18 | function readWaypoint(dataWrapper: RawBuffer): Waypoint { 19 | return readData(dataWrapper, new Waypoint()); 20 | } 21 | 22 | function readLatLonAlt(dataWrapper: RawBuffer): LatLonAlt { 23 | return readData(dataWrapper, new LatLonAlt()); 24 | } 25 | 26 | function readXYZ(dataWrapper: RawBuffer): XYZ { 27 | return readData(dataWrapper, new XYZ()); 28 | } 29 | 30 | function readData(dataWrapper: RawBuffer, obj: T): T { 31 | obj.readFrom(dataWrapper); 32 | return obj; 33 | } 34 | 35 | function readPBH(dataWrapper: RawBuffer): PBH { 36 | return readData(dataWrapper, new PBH()); 37 | } 38 | 39 | export { readInitPosition, readMarkerState, readWaypoint, readLatLonAlt, readXYZ, readPBH }; 40 | -------------------------------------------------------------------------------- /src/dto/icao.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | 3 | export type IcaoType = 'V' | 'N' | 'W'; 4 | 5 | export class Icao { 6 | type: IcaoType; 7 | 8 | ident: string; 9 | 10 | region: string; 11 | 12 | airport: string; 13 | 14 | constructor(data: RawBuffer) { 15 | this.type = data.readString(1)[0] as IcaoType; 16 | this.ident = data.readString(6); 17 | this.region = data.readString(3); 18 | this.airport = data.readString(5); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/dto/index.ts: -------------------------------------------------------------------------------- 1 | export * from './InitPosition'; 2 | export * from './LatLonAlt'; 3 | export * from './MarkerState'; 4 | export * from './SimConnectData'; 5 | export * from './Waypoint'; 6 | export * from './XYZ'; 7 | export * from './bufferHelpers'; 8 | export * from './icao'; 9 | export * from './PBH'; 10 | export * from '../datastructures/VersionBaseType'; 11 | -------------------------------------------------------------------------------- /src/enums/ClientDataPeriod.ts: -------------------------------------------------------------------------------- 1 | export enum ClientDataPeriod { 2 | /** Specifies that the data is not to be sent. */ 3 | NEVER, 4 | /** Specifies that the data should be sent once only. Note that this is not an efficient way of receiving 5 | * data frequently, use one of the other periods if there is a regular frequency to the data request. */ 6 | ONCE, 7 | /** Specifies that the data should be sent every visual (rendered) frame. */ 8 | VISUAL_FRAME, 9 | /** Specifies that the data should be sent whenever it is set. */ 10 | ON_SET, 11 | /** Specifies that the data should be sent once every second. */ 12 | SECOND, 13 | } 14 | -------------------------------------------------------------------------------- /src/enums/FacilityDataType.ts: -------------------------------------------------------------------------------- 1 | export enum FacilityDataType { 2 | AIRPORT, 3 | RUNWAY, 4 | START, 5 | FREQUENCY, 6 | HELIPAD, 7 | APPROACH, 8 | APPROACH_TRANSITION, 9 | APPROACH_LEG, 10 | FINAL_APPROACH_LEG, 11 | MISSED_APPROACH_LEG, 12 | DEPARTURE, 13 | ARRIVAL, 14 | RUNWAY_TRANSITION, 15 | ENROUTE_TRANSITION, 16 | TAXI_POINT, 17 | TAXI_PARKING, 18 | TAXI_PATH, 19 | TAXI_NAME, 20 | JETWAY, 21 | VOR, 22 | NDB, 23 | WAYPOINT, 24 | ROUTE, 25 | PAVEMENT, 26 | APPROACHLIGHTS, 27 | VASI, 28 | } 29 | 30 | module.exports = { 31 | FacilityDataType, 32 | }; 33 | -------------------------------------------------------------------------------- /src/enums/FacilityListType.ts: -------------------------------------------------------------------------------- 1 | export enum FacilityListType { 2 | /** Specifies that the type of information is for an airport */ 3 | AIRPORT, 4 | /** Specifies that the type of information is for a waypoint */ 5 | WAYPOINT, 6 | /** Specifies that the type of information is for an NDB */ 7 | NDB, 8 | /** Specifies that the type of information is for a VOR */ 9 | VOR, 10 | /** Not valid as a list type, but simply the number of list types. */ 11 | COUNT, 12 | } 13 | 14 | module.exports = { 15 | FacilityListType, 16 | }; 17 | -------------------------------------------------------------------------------- /src/enums/InputEventType.ts: -------------------------------------------------------------------------------- 1 | export const enum InputEventType { 2 | DOUBLE, 3 | STRING, 4 | } 5 | -------------------------------------------------------------------------------- /src/enums/JetwayStatus.ts: -------------------------------------------------------------------------------- 1 | export const enum JetwayStatus { 2 | REST, 3 | APPROACH_OUTSIDE, 4 | APPROACH_DOOR, 5 | HOOD_CONNECT, 6 | HOOD_DISCONNECT, 7 | RETRACT_OUTSIDE, 8 | RETRACT_HOME, 9 | FULLY_ATTACHED, 10 | } 11 | -------------------------------------------------------------------------------- /src/enums/NotificationPriority.ts: -------------------------------------------------------------------------------- 1 | export const enum NotificationPriority { 2 | /** The highest priority. */ 3 | HIGHEST = 0x00000001, 4 | /** The hightest priority that allows events to be masked. */ 5 | HIGHEST_MASKABLE = 0x00989680, 6 | /** The standard priority. */ 7 | STANDARD = 0x713fb300, 8 | /** The default priority. */ 9 | DEFAULT = 0x77359400, 10 | /** Priorities lower than this will be ignored. */ 11 | LOWEST = 0xee6b2800, 12 | } 13 | -------------------------------------------------------------------------------- /src/enums/Protocol.ts: -------------------------------------------------------------------------------- 1 | export enum Protocol { 2 | /** 3 | * FSX original release 4 | */ 5 | FSX_RTM = 0x2, 6 | /** 7 | * FSX SP1, supports enhanced client data, facilites, and modeless ui 8 | */ 9 | FSX_SP1 = 0x3, 10 | /** 11 | * FSX SP2/Acceleration, racing and another flight save 12 | */ 13 | FSX_SP2 = 0x4, 14 | /** 15 | * MSFS / Asobo 16 | */ 17 | KittyHawk = 0x5, 18 | } 19 | 20 | module.exports = { 21 | Protocol, 22 | }; 23 | -------------------------------------------------------------------------------- /src/enums/SimConnectDataType.ts: -------------------------------------------------------------------------------- 1 | export enum SimConnectDataType { 2 | /** Invalid data type */ 3 | INVALID, 4 | /** 32-bit integer number */ 5 | INT32, 6 | /** 64-bit integer number */ 7 | INT64, 8 | /** 32-bit floating-point number (float) */ 9 | FLOAT32, 10 | /** 64-bit floating-point number (double) */ 11 | FLOAT64, 12 | /** Fixed-length string, 8-byte */ 13 | STRING8, 14 | /** Fixed-length string, 32-byte */ 15 | STRING32, 16 | /** Fixed-length string, 64-byte */ 17 | STRING64, 18 | /** Fixed-length string, 128-byte */ 19 | STRING128, 20 | /** Fixed-length string, 256-byte */ 21 | STRING256, 22 | /** Fixed-length string, 260-byte */ 23 | STRING260, 24 | /** Variable-length string */ 25 | STRINGV, 26 | 27 | /** {@link InitPosition} data structure */ 28 | INITPOSITION, 29 | /** {@link MarkerState} data structure */ 30 | MARKERSTATE, 31 | /** {@link Waypoint} data structure */ 32 | WAYPOINT, 33 | /** {@link LatLonAlt} data structure */ 34 | LATLONALT, 35 | /** {@link XYZ} data structure */ 36 | XYZ, 37 | /** enum limit */ 38 | MAX, 39 | } 40 | 41 | module.exports = { 42 | SimConnectDataType, 43 | }; 44 | -------------------------------------------------------------------------------- /src/enums/SimConnectException.ts: -------------------------------------------------------------------------------- 1 | export enum SimConnectException { 2 | NONE, 3 | ERROR, 4 | SIZE_MISMATCH, 5 | UNRECOGNIZED_ID, 6 | UNOPENED, 7 | VERSION_MISMATCH, 8 | TOO_MANY_GROUPS, 9 | NAME_UNRECOGNIZED, 10 | TOO_MANY_EVENT_NAMES, 11 | EVENT_ID_DUPLICATE, 12 | TOO_MANY_MAPS, 13 | TOO_MANY_OBJECTS, 14 | TOO_MANY_REQUESTS, 15 | WEATHER_INVALID_PORT, 16 | WEATHER_INVALID_METAR, 17 | WEATHER_UNABLE_TO_GET_OBSERVATION, 18 | WEATHER_UNABLE_TO_CREATE_STATION, 19 | WEATHER_UNABLE_TO_REMOVE_STATION, 20 | INVALID_DATA_TYPE, 21 | INVALID_DATA_SIZE, 22 | DATA_ERROR, 23 | INVALID_ARRAY, 24 | CREATE_OBJECT_FAILED, 25 | LOAD_FLIGHTPLAN_FAILED, 26 | OPERATION_INVALID_FOR_OJBECT_TYPE, 27 | ILLEGAL_OPERATION, 28 | ALREADY_SUBSCRIBED, 29 | INVALID_ENUM, 30 | DEFINITION_ERROR, 31 | DUPLICATE_ID, 32 | DATUM_ID, 33 | OUT_OF_BOUNDS, 34 | ALREADY_CREATED, 35 | OBJECT_OUTSIDE_REALITY_BUBBLE, 36 | OBJECT_CONTAINER, 37 | OBJECT_AI, 38 | OBJECT_ATC, 39 | OBJECT_SCHEDULE, 40 | JETWAY_DATA, 41 | ACTION_NOT_FOUND, 42 | NOT_AN_ACTION, 43 | INCORRECT_ACTION_PARAMS, 44 | GET_INPUT_EVENT_FAILED, 45 | SET_INPUT_EVENT_FAILED, 46 | } 47 | 48 | module.exports = { 49 | SimConnectException, 50 | }; 51 | -------------------------------------------------------------------------------- /src/enums/SimConnectPeriod.ts: -------------------------------------------------------------------------------- 1 | export enum SimConnectPeriod { 2 | /** Specifies that the data is not to be sent. */ 3 | NEVER, 4 | /** Specifies that the data should be sent once only. Note that this is not an efficient way of receiving 5 | * data frequently, use one of the other periods if there is a regular frequency to the data request. */ 6 | ONCE, 7 | /** Specifies that the data should be sent every visual (rendered) frame. */ 8 | VISUAL_FRAME, 9 | /** Specifies that the data should be sent every simulated frame, whether that frame is rendered or not. */ 10 | SIM_FRAME, 11 | /** Specifies that the data should be sent once every second. */ 12 | SECOND, 13 | } 14 | 15 | module.exports = { 16 | SimConnectPeriod, 17 | }; 18 | -------------------------------------------------------------------------------- /src/enums/SimObjectType.ts: -------------------------------------------------------------------------------- 1 | export enum SimObjectType { 2 | /** Specifies the user's aircraft. */ 3 | USER, 4 | /** Specifies all AI controlled objects. */ 5 | ALL, 6 | /** Specifies all aircraft. */ 7 | AIRCRAFT, 8 | /** Specifies all helicopters. */ 9 | HELICOPTER, 10 | /** Specifies all AI controlled boats. */ 11 | BOAT, 12 | /** Specifies all AI controlled ground vehicles */ 13 | GROUND, 14 | 15 | /** for invalid values */ 16 | INVALID, 17 | } 18 | 19 | module.exports = { 20 | SimObjectType, 21 | }; 22 | -------------------------------------------------------------------------------- /src/enums/TextResult.ts: -------------------------------------------------------------------------------- 1 | export enum TextResult { 2 | /** Specifies that the user has selected the menu item. */ 3 | MENU_SELECT_1 = 0, 4 | /** Specifies that the user has selected the menu item. */ 5 | MENU_SELECT_2 = 1, 6 | /** Specifies that the user has selected the menu item. */ 7 | MENU_SELECT_3 = 2, 8 | /** Specifies that the user has selected the menu item. */ 9 | MENU_SELECT_4 = 3, 10 | /** Specifies that the user has selected the menu item. */ 11 | MENU_SELECT_5 = 4, 12 | /** Specifies that the user has selected the menu item. */ 13 | MENU_SELECT_6 = 5, 14 | /** Specifies that the user has selected the menu item. */ 15 | MENU_SELECT_7 = 6, 16 | /** Specifies that the user has selected the menu item. */ 17 | MENU_SELECT_8 = 7, 18 | /** Specifies that the user has selected the menu item. */ 19 | MENU_SELECT_9 = 8, 20 | /** Specifies that the user has selected the menu item. */ 21 | MENU_SELECT_10 = 9, 22 | /** Specifies that the menu or text identified by the EventID is now on display. */ 23 | DISPLAYED = 0x00010000, 24 | /** Specifies that the menu or text identified by the EventID is waiting in a queue. */ 25 | QUEUED = 0x00010001, 26 | /** Specifies that the menu or text identified by the EventID has been removed from the queue. */ 27 | REMOVED = 0x00010002, 28 | /** Specifies that the menu or text identified by the EventID has been replaced in the queue. */ 29 | REPLACED = 0x00010003, 30 | /** Specifies that the menu or text identified by the EventID has timed-out and is no longer on display. */ 31 | TIMEOUT = 0x00010004, 32 | } 33 | 34 | module.exports = { 35 | TextResult, 36 | }; 37 | -------------------------------------------------------------------------------- /src/enums/TextType.ts: -------------------------------------------------------------------------------- 1 | export enum TextType { 2 | /** specify scrolling text in the named color */ 3 | SCROLL_BLACK = 0, 4 | /** specify scrolling text in the named color */ 5 | SCROLL_WHITE = 1, 6 | /** specify scrolling text in the named color */ 7 | SCROLL_RED = 2, 8 | /** specify scrolling text in the named color */ 9 | SCROLL_GREEN = 3, 10 | /** specify scrolling text in the named color */ 11 | SCROLL_BLUE = 4, 12 | /** specify scrolling text in the named color */ 13 | SCROLL_YELLOW = 5, 14 | /** specify scrolling text in the named color */ 15 | SCROLL_MAGENTA = 6, 16 | /** specify scrolling text in the named color */ 17 | SCROLL_CYAN = 7, 18 | /** specify static text in the named color */ 19 | PRINT_BLACK = 0x0100, 20 | /** specify static text in the named color */ 21 | PRINT_WHITE = 0x0101, 22 | /** specify static text in the named color */ 23 | PRINT_RED = 0x0102, 24 | /** specify static text in the named color */ 25 | PRINT_GREEN = 0x0103, 26 | /** specify static text in the named color */ 27 | PRINT_BLUE = 0x0104, 28 | /** specify static text in the named color */ 29 | PRINT_YELLOW = 0x0105, 30 | /** specify static text in the named color */ 31 | PRINT_MAGENTA = 0x0106, 32 | /** specify static text in the named color */ 33 | PRINT_CYAN = 0x0107, 34 | /** specify that the text is for a menu */ 35 | MENU = 0x0200, 36 | } 37 | -------------------------------------------------------------------------------- /src/enums/WeatherMode.ts: -------------------------------------------------------------------------------- 1 | export enum WeatherMode { 2 | /** Specifies that the weather has been set to a theme. */ 3 | THEME, 4 | /** Specifies that real-world weather has been set. */ 5 | RWW, 6 | /** Specifies that custom weather has been set. */ 7 | CUSTOM, 8 | /** Specifies that the global weather mode has been set. */ 9 | GLOBAL, 10 | } 11 | -------------------------------------------------------------------------------- /src/enums/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ClientDataPeriod'; 2 | export * from './SimConnectDataType'; 3 | export * from './TextType'; 4 | export * from './TextResult'; 5 | export * from './SimConnectPeriod'; 6 | export * from './SimConnectException'; 7 | export * from './SimObjectType'; 8 | export * from './FacilityListType'; 9 | export * from './NotificationPriority'; 10 | export * from './Protocol'; 11 | export * from './WeatherMode'; 12 | export * from './InputEventType'; 13 | export * from './FacilityDataType'; 14 | export * from './JetwayStatus'; 15 | -------------------------------------------------------------------------------- /src/flags/ClientDataRequestFlag.ts: -------------------------------------------------------------------------------- 1 | export enum ClientDataRequestFlag { 2 | /** The default, data will be sent strictly according to the defined period. 3 | */ 4 | CLIENT_DATA_REQUEST_FLAG_DEFAULT = 0x00000000, 5 | /** Data will only be sent to the client when one or more values have changed. If 6 | * this is the only flag set, then all the variables in a data definition will be returned 7 | * if just one of the values changes. 8 | */ 9 | CLIENT_DATA_REQUEST_FLAG_CHANGED = 0x00000001, // send requested data when value(s) change 10 | /** Requested data will be sent in tagged format (datum ID/value pairs). Tagged 11 | * format requires that a datum reference ID is returned along with the data value, 12 | * in order that the client code is able to identify the variable. This flag is 13 | * usually set in conjunction with the previous flag, but it can be used on its own to 14 | * return all the values in a data definition in datum ID/value pairs. 15 | */ 16 | CLIENT_DATA_REQUEST_FLAG_TAGGED = 0x00000002, // send requested data in tagged format 17 | } 18 | -------------------------------------------------------------------------------- /src/flags/DataRequestFlag.ts: -------------------------------------------------------------------------------- 1 | export enum DataRequestFlag { 2 | /** The default, data will be sent strictly according to the defined period. */ 3 | DATA_REQUEST_FLAG_DEFAULT = 0x00000000, 4 | /** Data will only be sent to the client when one or more values have changed. If 5 | * this is the only flag set, then all the variables in a data definition will be returned 6 | * if just one of the values changes. */ 7 | DATA_REQUEST_FLAG_CHANGED = 0x00000001, 8 | /** Requested data will be sent in tagged format (datum ID/value pairs). Tagged 9 | * format requires that a datum reference ID is returned along with the data value, 10 | * in order that the client code is able to identify the variable. This flag is 11 | * usually set in conjunction with the previous flag, but it can be used on its own to 12 | * return all the values in a data definition in datum ID/value pairs */ 13 | DATA_REQUEST_FLAG_TAGGED = 0x00000002, 14 | } 15 | -------------------------------------------------------------------------------- /src/flags/DataSetFlag.ts: -------------------------------------------------------------------------------- 1 | export enum DataSetFlag { 2 | /** The data to be set is not in tagged format 3 | */ 4 | DEFAULT = 0x00000000, 5 | /** The data to be set is being sent in tagged format. Refer to {@link SimConnectConnection.requestDataOnSimObject} for more details on the tagged format. */ 6 | TAGGED = 0x00000001, 7 | } 8 | -------------------------------------------------------------------------------- /src/flags/EventFlag.ts: -------------------------------------------------------------------------------- 1 | export enum EventFlag { 2 | /** Do nothing. */ 3 | EVENT_FLAG_DEFAULT = 0x00000000, 4 | /** The flag will effectively reset the repeat timer to simulate slow repeat. Use this flag to 5 | * reset the repeat timer that works with various keyboard events and mouse clicks. */ 6 | EVENT_FLAG_FAST_REPEAT_TIMER = 0x00000001, // set event repeat timer to simulate fast repeat 7 | /** The flag will effectively reset the repeat timer to simulate slow repeat. 8 | Use this flag to reset the repeat timer that works with various keyboard events and mouse clicks. */ 9 | EVENT_FLAG_SLOW_REPEAT_TIMER = 0x00000002, // set event repeat timer to simulate slow repeat 10 | /** Indicates to the SimConnect server to treat the GroupID as a priority value. 11 | If this parameter is set to {@link NotificationPriority.HIGHEST} then all 12 | client notification groups that have subscribed to the event will receive 13 | the notification (unless one of them masks it). The event will be transmitted 14 | to clients starting at the given priority, though this can result in the 15 | client that transmitted the event, receiving it again. */ 16 | EVENT_FLAG_GROUPID_IS_PRIORITY = 0x00000010, // interpret GroupID parameter as priority value 17 | } 18 | -------------------------------------------------------------------------------- /src/flags/index.ts: -------------------------------------------------------------------------------- 1 | export * from './EventFlag'; 2 | export * from './DataRequestFlag'; 3 | export * from './DataSetFlag'; 4 | export * from './ClientDataRequestFlag'; 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { SimConnectConnection, ConnectionOptions } from './SimConnectConnection'; 2 | import { Protocol } from './enums/Protocol'; 3 | import { RecvOpen } from './recv'; 4 | 5 | export * from './SimConnectConstants'; 6 | export * from './SimConnectPacketBuilder'; 7 | export * from './enums'; 8 | export * from './SimConnectConnection'; 9 | export * from './SimConnectSocket'; 10 | export * from './flags'; 11 | export * from './datastructures'; 12 | export * from './Types'; 13 | export * from './recv'; 14 | export * from './dto'; 15 | export { RawBuffer } from './RawBuffer'; 16 | 17 | /** 18 | * @member recvOpen - Information about the flight simulator 19 | * @member handle - The object used to interact with SimConnect 20 | */ 21 | export interface OpenEvent { 22 | recvOpen: RecvOpen; 23 | handle: SimConnectConnection; 24 | } 25 | 26 | /** 27 | * Try opening a connection to SimConnect 28 | * 29 | * @param appName - An appropriate name for the client program. Your app will appear with this name in the SimConnect inspector in MSFS. 30 | * @param protocolVersion - Lowest protocol version 31 | * @param options - Used for connecting to a remote instance of SimConnect. If omitted it will attempt to read connection parameters from the following sources: 32 | * 33 | * - IP + port number from SimConnect.cfg in the node.js installation directory (or the installation directory of the Electron app) 34 | * 35 | * - IP + port number from SimConnect.cfg in the user's home directory 36 | * 37 | * - Named pipe found in the Windows registry (available when the sim has started) 38 | * 39 | * - Port number, for use with localhost, found in the Windows registry (available when the sim has started) 40 | * 41 | */ 42 | export function open( 43 | appName: string, 44 | protocolVersion: Protocol, 45 | options?: ConnectionOptions 46 | ): Promise { 47 | const simConnectConnection = new SimConnectConnection(appName, protocolVersion); 48 | return new Promise((resolve, reject) => { 49 | simConnectConnection.on('open', data => { 50 | resolve({ recvOpen: data, handle: simConnectConnection }); 51 | }); 52 | simConnectConnection.on('error', error => { 53 | reject(error); 54 | }); 55 | simConnectConnection.connect(options); 56 | }); 57 | } 58 | 59 | export type ConnectionHandle = InstanceType; 60 | -------------------------------------------------------------------------------- /src/recv/RecvActionCallback.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { RecvEvent } from './RecvEvent'; 3 | import { DataRequestId } from '../Types'; 4 | 5 | export class RecvActionCallback extends RecvEvent { 6 | requestID: DataRequestId; 7 | 8 | actionID: string; 9 | 10 | constructor(data: RawBuffer) { 11 | super(data); 12 | this.actionID = data.readString260(); 13 | this.requestID = data.readInt32(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/recv/RecvAirportList.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { FacilityAirport } from '../datastructures/FacilityAirport'; 3 | import { RecvFacilitiesList } from './RecvFacilitiesList'; 4 | 5 | export class RecvAirportList extends RecvFacilitiesList { 6 | airports: FacilityAirport[]; 7 | 8 | constructor(data: RawBuffer) { 9 | super(data); 10 | this.airports = []; 11 | for (let i = 0; i < this.arraySize; i++) { 12 | this.airports.push(new FacilityAirport(data)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/recv/RecvAssignedObjectID.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { DataRequestId, ObjectId } from '../Types'; 3 | 4 | export class RecvAssignedObjectID { 5 | requestID: DataRequestId; 6 | 7 | objectID: ObjectId; 8 | 9 | constructor(data: RawBuffer) { 10 | this.requestID = data.readInt32() as DataRequestId; 11 | this.objectID = data.readInt32() as ObjectId; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/recv/RecvCloudState.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { DataRequestId } from '../Types'; 3 | 4 | export class RecvCloudState { 5 | requestID: DataRequestId; 6 | 7 | arraySize: number; 8 | 9 | data: number[][]; 10 | 11 | constructor(data: RawBuffer) { 12 | this.requestID = data.readInt32() as DataRequestId; 13 | this.arraySize = data.readInt32(); 14 | this.data = []; 15 | // Read 2D-array of 64x64 bytes 16 | for (let i = 0; i < 64; i++) { 17 | this.data[i] = [...data.readBytes(64)]; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/recv/RecvControllersList.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { RecvListTemplate } from './RecvListTemplate'; 3 | import { ControllerItem } from '../datastructures/ControllerItem'; 4 | 5 | export class RecvControllersList extends RecvListTemplate { 6 | controllers: ControllerItem[] = []; 7 | 8 | constructor(data: RawBuffer) { 9 | super(data); 10 | 11 | this.controllers = []; 12 | for (let i = 0; i < this.arraySize; i++) { 13 | this.controllers.push(new ControllerItem(data)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/recv/RecvCustomAction.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | 3 | export class RecvCustomAction { 4 | guid: Buffer; 5 | 6 | waitForCompletion: number; 7 | 8 | payload: string; 9 | 10 | constructor(data: RawBuffer) { 11 | this.guid = data.readBytes(16); 12 | this.waitForCompletion = data.readInt32(); 13 | this.payload = data.readString(data.remaining()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/recv/RecvEnumerateInputEventParams.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | 3 | export class RecvEnumerateInputEventParams { 4 | inputEventIdHash: bigint; 5 | 6 | value: string; 7 | 8 | constructor(data: RawBuffer) { 9 | this.inputEventIdHash = data.readUint64(); 10 | this.value = data.readStringV(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/recv/RecvEnumerateInputEvents.ts: -------------------------------------------------------------------------------- 1 | import { RecvListTemplate } from './RecvListTemplate'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { InputEventDescriptor } from '../datastructures/InputEventDescriptor'; 4 | 5 | export class RecvEnumerateInputEvents extends RecvListTemplate { 6 | inputEventDescriptors: InputEventDescriptor[] = []; 7 | 8 | constructor(data: RawBuffer) { 9 | super(data); 10 | 11 | this.inputEventDescriptors = []; 12 | for (let i = 0; i < this.arraySize; i++) { 13 | this.inputEventDescriptors.push(new InputEventDescriptor(data)); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/recv/RecvEvent.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { ClientEventId, InputGroupId, NotificationGroupId } from '../Types'; 3 | 4 | export class RecvEvent { 5 | groupID: NotificationGroupId | InputGroupId; 6 | 7 | clientEventId: ClientEventId; 8 | 9 | data: number; 10 | 11 | constructor(data: RawBuffer) { 12 | this.groupID = data.readInt32() as NotificationGroupId; 13 | this.clientEventId = data.readInt32() as ClientEventId; 14 | this.data = data.readInt32(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/recv/RecvEventAddRemove.ts: -------------------------------------------------------------------------------- 1 | import { SimObjectType } from '../enums/SimObjectType'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { RecvEvent } from './RecvEvent'; 4 | 5 | export class RecvEventAddRemove extends RecvEvent { 6 | type: SimObjectType; 7 | 8 | constructor(data: RawBuffer) { 9 | super(data); 10 | this.type = data.readInt32(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/recv/RecvEventEx1.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { ClientEventId, InputGroupId, NotificationGroupId } from '../Types'; 3 | 4 | export class RecvEventEx1 { 5 | groupID: NotificationGroupId | InputGroupId; 6 | 7 | clientEventId: ClientEventId; 8 | 9 | /** Contains max 5 bytes of data */ 10 | data: [number, number, number, number, number]; 11 | 12 | constructor(data: RawBuffer) { 13 | this.groupID = data.readInt32() as NotificationGroupId; 14 | this.clientEventId = data.readInt32() as ClientEventId; 15 | this.data = [ 16 | data.readInt32(), 17 | data.readInt32(), 18 | data.readInt32(), 19 | data.readInt32(), 20 | data.readInt32(), 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/recv/RecvEventFilename.ts: -------------------------------------------------------------------------------- 1 | import { SimConnectConstants } from '../SimConnectConstants'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { RecvEvent } from './RecvEvent'; 4 | 5 | export class RecvEventFilename extends RecvEvent { 6 | fileName: string; 7 | 8 | flags: number; 9 | 10 | constructor(data: RawBuffer) { 11 | super(data); 12 | this.fileName = data.readString(SimConnectConstants.MAX_PATH); 13 | this.flags = data.readInt32(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/recv/RecvEventFrame.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { RecvEvent } from './RecvEvent'; 3 | 4 | export class RecvEventFrame extends RecvEvent { 5 | frameRate: number; 6 | 7 | simSpeed: number; 8 | 9 | constructor(data: RawBuffer) { 10 | super(data); 11 | this.frameRate = data.readFloat32(); 12 | this.simSpeed = data.readFloat32(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/recv/RecvEventRaceEnd.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { RecvEvent } from './RecvEvent'; 3 | 4 | export class RecvEventRaceEnd extends RecvEvent { 5 | /** The index of the racer the results are for */ 6 | racerNumber: number; 7 | 8 | /** The total number of racers */ 9 | numberRacers: number; 10 | 11 | /** The name of the mission to execute, NULL if no mission */ 12 | missionGUID: Buffer; 13 | 14 | /** The name of the player */ 15 | playerName: string; 16 | 17 | /** The type of the multiplayer session: "LAN", "GAMESPY") */ 18 | sessionType: string; 19 | 20 | /** The aircraft type */ 21 | aircraft: string; 22 | 23 | /** The player role in the mission */ 24 | playerRole: string; 25 | 26 | /** Total time in seconds, 0 means DNF */ 27 | totalTime: number; 28 | 29 | /** Total penalty time in seconds */ 30 | penaltyTime: number; 31 | 32 | /** non 0 - disqualified, 0 - not disqualified */ 33 | disqualified: boolean; 34 | 35 | constructor(data: RawBuffer) { 36 | super(data); 37 | this.racerNumber = data.readInt32(); 38 | this.numberRacers = data.readInt32(); 39 | this.missionGUID = data.readBytes(16); 40 | this.playerName = data.readString(260); 41 | this.sessionType = data.readString(260); 42 | this.aircraft = data.readString(260); 43 | this.playerRole = data.readString(260); 44 | this.totalTime = data.readFloat64(); 45 | this.penaltyTime = data.readFloat64(); 46 | this.disqualified = data.readInt32() === 1; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/recv/RecvEventRaceLap.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { RecvEvent } from './RecvEvent'; 3 | 4 | export class RecvEventRaceLap extends RecvEvent { 5 | /** The index of the racer the results are for */ 6 | lapIndex: number; 7 | 8 | /** The total number of racers */ 9 | numberRacers: number; 10 | 11 | /** The name of the mission to execute, NULL if no mission */ 12 | missionGUID: Buffer; 13 | 14 | /** The name of the player */ 15 | playerName: string; 16 | 17 | /** The type of the multiplayer session: "LAN", "GAMESPY") */ 18 | sessionType: string; 19 | 20 | /** The aircraft type */ 21 | aircraft: string; 22 | 23 | /** The player role in the mission */ 24 | playerRole: string; 25 | 26 | /** Total time in seconds, 0 means DNF */ 27 | totalTime: number; 28 | 29 | /** Total penalty time in seconds */ 30 | penaltyTime: number; 31 | 32 | /** non 0 - disqualified, 0 - not disqualified */ 33 | disqualified: boolean; 34 | 35 | constructor(data: RawBuffer) { 36 | super(data); 37 | this.lapIndex = data.readInt32(); 38 | this.numberRacers = data.readInt32(); 39 | this.missionGUID = data.readBytes(16); 40 | this.playerName = data.readString(260); 41 | this.sessionType = data.readString(260); 42 | this.aircraft = data.readString(260); 43 | this.playerRole = data.readString(260); 44 | this.totalTime = data.readFloat64(); 45 | this.penaltyTime = data.readFloat64(); 46 | this.disqualified = data.readInt32() === 1; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/recv/RecvEventWeatherMode.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { WeatherMode } from '../enums/WeatherMode'; 3 | import { RecvEvent } from './RecvEvent'; 4 | 5 | export class RecvEventWeatherMode extends RecvEvent { 6 | mode: WeatherMode; 7 | 8 | constructor(data: RawBuffer) { 9 | super(data); 10 | this.mode = 11 | this.data < 0 || this.data > WeatherMode.GLOBAL 12 | ? WeatherMode.THEME 13 | : this.data; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/recv/RecvException.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { SimConnectException } from '../enums/SimConnectException'; 3 | 4 | export class RecvException { 5 | exception: number; 6 | 7 | sendId: number; 8 | 9 | index: number; 10 | 11 | exceptionName: string; 12 | 13 | constructor(data: RawBuffer) { 14 | this.exception = data.readInt32(); 15 | this.sendId = data.readInt32(); 16 | this.index = data.readInt32(); 17 | 18 | this.exceptionName = SimConnectException[this.exception]; 19 | } 20 | } 21 | 22 | module.exports = { RecvException }; 23 | -------------------------------------------------------------------------------- /src/recv/RecvFacilitiesList.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { DataRequestId } from '../Types'; 3 | 4 | export class RecvFacilitiesList { 5 | requestID: DataRequestId; 6 | 7 | arraySize: number; 8 | 9 | entryNumber: number; 10 | 11 | outOf: number; 12 | 13 | constructor(data: RawBuffer) { 14 | this.requestID = data.readInt32() as DataRequestId; 15 | this.arraySize = data.readInt32(); 16 | this.entryNumber = data.readInt32(); 17 | this.outOf = data.readInt32(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/recv/RecvFacilityData.ts: -------------------------------------------------------------------------------- 1 | import { FacilityDataType } from '../enums/FacilityDataType'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { DataDefinitionId, DataRequestId } from '../Types'; 4 | 5 | export class RecvFacilityData { 6 | userRequestId: DataRequestId; 7 | 8 | uniqueRequestId: DataDefinitionId; 9 | 10 | parentUniqueRequestId: DataDefinitionId; 11 | 12 | type: FacilityDataType; 13 | 14 | isListItem: boolean; 15 | 16 | itemIndex: number; 17 | 18 | listSize: number; 19 | 20 | data: RawBuffer; 21 | 22 | constructor(data: RawBuffer) { 23 | this.userRequestId = data.readInt32(); 24 | this.uniqueRequestId = data.readInt32(); 25 | this.parentUniqueRequestId = data.readInt32(); 26 | this.type = data.readInt32(); 27 | this.isListItem = data.readInt32() === 1; 28 | this.itemIndex = data.readInt32(); 29 | this.listSize = data.readInt32(); 30 | this.data = data; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/recv/RecvFacilityDataEnd.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { DataRequestId } from '../Types'; 3 | 4 | export class RecvFacilityDataEnd { 5 | // extends RecvEvent 6 | userRequestId: DataRequestId; 7 | 8 | constructor(data: RawBuffer) { 9 | this.userRequestId = data.readInt32(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/recv/RecvFacilityMinimalList.ts: -------------------------------------------------------------------------------- 1 | import { FacilityMinimal } from '../datastructures/FacilityMinimal'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { RecvFacilitiesList } from './RecvFacilitiesList'; 4 | 5 | export class RecvFacilityMinimalList extends RecvFacilitiesList { 6 | data: FacilityMinimal[]; 7 | 8 | constructor(data: RawBuffer) { 9 | super(data); 10 | this.data = []; 11 | for (let i = 0; i < this.arraySize; i++) { 12 | this.data.push(new FacilityMinimal(data)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/recv/RecvGetInputEvent.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { DataRequestId } from '../Types'; 3 | import { InputEventType } from '../enums/InputEventType'; 4 | 5 | export class RecvGetInputEvent { 6 | requestID: DataRequestId; 7 | 8 | type: InputEventType; 9 | 10 | value: number | string; 11 | 12 | constructor(data: RawBuffer) { 13 | this.requestID = data.readInt32(); 14 | this.type = data.readUint32(); 15 | 16 | switch (this.type) { 17 | case InputEventType.STRING: 18 | this.value = data.readString256(); 19 | break; 20 | case InputEventType.DOUBLE: 21 | this.value = data.readFloat64(); 22 | break; 23 | default: 24 | throw Error(`Unknown input event type: ${this.type}`); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/recv/RecvJetwayData.ts: -------------------------------------------------------------------------------- 1 | import { RecvListTemplate } from './RecvListTemplate'; 2 | import { JetwayData } from '../datastructures/JetwayData'; 3 | import { RawBuffer } from '../RawBuffer'; 4 | 5 | export class RecvJetwayData extends RecvListTemplate { 6 | jetways: JetwayData[]; 7 | 8 | constructor(data: RawBuffer) { 9 | super(data); 10 | 11 | this.jetways = []; 12 | 13 | for (let i = 0; i < this.arraySize; i++) { 14 | this.jetways.push(new JetwayData(data)); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/recv/RecvListTemplate.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { DataRequestId } from '../Types'; 3 | 4 | export class RecvListTemplate { 5 | requestID: DataRequestId; 6 | 7 | arraySize: number; 8 | 9 | entryNumber: number; 10 | 11 | outOf: number; 12 | 13 | constructor(data: RawBuffer) { 14 | this.requestID = data.readInt32() as DataRequestId; 15 | this.arraySize = data.readInt32(); 16 | this.entryNumber = data.readInt32(); 17 | this.outOf = data.readInt32(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/recv/RecvNDBList.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { FacilityNDB } from '../datastructures/FacilityNDB'; 3 | import { RecvFacilitiesList } from './RecvFacilitiesList'; 4 | 5 | export class RecvNDBList extends RecvFacilitiesList { 6 | ndbs: FacilityNDB[]; 7 | 8 | constructor(data: RawBuffer) { 9 | super(data); 10 | this.ndbs = []; 11 | for (let i = 0; i < this.arraySize; i++) { 12 | this.ndbs.push(new FacilityNDB(data)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/recv/RecvOpen.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | 3 | export class RecvOpen { 4 | applicationName: string; 5 | 6 | applicationVersionMajor: number; 7 | 8 | applicationVersionMinor: number; 9 | 10 | applicationBuildMajor: number; 11 | 12 | applicationBuildMinor: number; 13 | 14 | simConnectVersionMajor: number; 15 | 16 | simConnectVersionMinor: number; 17 | 18 | simConnectBuildMajor: number; 19 | 20 | simConnectBuildMinor: number; 21 | 22 | reserved1: number; 23 | 24 | reserved2: number; 25 | 26 | constructor(data: RawBuffer) { 27 | this.applicationName = data.readString256(); 28 | this.applicationVersionMajor = data.readInt32(); 29 | this.applicationVersionMinor = data.readInt32(); 30 | this.applicationBuildMajor = data.readInt32(); 31 | this.applicationBuildMinor = data.readInt32(); 32 | this.simConnectVersionMajor = data.readInt32(); 33 | this.simConnectVersionMinor = data.readInt32(); 34 | this.simConnectBuildMajor = data.readInt32(); 35 | this.simConnectBuildMinor = data.readInt32(); 36 | this.reserved1 = data.readInt32(); 37 | this.reserved2 = data.readInt32(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/recv/RecvReservedKey.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | 3 | export class RecvReservedKey { 4 | choiceReserved: string; 5 | 6 | reservedKey: string; 7 | 8 | constructor(data: RawBuffer) { 9 | this.choiceReserved = data.readString(50); 10 | this.reservedKey = data.readString(30); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/recv/RecvSimObjectData.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { DataRequestFlag } from '../flags/DataRequestFlag'; 3 | import { DataDefinitionId, DataRequestId, ObjectId } from '../Types'; 4 | 5 | export class RecvSimObjectData { 6 | requestID: DataRequestId; 7 | 8 | objectID: ObjectId; 9 | 10 | defineID: DataDefinitionId; 11 | 12 | flags: DataRequestFlag; 13 | 14 | entryNumber: number; 15 | 16 | outOf: number; 17 | 18 | defineCount: number; 19 | 20 | data: RawBuffer; 21 | 22 | constructor(data: RawBuffer) { 23 | // data.skip(8) 24 | this.requestID = data.readInt32() as DataRequestId; 25 | this.objectID = data.readInt32() as ObjectId; 26 | this.defineID = data.readInt32() as DataDefinitionId; 27 | this.flags = data.readInt32() as DataRequestFlag; 28 | this.entryNumber = data.readInt32(); 29 | this.outOf = data.readInt32(); 30 | this.defineCount = data.readInt32(); 31 | this.data = data; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/recv/RecvSubscribeInputEvent.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { InputEventType } from '../enums/InputEventType'; 3 | 4 | export class RecvSubscribeInputEvent { 5 | inputEventIdHash: bigint; 6 | 7 | type: InputEventType; 8 | 9 | value: number | string; 10 | 11 | constructor(data: RawBuffer) { 12 | this.inputEventIdHash = data.readUint64(); 13 | this.type = data.readUint32(); 14 | 15 | switch (this.type) { 16 | case InputEventType.STRING: 17 | this.value = data.readString256(); 18 | break; 19 | case InputEventType.DOUBLE: 20 | this.value = data.readFloat64(); 21 | break; 22 | default: 23 | throw Error(`Unknown input event type: ${this.type}`); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/recv/RecvSystemState.ts: -------------------------------------------------------------------------------- 1 | import { SimConnectConstants } from '../SimConnectConstants'; 2 | import { RawBuffer } from '../RawBuffer'; 3 | import { DataRequestId } from '../Types'; 4 | 5 | export class RecvSystemState { 6 | requestID: DataRequestId; 7 | 8 | dataInteger: number; 9 | 10 | dataFloat: number; 11 | 12 | dataString: string; 13 | 14 | constructor(data: RawBuffer) { 15 | this.requestID = data.readInt32() as DataRequestId; 16 | this.dataInteger = data.readInt32(); 17 | this.dataFloat = data.readFloat32(); 18 | this.dataString = data.readString(SimConnectConstants.MAX_PATH); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/recv/RecvVORList.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { FacilityVOR } from '../datastructures/FacilityVOR'; 3 | import { RecvFacilitiesList } from './RecvFacilitiesList'; 4 | 5 | export class RecvVORList extends RecvFacilitiesList { 6 | vors: FacilityVOR[]; 7 | 8 | constructor(data: RawBuffer) { 9 | super(data); 10 | this.vors = []; 11 | for (let i = 0; i < this.arraySize; i++) { 12 | this.vors.push(new FacilityVOR(data)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/recv/RecvWaypointList.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { FacilityWaypoint } from '../datastructures/FacilityWaypoint'; 3 | import { RecvFacilitiesList } from './RecvFacilitiesList'; 4 | 5 | export class RecvWaypointList extends RecvFacilitiesList { 6 | waypoints: FacilityWaypoint[]; 7 | 8 | constructor(data: RawBuffer) { 9 | super(data); 10 | this.waypoints = []; 11 | for (let i = 0; i < this.arraySize; i++) { 12 | this.waypoints.push(new FacilityWaypoint(data)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/recv/RecvWeatherObservation.ts: -------------------------------------------------------------------------------- 1 | import { RawBuffer } from '../RawBuffer'; 2 | import { DataRequestId } from '../Types'; 3 | 4 | export class RecvWeatherObservation { 5 | requestID: DataRequestId; 6 | 7 | metar: string; 8 | 9 | constructor(data: RawBuffer) { 10 | this.requestID = data.readInt32() as DataRequestId; 11 | this.metar = data.readStringV(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/recv/index.ts: -------------------------------------------------------------------------------- 1 | export * from './RecvAirportList'; 2 | export * from './RecvAssignedObjectID'; 3 | export * from './RecvCloudState'; 4 | export * from './RecvCustomAction'; 5 | export * from './RecvEvent'; 6 | export * from './RecvEventEx1'; 7 | export * from './RecvEventAddRemove'; 8 | export * from './RecvEventFilename'; 9 | export * from './RecvEventFrame'; 10 | export * from './RecvEventRaceEnd'; 11 | export * from './RecvEventRaceLap'; 12 | export * from './RecvEventWeatherMode'; 13 | export * from './RecvException'; 14 | export * from './RecvFacilitiesList'; 15 | export * from './RecvFacilityData'; 16 | export * from './RecvFacilityDataEnd'; 17 | export * from './RecvFacilityMinimalList'; 18 | export * from './RecvNDBList'; 19 | export * from './RecvOpen'; 20 | export * from './RecvReservedKey'; 21 | export * from './RecvSimObjectData'; 22 | export * from './RecvSystemState'; 23 | export * from './RecvVORList'; 24 | export * from './RecvWaypointList'; 25 | export * from './RecvWeatherObservation'; 26 | export * from './RecvJetwayData'; 27 | export * from './RecvControllersList'; 28 | export * from './RecvEnumerateInputEvents'; 29 | export * from './RecvGetInputEvent'; 30 | export * from './RecvSubscribeInputEvent'; 31 | export * from './RecvEnumerateInputEventParams'; 32 | export * from './RecvActionCallback'; 33 | export * from './RecvListTemplate'; 34 | -------------------------------------------------------------------------------- /src/utils/ini.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as ini from 'ini'; 3 | 4 | // eslint-disable-next-line 5 | export type IniStructure = { [key: string]: any }; 6 | 7 | export function readIniFile(filePath: string): Promise { 8 | return new Promise((resolve, reject) => { 9 | fs.readFile(filePath, { encoding: 'utf-8' }, (err, data) => { 10 | if (err) { 11 | reject(new Error(`Failed to load INI-file: err ${err}`)); 12 | } else { 13 | resolve(ini.parse(data)); 14 | } 15 | }); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/network.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | 3 | export async function checkIfNamedPipeExist(pipeName: string): Promise { 4 | return new Promise(resolve => { 5 | fs.access(pipeName, fs.constants.F_OK, err => { 6 | if (err) resolve(false); 7 | else resolve(true); 8 | }); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/registry.ts: -------------------------------------------------------------------------------- 1 | import regedit from 'regedit'; 2 | 3 | /** 4 | * Returns `undefined` if the key does not exist. 5 | */ 6 | export function readRegistryValue(key: string, subKey: string): Promise { 7 | return new Promise((resolve, reject) => { 8 | regedit.list([key], (err, result) => { 9 | if (err) { 10 | reject(new Error(`Failed to read registry value ${key} (${err})`)); 11 | } else { 12 | const values = result[key]?.values; 13 | if (subKey in values) { 14 | resolve(values[subKey].value as string); 15 | } else { 16 | resolve(undefined); 17 | } 18 | } 19 | }); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /tests/flightLoad.test.ts: -------------------------------------------------------------------------------- 1 | import { SimConnectConnection } from '../src/SimConnectConnection'; 2 | import { Protocol } from '../src/enums/Protocol'; 3 | import { expectBufferContent } from './utils'; 4 | 5 | test('The packet content when loading a flight is correctly formatted', () => { 6 | const MockSocket = jest.fn(); 7 | const scc = new SimConnectConnection('', Protocol.KittyHawk); 8 | // @ts-ignore 9 | scc._clientSocket = new MockSocket(); 10 | scc._clientSocket.write = MockSocket.bind(null); 11 | 12 | scc.flightLoad('C:\\Users\\Øystein\\flightplan'); 13 | 14 | expectBufferContent(MockSocket.mock.calls[1][0], expectedMessageContent); 15 | }); 16 | 17 | /** * 18 | * This sample was found by inspecting the data sent from an application that uses 19 | * the official SDK. This tool was used: https://github.com/Dragonlaird/SimConnect_Proxy 20 | */ 21 | const expectedMessageContent = ` 22 | 14 01 00 00 05 00 00 00 23 | 3D 00 00 F0 02 00 00 00 24 | 43 3A 5C 55 73 65 72 73 25 | 5C C3 98 79 73 74 65 69 26 | 6E 5C 66 6C 69 67 68 74 27 | 70 6C 61 6E 00 00 00 00 28 | 00 00 00 00 00 00 00 00 29 | 00 00 00 00 00 00 00 00 30 | 00 00 00 00 00 00 00 00 31 | 00 00 00 00 00 00 00 00 32 | 00 00 00 00 00 00 00 00 33 | 00 00 00 00 00 00 00 00 34 | 00 00 00 00 00 00 00 00 35 | 00 00 00 00 00 00 00 00 36 | 00 00 00 00 00 00 00 00 37 | 00 00 00 00 00 00 00 00 38 | 00 00 00 00 00 00 00 00 39 | 00 00 00 00 00 00 00 00 40 | 00 00 00 00 00 00 00 00 41 | 00 00 00 00 00 00 00 00 42 | 00 00 00 00 00 00 00 00 43 | 00 00 00 00 00 00 00 00 44 | 00 00 00 00 00 00 00 00 45 | 00 00 00 00 00 00 00 00 46 | 00 00 00 00 00 00 00 00 47 | 00 00 00 00 00 00 00 00 48 | 00 00 00 00 00 00 00 00 49 | 00 00 00 00 00 00 00 00 50 | 00 00 00 00 00 00 00 00 51 | 00 00 00 00 00 00 00 00 52 | 00 00 00 00 00 00 00 00 53 | 00 00 00 00 00 00 00 00 54 | 00 00 00 00 00 00 00 00 55 | 00 00 00 00 00 00 00 00 56 | 00 00 00 00 57 | `; 58 | -------------------------------------------------------------------------------- /tests/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param actual 4 | * @param expected Expected message content encoded as a string of hex-values (eg: 14 01 00 00 05 00 00 00 ...) 5 | */ 6 | export function expectBufferContent(actual: Buffer, expected: string) { 7 | const expectedBuffer = toBuffer(expected); 8 | expect(formattedBufferContent(actual)).toBe(formattedBufferContent(expectedBuffer)); 9 | } 10 | 11 | function toBuffer(hexData: string): Buffer { 12 | const buffer = Buffer.from(hexData.replace(/\W/g, ''), 'hex'); 13 | resetPacketId(buffer); 14 | return buffer; 15 | } 16 | 17 | function resetPacketId(buffer: Buffer) { 18 | // Byte at index 12-16 is the packet ID which will vay 19 | buffer.set([0, 0, 0, 0], 12); 20 | } 21 | 22 | function formattedBufferContent(data: Buffer): string { 23 | const decodedString = data.toString(); 24 | const rawBytes = data.toString('hex').replace(/(.{2})/g, ' 0x$1'); 25 | return `String:\r\n${decodedString}\r\nBytes:\r\n${rawBytes}`; 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["tests/**/*", "utils/**/*"] 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "CommonJS", 4 | "target": "es2015", 5 | "outDir": "./dist", 6 | "strict": true, 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "inlineSourceMap": true, 12 | "inlineSources": true 13 | }, 14 | "include": ["src/**/*", "tests/**/*", "utils/**/*"] 15 | } 16 | -------------------------------------------------------------------------------- /utils/packetInspectorProxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * To run this file: "npx ts-node .\utils\packetInspectorProxy.ts" 3 | * 4 | * Starts a proxy server that can be used to inspect packages sent between 5 | * the SimConnect server and a client application. Inspired by 6 | * https://github.com/Dragonlaird/SimConnect_Proxy 7 | * 8 | * Requires SimConnect network setup. Create a SimConnect.xml in the following directory: 9 | * X:\Users\\AppData\Local\Packages\Microsoft.FlightSimulator_**********\LocalCache 10 | * 11 | * 12 | * 13 | * SimConnect.xml 14 | * 15 | * IPv4 16 | * local 17 | * 500 18 | * 64 19 | * 41088 20 | *
0.0.0.0
21 | *
22 | *
23 | * 24 | * 25 | * Create a SimConnect.cfg file next to the client application .exe: 26 | * 27 | * [SimConnect] 28 | * Protocol=Ipv4 29 | * Port=1337 # Must match the proxy server's port number 30 | * Address=127.0.0.1 31 | */ 32 | 33 | import * as net from 'net'; 34 | 35 | const proxyHost = '127.0.0.1'; 36 | const proxyPort = 1337; 37 | 38 | const simConnectHost = '127.0.0.1'; 39 | const simConnectPort = 500; // Must match the port number in SimConnect.xml 40 | 41 | const server = net.createServer(clientSocket => { 42 | const targetSocket = net.connect(simConnectPort, simConnectHost, () => { 43 | console.log(`Proxy connected to target server: ${simConnectHost}:${simConnectPort}`); 44 | 45 | clientSocket.on('data', data => { 46 | console.log( 47 | `[Application -> SimConnect] ${data.length} bytes ::::::::::::::::::::::::\n` 48 | ); 49 | const hexString = formatAndPrint(data); 50 | 51 | targetSocket.write(data); 52 | targetSocket.write(Buffer.from(hexString, 'hex')); // Forwarding the data to the target server 53 | }); 54 | 55 | targetSocket.on('data', data => { 56 | console.log( 57 | `[SimConnect -> Application] ${data.length} bytes ::::::::::::::::::::::::\n` 58 | ); 59 | const hexString = formatAndPrint(data); 60 | 61 | clientSocket.write(data); 62 | clientSocket.write(Buffer.from(hexString, 'hex')); // Forwarding the data to the client 63 | }); 64 | 65 | clientSocket.on('end', () => { 66 | console.log('Client disconnected'); 67 | targetSocket.end(); 68 | }); 69 | 70 | targetSocket.on('end', () => { 71 | console.log('Target server disconnected'); 72 | clientSocket.end(); 73 | }); 74 | 75 | clientSocket.on('error', err => console.error(`Client socket error: ${err.message}`)); 76 | targetSocket.on('error', err => console.error(`Target socket error: ${err.message}`)); 77 | }); 78 | 79 | clientSocket.on('end', () => { 80 | console.log('Client disconnected'); 81 | targetSocket.end(); 82 | }); 83 | 84 | clientSocket.on('error', err => console.error(`Client socket error: ${err.message}`)); 85 | }); 86 | 87 | server.listen(proxyPort, proxyHost, () => { 88 | console.log(`Proxy server listening on ${proxyHost}:${proxyPort}`); 89 | }); 90 | 91 | function formatAndPrint(data: Buffer): string { 92 | const hexString = data.toString('hex'); 93 | for (let i = 0; i < hexString.length; i += 32) { 94 | const slice = hexString.slice(i, i + 32); 95 | const utf8String = Buffer.from(slice, 'hex').toString('utf-8'); 96 | console.log(`${slice.match(/.{1,8}/g)?.join(' ')}\t\t${utf8String}`); 97 | } 98 | console.log('\n\n'); 99 | return hexString; // Return the original hex string for forwarding 100 | } 101 | --------------------------------------------------------------------------------