@hotlap.ai/telemetry 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ export { IBT, parseIbt, parseIbtBuffer } from './ibt';
2
+ export type { IbtOptions } from './ibt';
3
+ export { IRSDK, createIRSDK } from './live';
4
+ export type { IRSDKOptions } from './live';
5
+ export type { SessionInfoMessage, TelemetryMessage, TelemetryFrame, LapChangeMessage, StatusMessage, ServerMessage, } from './telemetry-server';
6
+ export { VERSION, SIM_STATUS_URL, DATA_VALID_EVENT_NAME, MEMMAPFILE, MEMMAPFILESIZE, BROADCASTMSGNAME, VAR_TYPE_SIZES, HEADER_SIZE, VAR_HEADER_SIZE, VAR_BUFFER_SIZE, DISK_SUB_HEADER_OFFSET, DISK_SUB_HEADER_SIZE, YAML_CODE_PAGE, StatusField, EngineWarnings, Flags, TrkLoc, TrkSurf, SessionState, CameraState, BroadcastMsg, ChatCommandMode, PitCommandMode, TelemCommandMode, RpyStateMode, ReloadTexturesMode, RpySrchMode, RpyPosMode, CsMode, PitSvFlags, PitSvStatus, PaceMode, PaceFlags, CarLeftRight, FFBCommandMode, VideoCaptureMode, TrackWetness, } from './constants';
7
+ export type { VarType } from './constants';
8
+ export type { VarHeader, VarBuffer, Header, DiskSubHeader, SessionInfo, WeekendInfo, DriverInfo, Driver, SessionInfoData, Session, ResultPosition, FastestLap, TelemetryValue, TelemetryData, } from './types';
9
+ export { readInt32LE, readUint32LE, readFloat32LE, readFloat64LE, readBigUint64LE, readBoolLE, readCharLE, readString, readVarValue, readVarValueBatch, parseHeader, parseVarHeader, parseDiskSubHeader, parseVarHeaders, getLatestVarBuffer, } from './binary-reader';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAExC,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AAC5C,YAAY,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAE3C,YAAY,EACV,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,aAAa,GACd,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EACL,OAAO,EACP,cAAc,EACd,qBAAqB,EACrB,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,WAAW,EACX,cAAc,EACd,KAAK,EACL,MAAM,EACN,OAAO,EACP,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,eAAe,EACf,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,MAAM,EACN,UAAU,EACV,WAAW,EACX,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,YAAY,GACb,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,YAAY,EACV,SAAS,EACT,SAAS,EACT,MAAM,EACN,aAAa,EACb,WAAW,EACX,WAAW,EACX,UAAU,EACV,MAAM,EACN,eAAe,EACf,OAAO,EACP,cAAc,EACd,UAAU,EACV,cAAc,EACd,aAAa,GACd,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,WAAW,EACX,YAAY,EACZ,aAAa,EACb,aAAa,EACb,eAAe,EACf,UAAU,EACV,UAAU,EACV,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,WAAW,EACX,cAAc,EACd,kBAAkB,EAClB,eAAe,EACf,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,126 @@
1
+ // @bun
2
+ import {
3
+ IBT,
4
+ parseIbt,
5
+ parseIbtBuffer
6
+ } from "./ibt.js";
7
+ import {
8
+ IRSDK,
9
+ createIRSDK
10
+ } from "./live.js";
11
+ import"./index-k5bgtn4c.js";
12
+ import {
13
+ getLatestVarBuffer,
14
+ parseDiskSubHeader,
15
+ parseHeader,
16
+ parseVarHeader,
17
+ parseVarHeaders,
18
+ readBigUint64LE,
19
+ readBoolLE,
20
+ readCharLE,
21
+ readFloat32LE,
22
+ readFloat64LE,
23
+ readInt32LE,
24
+ readString,
25
+ readUint32LE,
26
+ readVarValue,
27
+ readVarValueBatch
28
+ } from "./binary-reader.js";
29
+ import {
30
+ BROADCASTMSGNAME,
31
+ BroadcastMsg,
32
+ CameraState,
33
+ CarLeftRight,
34
+ ChatCommandMode,
35
+ CsMode,
36
+ DATA_VALID_EVENT_NAME,
37
+ DISK_SUB_HEADER_OFFSET,
38
+ DISK_SUB_HEADER_SIZE,
39
+ EngineWarnings,
40
+ FFBCommandMode,
41
+ Flags,
42
+ HEADER_SIZE,
43
+ MEMMAPFILE,
44
+ MEMMAPFILESIZE,
45
+ PaceFlags,
46
+ PaceMode,
47
+ PitCommandMode,
48
+ PitSvFlags,
49
+ PitSvStatus,
50
+ ReloadTexturesMode,
51
+ RpyPosMode,
52
+ RpySrchMode,
53
+ RpyStateMode,
54
+ SIM_STATUS_URL,
55
+ SessionState,
56
+ StatusField,
57
+ TelemCommandMode,
58
+ TrackWetness,
59
+ TrkLoc,
60
+ TrkSurf,
61
+ VAR_BUFFER_SIZE,
62
+ VAR_HEADER_SIZE,
63
+ VAR_TYPE_SIZES,
64
+ VERSION,
65
+ VideoCaptureMode,
66
+ YAML_CODE_PAGE
67
+ } from "./constants.js";
68
+ export {
69
+ readVarValueBatch,
70
+ readVarValue,
71
+ readUint32LE,
72
+ readString,
73
+ readInt32LE,
74
+ readFloat64LE,
75
+ readFloat32LE,
76
+ readCharLE,
77
+ readBoolLE,
78
+ readBigUint64LE,
79
+ parseVarHeaders,
80
+ parseVarHeader,
81
+ parseIbtBuffer,
82
+ parseIbt,
83
+ parseHeader,
84
+ parseDiskSubHeader,
85
+ getLatestVarBuffer,
86
+ createIRSDK,
87
+ YAML_CODE_PAGE,
88
+ VideoCaptureMode,
89
+ VERSION,
90
+ VAR_TYPE_SIZES,
91
+ VAR_HEADER_SIZE,
92
+ VAR_BUFFER_SIZE,
93
+ TrkSurf,
94
+ TrkLoc,
95
+ TrackWetness,
96
+ TelemCommandMode,
97
+ StatusField,
98
+ SessionState,
99
+ SIM_STATUS_URL,
100
+ RpyStateMode,
101
+ RpySrchMode,
102
+ RpyPosMode,
103
+ ReloadTexturesMode,
104
+ PitSvStatus,
105
+ PitSvFlags,
106
+ PitCommandMode,
107
+ PaceMode,
108
+ PaceFlags,
109
+ MEMMAPFILESIZE,
110
+ MEMMAPFILE,
111
+ IRSDK,
112
+ IBT,
113
+ HEADER_SIZE,
114
+ Flags,
115
+ FFBCommandMode,
116
+ EngineWarnings,
117
+ DISK_SUB_HEADER_SIZE,
118
+ DISK_SUB_HEADER_OFFSET,
119
+ DATA_VALID_EVENT_NAME,
120
+ CsMode,
121
+ ChatCommandMode,
122
+ CarLeftRight,
123
+ CameraState,
124
+ BroadcastMsg,
125
+ BROADCASTMSGNAME
126
+ };
package/dist/live.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ import type { TelemetryValue } from './types';
2
+ export interface IRSDKOptions {
3
+ parseYamlAsync?: boolean;
4
+ }
5
+ export declare class IRSDK {
6
+ private parseYamlAsync;
7
+ private sharedMemPtr;
8
+ private sharedMemBuffer;
9
+ private sharedMemView;
10
+ private fileMappingHandle;
11
+ private dataValidEvent;
12
+ private header;
13
+ private varHeaders;
14
+ private varHeadersNames;
15
+ private sessionInfoCache;
16
+ private broadcastMsgId;
17
+ private lastSessionInfoUpdate;
18
+ private workaroundConnectedState;
19
+ private frozenBuffer;
20
+ private frozenView;
21
+ private frozenBufOffset;
22
+ private testMode;
23
+ isInitialized: boolean;
24
+ constructor(options?: IRSDKOptions);
25
+ get isConnected(): boolean;
26
+ get sessionInfoUpdate(): number;
27
+ get varHeaderNames(): string[] | null;
28
+ startup(testFile?: string, dumpTo?: string): Promise<boolean>;
29
+ private startupFromFile;
30
+ shutdown(): void;
31
+ get(key: string): TelemetryValue | null;
32
+ freezeVarBufferLatest(): void;
33
+ unfreezeVarBufferLatest(): void;
34
+ private checkSimStatus;
35
+ private waitValidDataEvent;
36
+ getSessionInfo<T = unknown>(key: string): T | null;
37
+ private cleanYaml;
38
+ private getBroadcastMsgId;
39
+ private broadcastMsg;
40
+ private padCarNum;
41
+ camSwitchPos(position?: number, group?: number, camera?: number): boolean;
42
+ camSwitchNum(carNumber?: string | number, group?: number, camera?: number): boolean;
43
+ camSetState(cameraState?: 4): boolean;
44
+ replaySetPlaySpeed(speed?: number, slowMotion?: boolean): boolean;
45
+ replaySetPlayPosition(posMode?: 0, frameNum?: number): boolean;
46
+ replaySearch(searchMode?: 0): boolean;
47
+ replaySetState(stateMode?: 0): boolean;
48
+ reloadAllTextures(): boolean;
49
+ reloadTexture(carIdx?: number): boolean;
50
+ chatCommand(chatCommandMode?: 1): boolean;
51
+ chatCommandMacro(macroNum?: number): boolean;
52
+ pitCommand(pitCommandMode?: 0, variable?: number): boolean;
53
+ telemCommand(telemCommandMode?: 0): boolean;
54
+ ffbCommand(ffbCommandMode?: 0, value?: number): boolean;
55
+ replaySearchSessionTime(sessionNum?: number, sessionTimeMs?: number): boolean;
56
+ videoCapture(videoCaptureMode?: 0): boolean;
57
+ }
58
+ export declare function createIRSDK(options?: IRSDKOptions): IRSDK;
59
+ //# sourceMappingURL=live.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../src/live.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,cAAc,EAAE,MAAM,SAAS,CAAC;AAuGjE,MAAM,WAAW,YAAY;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,qBAAa,KAAK;IAChB,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,YAAY,CAAiB;IACrC,OAAO,CAAC,eAAe,CAA2B;IAClD,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAAuC;IACzD,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,gBAAgB,CAA6D;IACrF,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,qBAAqB,CAAK;IAClC,OAAO,CAAC,wBAAwB,CAAK;IACrC,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,UAAU,CAAyB;IAC3C,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,QAAQ,CAAS;IACzB,aAAa,UAAS;gBAEV,OAAO,GAAE,YAAiB;IAItC,IAAI,WAAW,IAAI,OAAO,CAoBzB;IAED,IAAI,iBAAiB,IAAI,MAAM,CAE9B;IAED,IAAI,cAAc,IAAI,MAAM,EAAE,GAAG,IAAI,CAMpC;IAEK,OAAO,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAkErD,eAAe;IAqB7B,QAAQ,IAAI,IAAI;IAiChB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAiBvC,qBAAqB,IAAI,IAAI;IAa7B,uBAAuB,IAAI,IAAI;YAMjB,cAAc;IAU5B,OAAO,CAAC,kBAAkB;IAQ1B,cAAc,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IAoClD,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,iBAAiB;IAUzB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,SAAS;IAcjB,YAAY,CAAC,QAAQ,SAAI,EAAE,KAAK,SAAI,EAAE,MAAM,SAAI,GAAG,OAAO;IAI1D,YAAY,CAAC,SAAS,GAAE,MAAM,GAAG,MAAY,EAAE,KAAK,SAAI,EAAE,MAAM,SAAI,GAAG,OAAO;IAI9E,WAAW,CAAC,WAAW,IAA8B,GAAG,OAAO;IAI/D,kBAAkB,CAAC,KAAK,SAAI,EAAE,UAAU,UAAQ,GAAG,OAAO;IAI1D,qBAAqB,CAAC,OAAO,IAAmB,EAAE,QAAQ,SAAI,GAAG,OAAO;IAIxE,YAAY,CAAC,UAAU,IAAuB,GAAG,OAAO;IAIxD,cAAc,CAAC,SAAS,IAA0B,GAAG,OAAO;IAI5D,iBAAiB,IAAI,OAAO;IAI5B,aAAa,CAAC,MAAM,SAAI,GAAG,OAAO;IAIlC,WAAW,CAAC,eAAe,IAA6B,GAAG,OAAO;IAIlE,gBAAgB,CAAC,QAAQ,SAAI,GAAG,OAAO;IAIvC,UAAU,CAAC,cAAc,IAAuB,EAAE,QAAQ,SAAI,GAAG,OAAO;IAIxE,YAAY,CAAC,gBAAgB,IAAwB,GAAG,OAAO;IAI/D,UAAU,CAAC,cAAc,IAAuC,EAAE,KAAK,SAAI,GAAG,OAAO;IAIrF,uBAAuB,CAAC,UAAU,SAAI,EAAE,aAAa,SAAI,GAAG,OAAO;IAInE,YAAY,CAAC,gBAAgB,IAAuC,GAAG,OAAO;CAG/E;AAED,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,YAAY,GAAG,KAAK,CAEzD"}
package/dist/live.js ADDED
@@ -0,0 +1,420 @@
1
+ // @bun
2
+ import {
3
+ $parse
4
+ } from "./index-k5bgtn4c.js";
5
+ import {
6
+ getLatestVarBuffer,
7
+ parseHeader,
8
+ parseVarHeaders,
9
+ readString,
10
+ readVarValue
11
+ } from "./binary-reader.js";
12
+ import {
13
+ BroadcastMsg,
14
+ CameraState,
15
+ ChatCommandMode,
16
+ FFBCommandMode,
17
+ PitCommandMode,
18
+ ReloadTexturesMode,
19
+ RpyPosMode,
20
+ RpySrchMode,
21
+ RpyStateMode,
22
+ SIM_STATUS_URL,
23
+ StatusField,
24
+ TelemCommandMode,
25
+ VideoCaptureMode,
26
+ __require
27
+ } from "./constants.js";
28
+
29
+ // src/live.ts
30
+ var kernel32 = null;
31
+ var user32 = null;
32
+ function loadKernel32() {
33
+ const { dlopen, FFIType, suffix } = __require("bun:ffi");
34
+ return dlopen(`kernel32.${suffix}`, {
35
+ OpenEventW: {
36
+ args: [FFIType.u32, FFIType.bool, FFIType.ptr],
37
+ returns: FFIType.ptr
38
+ },
39
+ OpenFileMappingW: {
40
+ args: [FFIType.u32, FFIType.bool, FFIType.ptr],
41
+ returns: FFIType.ptr
42
+ },
43
+ MapViewOfFile: {
44
+ args: [FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.u32, FFIType.u32],
45
+ returns: FFIType.ptr
46
+ },
47
+ UnmapViewOfFile: {
48
+ args: [FFIType.ptr],
49
+ returns: FFIType.bool
50
+ },
51
+ CloseHandle: {
52
+ args: [FFIType.ptr],
53
+ returns: FFIType.bool
54
+ },
55
+ WaitForSingleObject: {
56
+ args: [FFIType.ptr, FFIType.u32],
57
+ returns: FFIType.u32
58
+ }
59
+ });
60
+ }
61
+ function loadUser32() {
62
+ const { dlopen, FFIType, suffix } = __require("bun:ffi");
63
+ return dlopen(`user32.${suffix}`, {
64
+ RegisterWindowMessageW: {
65
+ args: [FFIType.ptr],
66
+ returns: FFIType.u32
67
+ },
68
+ SendNotifyMessageW: {
69
+ args: [FFIType.ptr, FFIType.u32, FFIType.u32, FFIType.u32],
70
+ returns: FFIType.bool
71
+ }
72
+ });
73
+ }
74
+ var FILE_MAP_READ = 4;
75
+ var SYNCHRONIZE = 1048576;
76
+ var WAIT_OBJECT_0 = 0;
77
+ var HWND_BROADCAST = 65535;
78
+ var DATA_VALID_EVENT_NAME = "Local\\IRSDKDataValidEvent";
79
+ var MEMMAPFILE = "Local\\IRSDKMemMapFileName";
80
+ var MEMMAPFILESIZE = 1164 * 1024;
81
+ var BROADCASTMSGNAME = "IRSDK_BROADCASTMSG";
82
+ function toWideString(str) {
83
+ const buf = new Uint8Array((str.length + 1) * 2);
84
+ for (let i = 0;i < str.length; i++) {
85
+ const code = str.charCodeAt(i);
86
+ buf[i * 2] = code & 255;
87
+ buf[i * 2 + 1] = code >> 8 & 255;
88
+ }
89
+ return buf;
90
+ }
91
+ function ensureFFILoaded() {
92
+ if (process.platform !== "win32") {
93
+ return false;
94
+ }
95
+ try {
96
+ if (!kernel32)
97
+ kernel32 = loadKernel32();
98
+ if (!user32)
99
+ user32 = loadUser32();
100
+ return true;
101
+ } catch {
102
+ return false;
103
+ }
104
+ }
105
+
106
+ class IRSDK {
107
+ parseYamlAsync;
108
+ sharedMemPtr = null;
109
+ sharedMemBuffer = null;
110
+ sharedMemView = null;
111
+ fileMappingHandle = null;
112
+ dataValidEvent = null;
113
+ header = null;
114
+ varHeaders = null;
115
+ varHeadersNames = null;
116
+ sessionInfoCache = new Map;
117
+ broadcastMsgId = null;
118
+ lastSessionInfoUpdate = 0;
119
+ workaroundConnectedState = 0;
120
+ frozenBuffer = null;
121
+ frozenView = null;
122
+ frozenBufOffset = 0;
123
+ testMode = false;
124
+ isInitialized = false;
125
+ constructor(options = {}) {
126
+ this.parseYamlAsync = options.parseYamlAsync ?? false;
127
+ }
128
+ get isConnected() {
129
+ if (!this.header)
130
+ return false;
131
+ if (this.header.status === StatusField.STATUS_CONNECTED) {
132
+ this.workaroundConnectedState = 0;
133
+ }
134
+ if (this.workaroundConnectedState === 0 && this.header.status !== StatusField.STATUS_CONNECTED) {
135
+ this.workaroundConnectedState = 1;
136
+ }
137
+ if (this.workaroundConnectedState === 1 && this.get("SessionNum") === null) {
138
+ this.workaroundConnectedState = 2;
139
+ }
140
+ if (this.workaroundConnectedState === 2 && this.get("SessionNum") !== null) {
141
+ this.workaroundConnectedState = 3;
142
+ }
143
+ return (this.dataValidEvent !== null || this.testMode) && (this.header.status === StatusField.STATUS_CONNECTED || this.workaroundConnectedState === 3);
144
+ }
145
+ get sessionInfoUpdate() {
146
+ return this.header?.sessionInfoUpdate ?? 0;
147
+ }
148
+ get varHeaderNames() {
149
+ if (!this.varHeaders)
150
+ return null;
151
+ if (!this.varHeadersNames) {
152
+ this.varHeadersNames = Array.from(this.varHeaders.keys());
153
+ }
154
+ return this.varHeadersNames;
155
+ }
156
+ async startup(testFile, dumpTo) {
157
+ if (testFile) {
158
+ return this.startupFromFile(testFile, dumpTo);
159
+ }
160
+ if (!ensureFFILoaded() || !kernel32) {
161
+ console.error("FFI not available - live telemetry requires Windows");
162
+ return false;
163
+ }
164
+ const simRunning = await this.checkSimStatus();
165
+ if (!simRunning)
166
+ return false;
167
+ const { ptr, toArrayBuffer } = __require("bun:ffi");
168
+ const eventNameWide = toWideString(DATA_VALID_EVENT_NAME);
169
+ this.dataValidEvent = kernel32.symbols.OpenEventW(SYNCHRONIZE, false, ptr(eventNameWide));
170
+ if (!this.waitValidDataEvent()) {
171
+ this.dataValidEvent = null;
172
+ return false;
173
+ }
174
+ const fileNameWide = toWideString(MEMMAPFILE);
175
+ this.fileMappingHandle = kernel32.symbols.OpenFileMappingW(FILE_MAP_READ, false, ptr(fileNameWide));
176
+ if (!this.fileMappingHandle)
177
+ return false;
178
+ this.sharedMemPtr = kernel32.symbols.MapViewOfFile(this.fileMappingHandle, FILE_MAP_READ, 0, 0, MEMMAPFILESIZE);
179
+ if (!this.sharedMemPtr) {
180
+ kernel32.symbols.CloseHandle(this.fileMappingHandle);
181
+ this.fileMappingHandle = null;
182
+ return false;
183
+ }
184
+ const arrayBuffer = toArrayBuffer(this.sharedMemPtr, 0, MEMMAPFILESIZE);
185
+ this.sharedMemBuffer = new Uint8Array(arrayBuffer);
186
+ this.sharedMemView = new DataView(arrayBuffer);
187
+ if (this.sharedMemView) {
188
+ if (dumpTo) {
189
+ await Bun.write(dumpTo, this.sharedMemBuffer);
190
+ }
191
+ this.header = parseHeader(this.sharedMemView);
192
+ this.isInitialized = this.header.version >= 1 && this.header.varBuf.length > 0;
193
+ if (this.isInitialized) {
194
+ this.varHeaders = parseVarHeaders(this.sharedMemView, this.sharedMemBuffer, this.header);
195
+ }
196
+ }
197
+ return this.isInitialized;
198
+ }
199
+ async startupFromFile(testFile, dumpTo) {
200
+ this.testMode = true;
201
+ const file = Bun.file(testFile);
202
+ const arrayBuffer = await file.arrayBuffer();
203
+ this.sharedMemBuffer = new Uint8Array(arrayBuffer);
204
+ this.sharedMemView = new DataView(arrayBuffer);
205
+ if (dumpTo) {
206
+ await Bun.write(dumpTo, this.sharedMemBuffer);
207
+ }
208
+ this.header = parseHeader(this.sharedMemView);
209
+ this.isInitialized = this.header.version >= 1 && this.header.varBuf.length > 0;
210
+ if (this.isInitialized) {
211
+ this.varHeaders = parseVarHeaders(this.sharedMemView, this.sharedMemBuffer, this.header);
212
+ }
213
+ return this.isInitialized;
214
+ }
215
+ shutdown() {
216
+ this.isInitialized = false;
217
+ this.lastSessionInfoUpdate = 0;
218
+ if (kernel32) {
219
+ if (this.sharedMemPtr) {
220
+ kernel32.symbols.UnmapViewOfFile(this.sharedMemPtr);
221
+ this.sharedMemPtr = null;
222
+ }
223
+ if (this.fileMappingHandle) {
224
+ kernel32.symbols.CloseHandle(this.fileMappingHandle);
225
+ this.fileMappingHandle = null;
226
+ }
227
+ if (this.dataValidEvent) {
228
+ kernel32.symbols.CloseHandle(this.dataValidEvent);
229
+ this.dataValidEvent = null;
230
+ }
231
+ }
232
+ this.sharedMemBuffer = null;
233
+ this.sharedMemView = null;
234
+ this.header = null;
235
+ this.varHeaders = null;
236
+ this.varHeadersNames = null;
237
+ this.sessionInfoCache.clear();
238
+ this.broadcastMsgId = null;
239
+ this.frozenBuffer = null;
240
+ this.frozenView = null;
241
+ this.testMode = false;
242
+ }
243
+ get(key) {
244
+ if (!this.header || !this.varHeaders)
245
+ return null;
246
+ const varHeader = this.varHeaders.get(key);
247
+ if (!varHeader) {
248
+ return this.getSessionInfo(key);
249
+ }
250
+ const view = this.frozenView ?? this.sharedMemView;
251
+ if (!view)
252
+ return null;
253
+ const varBuf = this.frozenView ? { bufOffset: this.frozenBufOffset } : getLatestVarBuffer(this.header.varBuf);
254
+ const offset = varBuf.bufOffset + varHeader.offset;
255
+ return readVarValue(view, offset, varHeader.type, varHeader.count);
256
+ }
257
+ freezeVarBufferLatest() {
258
+ this.unfreezeVarBufferLatest();
259
+ if (!this.header || !this.sharedMemBuffer)
260
+ return;
261
+ this.waitValidDataEvent();
262
+ const varBuf = this.header.varBuf.reduce((a, b) => a.tickCount > b.tickCount ? a : b);
263
+ this.frozenBufOffset = 0;
264
+ this.frozenBuffer = this.sharedMemBuffer.slice(varBuf.bufOffset, varBuf.bufOffset + this.header.bufLen);
265
+ this.frozenView = new DataView(this.frozenBuffer.buffer);
266
+ }
267
+ unfreezeVarBufferLatest() {
268
+ this.frozenBuffer = null;
269
+ this.frozenView = null;
270
+ this.frozenBufOffset = 0;
271
+ }
272
+ async checkSimStatus() {
273
+ try {
274
+ const response = await fetch(SIM_STATUS_URL);
275
+ const text = await response.text();
276
+ return text.includes("running:1");
277
+ } catch {
278
+ return false;
279
+ }
280
+ }
281
+ waitValidDataEvent() {
282
+ if (!kernel32)
283
+ return true;
284
+ if (this.dataValidEvent) {
285
+ return kernel32.symbols.WaitForSingleObject(this.dataValidEvent, 32) === WAIT_OBJECT_0;
286
+ }
287
+ return true;
288
+ }
289
+ getSessionInfo(key) {
290
+ if (!this.header || !this.sharedMemBuffer)
291
+ return null;
292
+ if (this.lastSessionInfoUpdate < this.header.sessionInfoUpdate) {
293
+ this.lastSessionInfoUpdate = this.header.sessionInfoUpdate;
294
+ for (const [k, v] of this.sessionInfoCache) {
295
+ if (v.data) {
296
+ this.sessionInfoCache.set(k, { ...v, data: null });
297
+ }
298
+ }
299
+ }
300
+ const cached = this.sessionInfoCache.get(key);
301
+ if (cached?.data) {
302
+ return cached.data;
303
+ }
304
+ const start = this.header.sessionInfoOffset;
305
+ const len = this.header.sessionInfoLen;
306
+ const raw = readString(this.sharedMemBuffer, start, len);
307
+ const sectionMatch = raw.match(new RegExp(`
308
+ ${key}:
309
+ ([\\s\\S]*?)(?=
310
+
311
+ |$)`));
312
+ if (!sectionMatch)
313
+ return null;
314
+ try {
315
+ const sectionYaml = `${key}:
316
+ ${sectionMatch[1]}`;
317
+ const cleaned = this.cleanYaml(sectionYaml);
318
+ const parsed = $parse(cleaned);
319
+ const result = parsed?.[key] ?? null;
320
+ this.sessionInfoCache.set(key, { data: result, update: this.lastSessionInfoUpdate });
321
+ return result;
322
+ } catch {
323
+ return null;
324
+ }
325
+ }
326
+ cleanYaml(yaml) {
327
+ let cleaned = yaml.replace(/[\x81\x8D\x8F\x90\x9D]/g, " ").replace(/[^\x09\x0A\x0D\x20-\x7E\xA0-\xFF]/g, "");
328
+ cleaned = cleaned.replace(/((?:DriverSetupName|UserName|TeamName|AbbrevName|Initials): )(.*)/g, (_, prefix, value) => {
329
+ const escaped = value.replace(/["\\]/g, "\\$&");
330
+ return `${prefix}"${escaped}"`;
331
+ });
332
+ cleaned = cleaned.replace(/(\w+: )(,.*)/g, '$1"$2"');
333
+ return cleaned;
334
+ }
335
+ getBroadcastMsgId() {
336
+ if (!user32)
337
+ return 0;
338
+ if (this.broadcastMsgId === null) {
339
+ const { ptr } = __require("bun:ffi");
340
+ const msgNameWide = toWideString(BROADCASTMSGNAME);
341
+ this.broadcastMsgId = user32.symbols.RegisterWindowMessageW(ptr(msgNameWide));
342
+ }
343
+ return this.broadcastMsgId;
344
+ }
345
+ broadcastMsg(broadcastType, var1 = 0, var2 = 0, var3 = 0) {
346
+ if (!user32)
347
+ return false;
348
+ return user32.symbols.SendNotifyMessageW(HWND_BROADCAST, this.getBroadcastMsgId(), (broadcastType | var1 << 16) >>> 0, (var2 | var3 << 16) >>> 0);
349
+ }
350
+ padCarNum(num) {
351
+ const numStr = String(num);
352
+ const numLen = numStr.length;
353
+ let zero = numLen - numStr.replace(/^0+/, "").length;
354
+ if (zero > 0 && numLen === zero)
355
+ zero--;
356
+ const numVal = parseInt(numStr, 10);
357
+ if (zero) {
358
+ const numPlace = numVal > 99 ? 3 : numVal > 9 ? 2 : 1;
359
+ return numVal + 1000 * (numPlace + zero);
360
+ }
361
+ return numVal;
362
+ }
363
+ camSwitchPos(position = 0, group = 1, camera = 0) {
364
+ return this.broadcastMsg(BroadcastMsg.CAM_SWITCH_POS, position, group, camera);
365
+ }
366
+ camSwitchNum(carNumber = "1", group = 1, camera = 0) {
367
+ return this.broadcastMsg(BroadcastMsg.CAM_SWITCH_NUM, this.padCarNum(carNumber), group, camera);
368
+ }
369
+ camSetState(cameraState = CameraState.CAM_TOOL_ACTIVE) {
370
+ return this.broadcastMsg(BroadcastMsg.CAM_SET_STATE, cameraState);
371
+ }
372
+ replaySetPlaySpeed(speed = 0, slowMotion = false) {
373
+ return this.broadcastMsg(BroadcastMsg.REPLAY_SET_PLAY_SPEED, speed, slowMotion ? 1 : 0);
374
+ }
375
+ replaySetPlayPosition(posMode = RpyPosMode.BEGIN, frameNum = 0) {
376
+ return this.broadcastMsg(BroadcastMsg.REPLAY_SET_PLAY_POSITION, posMode, frameNum);
377
+ }
378
+ replaySearch(searchMode = RpySrchMode.TO_START) {
379
+ return this.broadcastMsg(BroadcastMsg.REPLAY_SEARCH, searchMode);
380
+ }
381
+ replaySetState(stateMode = RpyStateMode.ERASE_TAPE) {
382
+ return this.broadcastMsg(BroadcastMsg.REPLAY_SET_STATE, stateMode);
383
+ }
384
+ reloadAllTextures() {
385
+ return this.broadcastMsg(BroadcastMsg.RELOAD_TEXTURES, ReloadTexturesMode.ALL);
386
+ }
387
+ reloadTexture(carIdx = 0) {
388
+ return this.broadcastMsg(BroadcastMsg.RELOAD_TEXTURES, ReloadTexturesMode.CAR_IDX, carIdx);
389
+ }
390
+ chatCommand(chatCommandMode = ChatCommandMode.BEGIN_CHAT) {
391
+ return this.broadcastMsg(BroadcastMsg.CHAT_COMMAND, chatCommandMode);
392
+ }
393
+ chatCommandMacro(macroNum = 0) {
394
+ return this.broadcastMsg(BroadcastMsg.CHAT_COMMAND, ChatCommandMode.MACRO, macroNum);
395
+ }
396
+ pitCommand(pitCommandMode = PitCommandMode.CLEAR, variable = 0) {
397
+ return this.broadcastMsg(BroadcastMsg.PIT_COMMAND, pitCommandMode, variable);
398
+ }
399
+ telemCommand(telemCommandMode = TelemCommandMode.STOP) {
400
+ return this.broadcastMsg(BroadcastMsg.TELEM_COMMAND, telemCommandMode);
401
+ }
402
+ ffbCommand(ffbCommandMode = FFBCommandMode.FFB_COMMAND_MAX_FORCE, value = 0) {
403
+ return this.broadcastMsg(BroadcastMsg.FFB_COMMAND, ffbCommandMode, Math.floor(value * 65536));
404
+ }
405
+ replaySearchSessionTime(sessionNum = 0, sessionTimeMs = 0) {
406
+ return this.broadcastMsg(BroadcastMsg.REPLAY_SEARCH_SESSION_TIME, sessionNum, sessionTimeMs);
407
+ }
408
+ videoCapture(videoCaptureMode = VideoCaptureMode.TRIGGER_SCREEN_SHOT) {
409
+ return this.broadcastMsg(BroadcastMsg.VIDEO_CAPTURE, videoCaptureMode);
410
+ }
411
+ }
412
+ function createIRSDK(options) {
413
+ return new IRSDK(options);
414
+ }
415
+ export {
416
+ createIRSDK,
417
+ IRSDK
418
+ };
419
+
420
+ export { IRSDK, createIRSDK };
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env bun
2
+ export interface SessionInfoMessage {
3
+ type: 'session_info';
4
+ data: {
5
+ trackId: number;
6
+ trackName: string;
7
+ trackDisplayName: string;
8
+ trackLength: string;
9
+ trackConfig: string;
10
+ carId: number;
11
+ carName: string;
12
+ carNumber: string;
13
+ driverName: string;
14
+ driverId: number;
15
+ sessionType: string;
16
+ sessionNum: number;
17
+ tickRate: number;
18
+ };
19
+ }
20
+ export interface TelemetryMessage {
21
+ type: 'telemetry';
22
+ data: TelemetryFrame;
23
+ }
24
+ export interface TelemetryFrame {
25
+ tick: number;
26
+ sessionTime: number;
27
+ lap: number;
28
+ lapDistPct: number;
29
+ speed: number;
30
+ rpm: number;
31
+ gear: number;
32
+ throttle: number;
33
+ brake: number;
34
+ clutch: number;
35
+ steer: number;
36
+ lat: number;
37
+ lon: number;
38
+ alt: number;
39
+ yaw: number;
40
+ pitch: number;
41
+ roll: number;
42
+ lapCurrentLapTime: number;
43
+ lapLastLapTime: number;
44
+ lapBestLapTime: number;
45
+ lapDeltaToBestLap: number;
46
+ fuelLevel: number;
47
+ fuelLevelPct: number;
48
+ oilTemp: number;
49
+ waterTemp: number;
50
+ isOnTrack: boolean;
51
+ playerPosition: number;
52
+ playerClassPosition: number;
53
+ brakeABSactive: boolean;
54
+ dcBrakeBias: number;
55
+ dcTractionControl: number;
56
+ dcABS: number;
57
+ tireTempLF: number;
58
+ tireTempRF: number;
59
+ tireTempLR: number;
60
+ tireTempRR: number;
61
+ }
62
+ export interface LapChangeMessage {
63
+ type: 'lap_change';
64
+ data: {
65
+ fromLap: number;
66
+ toLap: number;
67
+ lapTime: number;
68
+ sessionNum: number;
69
+ };
70
+ }
71
+ export interface StatusMessage {
72
+ type: 'status';
73
+ data: {
74
+ connected: boolean;
75
+ initialized: boolean;
76
+ error?: string;
77
+ };
78
+ }
79
+ export type ServerMessage = SessionInfoMessage | TelemetryMessage | LapChangeMessage | StatusMessage;
80
+ //# sourceMappingURL=telemetry-server.d.ts.map