@hotlap.ai/telemetry 1.0.0 → 1.1.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.
package/dist/live.js CHANGED
@@ -1,420 +1,13 @@
1
1
  // @bun
2
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
- }
3
+ IRSDK,
4
+ createIRSDK
5
+ } from "./index-t4qz2011.js";
6
+ import"./index-hxw291vy.js";
7
+ import"./index-c5jf6rjt.js";
8
+ import"./index-wb6ebd0j.js";
9
+ import"./index-ftpvnye9.js";
415
10
  export {
416
11
  createIRSDK,
417
12
  IRSDK
418
13
  };
419
-
420
- export { IRSDK, createIRSDK };