ai-remote 0.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.
Files changed (114) hide show
  1. package/README.md +50 -0
  2. package/dist/index.js +7 -0
  3. package/dist/index.js.map +7 -0
  4. package/dist/protocols/index.js +35 -0
  5. package/dist/protocols/index.js.map +7 -0
  6. package/dist/protocols/rdp/buffer.js +170 -0
  7. package/dist/protocols/rdp/buffer.js.map +7 -0
  8. package/dist/protocols/rdp/caps.js +260 -0
  9. package/dist/protocols/rdp/caps.js.map +7 -0
  10. package/dist/protocols/rdp/cert.js +135 -0
  11. package/dist/protocols/rdp/cert.js.map +7 -0
  12. package/dist/protocols/rdp/client.js +1068 -0
  13. package/dist/protocols/rdp/client.js.map +7 -0
  14. package/dist/protocols/rdp/cliprdr.js +240 -0
  15. package/dist/protocols/rdp/cliprdr.js.map +7 -0
  16. package/dist/protocols/rdp/credssp.js +254 -0
  17. package/dist/protocols/rdp/credssp.js.map +7 -0
  18. package/dist/protocols/rdp/crypto.js +369 -0
  19. package/dist/protocols/rdp/crypto.js.map +7 -0
  20. package/dist/protocols/rdp/display.js +589 -0
  21. package/dist/protocols/rdp/display.js.map +7 -0
  22. package/dist/protocols/rdp/gcc.js +132 -0
  23. package/dist/protocols/rdp/gcc.js.map +7 -0
  24. package/dist/protocols/rdp/keymap.js +132 -0
  25. package/dist/protocols/rdp/keymap.js.map +7 -0
  26. package/dist/protocols/rdp/mcs.js +223 -0
  27. package/dist/protocols/rdp/mcs.js.map +7 -0
  28. package/dist/protocols/rdp/ntlm.js +305 -0
  29. package/dist/protocols/rdp/ntlm.js.map +7 -0
  30. package/dist/protocols/rdp/pdu.js +444 -0
  31. package/dist/protocols/rdp/pdu.js.map +7 -0
  32. package/dist/protocols/rdp/rail.js +383 -0
  33. package/dist/protocols/rdp/rail.js.map +7 -0
  34. package/dist/protocols/rdp/rle.js +276 -0
  35. package/dist/protocols/rdp/rle.js.map +7 -0
  36. package/dist/protocols/rdp/sec.js +156 -0
  37. package/dist/protocols/rdp/sec.js.map +7 -0
  38. package/dist/protocols/rdp/session.js +307 -0
  39. package/dist/protocols/rdp/session.js.map +7 -0
  40. package/dist/protocols/rdp/tls.js +354 -0
  41. package/dist/protocols/rdp/tls.js.map +7 -0
  42. package/dist/protocols/rdp/vchannel.js +59 -0
  43. package/dist/protocols/rdp/vchannel.js.map +7 -0
  44. package/dist/protocols/rdp/x224.js +110 -0
  45. package/dist/protocols/rdp/x224.js.map +7 -0
  46. package/dist/protocols/ssh/kex.js +177 -0
  47. package/dist/protocols/ssh/kex.js.map +7 -0
  48. package/dist/protocols/ssh/messages.js +55 -0
  49. package/dist/protocols/ssh/messages.js.map +7 -0
  50. package/dist/protocols/ssh/session.js +608 -0
  51. package/dist/protocols/ssh/session.js.map +7 -0
  52. package/dist/protocols/ssh/terminal.js +191 -0
  53. package/dist/protocols/ssh/terminal.js.map +7 -0
  54. package/dist/protocols/ssh/transport.js +611 -0
  55. package/dist/protocols/ssh/transport.js.map +7 -0
  56. package/dist/protocols/ssh/wire.js +189 -0
  57. package/dist/protocols/ssh/wire.js.map +7 -0
  58. package/dist/protocols/types.js +1 -0
  59. package/dist/protocols/types.js.map +7 -0
  60. package/dist/protocols/vnc/input.js +16 -0
  61. package/dist/protocols/vnc/input.js.map +7 -0
  62. package/dist/protocols/vnc/session.js +259 -0
  63. package/dist/protocols/vnc/session.js.map +7 -0
  64. package/dist/shared/connection.js +114 -0
  65. package/dist/shared/connection.js.map +7 -0
  66. package/dist/shared/device.js +1 -0
  67. package/dist/shared/device.js.map +7 -0
  68. package/dist/shared/hosts.js +133 -0
  69. package/dist/shared/hosts.js.map +7 -0
  70. package/dist/shared/icons.js +42 -0
  71. package/dist/shared/icons.js.map +7 -0
  72. package/dist/shared/protocol.js +40 -0
  73. package/dist/shared/protocol.js.map +7 -0
  74. package/dist/shared/signals.js +42 -0
  75. package/dist/shared/signals.js.map +7 -0
  76. package/package.json +82 -0
  77. package/src/index.ts +14 -0
  78. package/src/protocols/index.ts +72 -0
  79. package/src/protocols/rdp/buffer.ts +204 -0
  80. package/src/protocols/rdp/caps.ts +304 -0
  81. package/src/protocols/rdp/cert.ts +190 -0
  82. package/src/protocols/rdp/client.ts +1363 -0
  83. package/src/protocols/rdp/cliprdr.ts +344 -0
  84. package/src/protocols/rdp/credssp.ts +319 -0
  85. package/src/protocols/rdp/crypto.ts +416 -0
  86. package/src/protocols/rdp/display.ts +750 -0
  87. package/src/protocols/rdp/gcc.ts +162 -0
  88. package/src/protocols/rdp/keymap.ts +68 -0
  89. package/src/protocols/rdp/mcs.ts +283 -0
  90. package/src/protocols/rdp/ntlm.ts +380 -0
  91. package/src/protocols/rdp/pdu.ts +497 -0
  92. package/src/protocols/rdp/rail.ts +480 -0
  93. package/src/protocols/rdp/rle.ts +343 -0
  94. package/src/protocols/rdp/sec.ts +184 -0
  95. package/src/protocols/rdp/session.ts +393 -0
  96. package/src/protocols/rdp/tls.ts +452 -0
  97. package/src/protocols/rdp/vchannel.ts +89 -0
  98. package/src/protocols/rdp/x224.ts +139 -0
  99. package/src/protocols/ssh/kex.ts +227 -0
  100. package/src/protocols/ssh/messages.ts +54 -0
  101. package/src/protocols/ssh/session.ts +878 -0
  102. package/src/protocols/ssh/terminal.ts +242 -0
  103. package/src/protocols/ssh/transport.ts +732 -0
  104. package/src/protocols/ssh/wire.ts +220 -0
  105. package/src/protocols/types.ts +146 -0
  106. package/src/protocols/vnc/input.ts +13 -0
  107. package/src/protocols/vnc/novnc.d.ts +75 -0
  108. package/src/protocols/vnc/session.ts +367 -0
  109. package/src/shared/connection.ts +206 -0
  110. package/src/shared/device.ts +28 -0
  111. package/src/shared/hosts.ts +240 -0
  112. package/src/shared/icons.ts +49 -0
  113. package/src/shared/protocol.ts +55 -0
  114. package/src/shared/signals.ts +67 -0
@@ -0,0 +1,497 @@
1
+ // The share layer: PDU headers, the client info PDU, input events, and the
2
+ // server update PDUs this client understands (MS-RDPBCGR 2.2.8, 2.2.9, 2.2.1.13).
3
+
4
+ import { Reader, Writer } from './buffer';
5
+
6
+ export const PDU_TYPE = {
7
+ DEMAND_ACTIVE: 1,
8
+ CONFIRM_ACTIVE: 3,
9
+ DEACTIVATE_ALL: 6,
10
+ DATA: 7,
11
+ SERVER_REDIRECT: 10,
12
+ };
13
+
14
+ export const PDU_TYPE2 = {
15
+ UPDATE: 2,
16
+ CONTROL: 20,
17
+ POINTER: 27,
18
+ INPUT: 28,
19
+ SYNCHRONIZE: 31,
20
+ REFRESH_RECT: 33,
21
+ PLAY_SOUND: 34,
22
+ SUPPRESS_OUTPUT: 35,
23
+ SHUTDOWN_REQUEST: 36,
24
+ SHUTDOWN_DENIED: 37,
25
+ SAVE_SESSION_INFO: 38,
26
+ FONT_LIST: 39,
27
+ FONT_MAP: 40,
28
+ SET_ERROR_INFO: 47,
29
+ MONITOR_LAYOUT: 55,
30
+ };
31
+
32
+ export const UPDATE_TYPE = {
33
+ ORDERS: 0,
34
+ BITMAP: 1,
35
+ PALETTE: 2,
36
+ SYNCHRONIZE: 3,
37
+ };
38
+
39
+ export const CONTROL_ACTION = {
40
+ REQUEST_CONTROL: 1,
41
+ GRANTED_CONTROL: 2,
42
+ DETACH: 3,
43
+ COOPERATE: 4,
44
+ };
45
+
46
+ export const INPUT_EVENT = {
47
+ SYNC: 0x0000,
48
+ SCANCODE: 0x0004,
49
+ UNICODE: 0x0005,
50
+ MOUSE: 0x8001,
51
+ MOUSEX: 0x8002,
52
+ };
53
+
54
+ export const KEYBOARD_FLAG = {
55
+ EXTENDED: 0x0100,
56
+ DOWN: 0x4000,
57
+ RELEASE: 0x8000,
58
+ };
59
+
60
+ export const POINTER_FLAG = {
61
+ WHEEL: 0x0200,
62
+ HWHEEL: 0x0400,
63
+ WHEEL_NEGATIVE: 0x0100,
64
+ MOVE: 0x0800,
65
+ DOWN: 0x8000,
66
+ BUTTON1: 0x1000,
67
+ BUTTON2: 0x2000,
68
+ BUTTON3: 0x4000,
69
+ };
70
+
71
+ /** Translate browser-style wheel direction to the signed RDP rotation flags. */
72
+ export function pointerWheelFlags(delta: number, horizontal = false): number {
73
+ const steps = horizontal ? Math.sign(delta) : -Math.sign(delta);
74
+ if (steps === 0) return 0;
75
+ const wheelFlag = horizontal ? POINTER_FLAG.HWHEEL : POINTER_FLAG.WHEEL;
76
+ const rotation = 120;
77
+ return steps > 0
78
+ ? wheelFlag | rotation
79
+ : wheelFlag | POINTER_FLAG.WHEEL_NEGATIVE | ((-rotation) & 0xff);
80
+ }
81
+
82
+ export const TOGGLE_FLAG = {
83
+ SCROLL_LOCK: 0x0001,
84
+ NUM_LOCK: 0x0002,
85
+ CAPS_LOCK: 0x0004,
86
+ KANA_LOCK: 0x0008,
87
+ };
88
+
89
+ const POINTER_MESSAGE = {
90
+ SYSTEM: 0x0001,
91
+ POSITION: 0x0003,
92
+ COLOR: 0x0006,
93
+ CACHED: 0x0007,
94
+ POINTER: 0x0008,
95
+ LARGE: 0x0009,
96
+ };
97
+
98
+ const INFO_FLAG =
99
+ 0x00000001 | // INFO_MOUSE
100
+ 0x00000002 | // INFO_DISABLECTRLALTDEL
101
+ 0x00000010 | // INFO_UNICODE
102
+ 0x00000020 | // INFO_MAXIMIZESHELL
103
+ 0x00000040 | // INFO_LOGONNOTIFY
104
+ 0x00000100 | // INFO_ENABLEWINDOWSKEY
105
+ 0x00010000 | // INFO_LOGONERRORS
106
+ 0x00020000 | // INFO_MOUSE_HAS_WHEEL
107
+ 0x00080000; // INFO_NOAUDIOPLAYBACK
108
+
109
+ const INFO_AUTOLOGON = 0x00000008;
110
+ const INFO_RAIL = 0x00008000;
111
+
112
+ const ERROR_INFO_MESSAGES = {
113
+ 0x00000001: 'The host disconnected the session (an administrative API call ended it).',
114
+ 0x00000002: 'The host logged the session off.',
115
+ 0x00000003: 'The session was disconnected after being idle too long.',
116
+ 0x00000004: 'The session was disconnected because sign-in took too long.',
117
+ 0x00000005: 'The session was taken over by another connection.',
118
+ 0x00000006: 'The host ran out of memory.',
119
+ 0x00000007: 'The host denied the connection.',
120
+ 0x00000009: 'This account does not have permission to sign in remotely.',
121
+ 0x0000000a: 'The host requires fresh credentials.',
122
+ 0x0000000b: 'A user on the host ended the session.',
123
+ 0x0000000c: 'The remote user signed out.',
124
+ 0x00000100: 'Remote Desktop licensing failed on the host (internal error).',
125
+ 0x00000101: 'The host could not reach a Remote Desktop licensing server.',
126
+ 0x00000102: 'The host has no Remote Desktop licence available for this client.',
127
+ 0x00000103: 'The host rejected this client during licensing.',
128
+ 0x00000104: 'The stored Remote Desktop licence does not match this client.',
129
+ 0x00000105: 'The host rejected the client licence.',
130
+ 0x00000106: 'Remote Desktop licensing could not be completed.',
131
+ 0x00000107: 'The client ended the licensing exchange.',
132
+ 0x00000108: 'The host rejected the client licence encryption.',
133
+ 0x00000109: 'The host could not upgrade the client licence.',
134
+ 0x0000010a: 'The host does not accept remote connections.',
135
+ 0x000010f9: 'The host rejected a malformed capability set from this client.',
136
+ 0x000010fa: 'The host rejected the client capability exchange.',
137
+ };
138
+
139
+ export function errorInfoMessage(errorInfo) {
140
+ return (
141
+ ERROR_INFO_MESSAGES[errorInfo] ||
142
+ `The host ended the session with error info 0x${errorInfo.toString(16).padStart(8, '0')}.`
143
+ );
144
+ }
145
+
146
+ // --- Outbound PDUs ----------------------------------------------------------
147
+
148
+ /** TS_SHARECONTROLHEADER + payload. */
149
+ export function shareControlPdu(pduType, userId: number, payload: Uint8Array) {
150
+ const writer = new Writer(payload.length + 6);
151
+ writer.u16le(payload.length + 6);
152
+ writer.u16le(pduType | 0x10); // low nibble is the type, high nibble the version
153
+ writer.u16le(userId);
154
+ writer.raw(payload);
155
+ return writer.take();
156
+ }
157
+
158
+ /** TS_SHAREDATAHEADER inside a share control PDU of type DATA. */
159
+ export function shareDataPdu(shareId: number, userId: number, pduType2, payload: Uint8Array) {
160
+ const writer = new Writer(payload.length + 12);
161
+ writer.u32le(shareId);
162
+ writer.u8(0); // pad
163
+ writer.u8(1); // streamId: low priority
164
+ writer.u16le(payload.length + 18); // uncompressedLength, headers included
165
+ writer.u8(pduType2);
166
+ writer.u8(0); // compressedType: none
167
+ writer.u16le(0); // compressedLength
168
+ writer.raw(payload);
169
+ return shareControlPdu(PDU_TYPE.DATA, userId, writer.take());
170
+ }
171
+
172
+ /** Write a UTF-16LE field with its terminator, returning the length without it. */
173
+ function unicodeField(writer: Writer, text: string) {
174
+ const start = writer.length;
175
+ writer.utf16le(text);
176
+ const byteLength = writer.length - start;
177
+ writer.u16le(0); // terminator
178
+ return byteLength;
179
+ }
180
+
181
+ /**
182
+ * Client Info PDU (MS-RDPBCGR 2.2.1.11). The credential lengths sit in front of
183
+ * the strings, so the strings are written to a scratch buffer first.
184
+ */
185
+ export function clientInfoPdu({ domain, username, password, performanceFlags, autoLogon, remoteApp = false }) {
186
+ const strings = new Writer(512);
187
+ const cbDomain = unicodeField(strings, domain || '');
188
+ const cbUsername = unicodeField(strings, username || '');
189
+ const cbPassword = unicodeField(strings, password || '');
190
+ const cbAlternateShell = unicodeField(strings, '');
191
+ const cbWorkingDir = unicodeField(strings, '');
192
+
193
+ const writer = new Writer(768);
194
+ writer.u32le(0); // codePage
195
+ writer.u32le((INFO_FLAG | (autoLogon ? INFO_AUTOLOGON : 0) | (remoteApp ? INFO_RAIL : 0)) >>> 0);
196
+ writer.u16le(cbDomain);
197
+ writer.u16le(cbUsername);
198
+ writer.u16le(cbPassword);
199
+ writer.u16le(cbAlternateShell);
200
+ writer.u16le(cbWorkingDir);
201
+ writer.raw(strings.take());
202
+
203
+ // TS_EXTENDED_INFO_PACKET
204
+ writer.u16le(2); // clientAddressFamily: AF_INET
205
+ writer.u16le(2); // cbClientAddress: just the terminator
206
+ writer.u16le(0);
207
+ writer.u16le(2); // cbClientDir
208
+ writer.u16le(0);
209
+ writer.zeros(172); // clientTimeZone
210
+ writer.u32le(0); // clientSessionId
211
+ writer.u32le(performanceFlags >>> 0);
212
+ writer.u16le(0); // cbAutoReconnectCookie
213
+
214
+ return writer.take();
215
+ }
216
+
217
+ export function synchronizePdu(targetUser) {
218
+ const writer = new Writer(4);
219
+ writer.u16le(1); // SYNCMSGTYPE_SYNC
220
+ writer.u16le(targetUser);
221
+ return writer.take();
222
+ }
223
+
224
+ export function controlPdu(action) {
225
+ const writer = new Writer(8);
226
+ writer.u16le(action);
227
+ writer.u16le(0); // grantId
228
+ writer.u32le(0); // controlId
229
+ return writer.take();
230
+ }
231
+
232
+ export function fontListPdu() {
233
+ const writer = new Writer(8);
234
+ writer.u16le(0); // numberFonts
235
+ writer.u16le(0); // totalNumberFonts
236
+ writer.u16le(0x0003); // listFlags: first and last
237
+ writer.u16le(0x0032); // entrySize
238
+ return writer.take();
239
+ }
240
+
241
+ export function confirmActivePdu(shareId: number, capabilities, capabilityCount) {
242
+ const sourceDescriptor = new TextEncoder().encode('MSTSC\0');
243
+ const writer = new Writer(capabilities.length + 32);
244
+
245
+ writer.u32le(shareId);
246
+ writer.u16le(0x03ea); // originatorId: the server channel
247
+ writer.u16le(sourceDescriptor.length);
248
+ writer.u16le(capabilities.length + 4); // lengthCombinedCapabilities
249
+ writer.raw(sourceDescriptor);
250
+ writer.u16le(capabilityCount);
251
+ writer.u16le(0); // pad
252
+ writer.raw(capabilities);
253
+
254
+ return writer.take();
255
+ }
256
+
257
+ /** TS_INPUT_PDU_DATA: a batch of input events sharing one header. */
258
+ export function inputEventsPdu(events) {
259
+ const writer = new Writer(events.length * 12 + 4);
260
+ writer.u16le(events.length);
261
+ writer.u16le(0); // pad
262
+ for (const event of events) writer.raw(event);
263
+ return writer.take();
264
+ }
265
+
266
+ function inputEvent(messageType, write) {
267
+ const writer = new Writer(12);
268
+ writer.u32le(0); // eventTime, ignored by every host
269
+ writer.u16le(messageType);
270
+ write(writer);
271
+ return writer.take();
272
+ }
273
+
274
+ export function scancodeEvent(scancode, pressed: boolean, extended) {
275
+ return inputEvent(INPUT_EVENT.SCANCODE, (writer: Writer) => {
276
+ writer.u16le(
277
+ (pressed ? KEYBOARD_FLAG.DOWN : KEYBOARD_FLAG.RELEASE) |
278
+ (extended ? KEYBOARD_FLAG.EXTENDED : 0)
279
+ );
280
+ writer.u16le(scancode);
281
+ writer.u16le(0);
282
+ });
283
+ }
284
+
285
+ export function unicodeEvent(codeUnit, pressed: boolean) {
286
+ return inputEvent(INPUT_EVENT.UNICODE, (writer: Writer) => {
287
+ writer.u16le(pressed ? KEYBOARD_FLAG.DOWN : KEYBOARD_FLAG.RELEASE);
288
+ writer.u16le(codeUnit);
289
+ writer.u16le(0);
290
+ });
291
+ }
292
+
293
+ export function mouseEvent(flags: number, x: number, y: number) {
294
+ return inputEvent(INPUT_EVENT.MOUSE, (writer: Writer) => {
295
+ writer.u16le(flags);
296
+ writer.u16le(Math.max(0, Math.min(0xffff, Math.round(x))));
297
+ writer.u16le(Math.max(0, Math.min(0xffff, Math.round(y))));
298
+ });
299
+ }
300
+
301
+ export function extendedMouseEvent(flags: number, x: number, y: number) {
302
+ return inputEvent(INPUT_EVENT.MOUSEX, (writer: Writer) => {
303
+ writer.u16le(flags);
304
+ writer.u16le(Math.max(0, Math.min(0xffff, Math.round(x))));
305
+ writer.u16le(Math.max(0, Math.min(0xffff, Math.round(y))));
306
+ });
307
+ }
308
+
309
+ export function syncEvent(toggleFlags: number) {
310
+ return inputEvent(INPUT_EVENT.SYNC, (writer: Writer) => {
311
+ writer.u16le(0);
312
+ writer.u32le(toggleFlags >>> 0);
313
+ });
314
+ }
315
+
316
+ export function refreshRectPdu(rects) {
317
+ const writer = new Writer(4 + rects.length * 8);
318
+ writer.u8(rects.length);
319
+ writer.zeros(3); // pad
320
+ for (const rect of rects) {
321
+ writer.u16le(rect.left);
322
+ writer.u16le(rect.top);
323
+ writer.u16le(rect.right);
324
+ writer.u16le(rect.bottom);
325
+ }
326
+ return writer.take();
327
+ }
328
+
329
+ export function suppressOutputPdu(allowDisplayUpdates, rect) {
330
+ const writer = new Writer(12);
331
+ writer.u8(allowDisplayUpdates ? 1 : 0);
332
+ writer.zeros(3); // pad
333
+ if (allowDisplayUpdates && rect) {
334
+ writer.u16le(rect.left);
335
+ writer.u16le(rect.top);
336
+ writer.u16le(rect.right);
337
+ writer.u16le(rect.bottom);
338
+ }
339
+ return writer.take();
340
+ }
341
+
342
+ // --- Inbound PDUs -----------------------------------------------------------
343
+
344
+ export function parseShareControlHeader(bytes: Uint8Array) {
345
+ const reader = new Reader(bytes);
346
+ const totalLength = reader.u16le();
347
+ const typeAndVersion = reader.u16le();
348
+ const pduSource = reader.u16le();
349
+
350
+ return {
351
+ totalLength,
352
+ pduType: typeAndVersion & 0x0f,
353
+ pduSource,
354
+ payload: reader.rest().subarray(0, Math.max(0, totalLength - 6)),
355
+ };
356
+ }
357
+
358
+ export function parseShareDataHeader(bytes: Uint8Array) {
359
+ const reader = new Reader(bytes);
360
+ const shareId = reader.u32le();
361
+ reader.u8(); // pad
362
+ reader.u8(); // streamId
363
+ reader.u16le(); // uncompressedLength
364
+ const pduType2 = reader.u8();
365
+ const compressedType = reader.u8();
366
+ const compressedLength = reader.u16le();
367
+
368
+ return { shareId, pduType2, compressedType, compressedLength, payload: reader.rest() };
369
+ }
370
+
371
+ export function parseDemandActive(bytes: Uint8Array) {
372
+ const reader = new Reader(bytes);
373
+ const shareId = reader.u32le();
374
+ const lengthSourceDescriptor = reader.u16le();
375
+ const lengthCombinedCapabilities = reader.u16le();
376
+ reader.skip(lengthSourceDescriptor);
377
+
378
+ const capabilities = reader.raw(Math.min(lengthCombinedCapabilities, reader.remaining));
379
+ return { shareId, capabilities };
380
+ }
381
+
382
+ const BITMAP_COMPRESSION = 0x0001;
383
+ const NO_BITMAP_COMPRESSION_HDR = 0x0400;
384
+
385
+ export function parseBitmapUpdate(reader: Reader) {
386
+ const count = reader.u16le();
387
+ const rects = [];
388
+
389
+ for (let i = 0; i < count && reader.remaining >= 18; i++) {
390
+ const left = reader.u16le();
391
+ const top = reader.u16le();
392
+ const right = reader.u16le();
393
+ const bottom = reader.u16le();
394
+ const width = reader.u16le();
395
+ const height = reader.u16le();
396
+ const bitsPerPixel = reader.u16le();
397
+ const flags = reader.u16le();
398
+ let bitmapLength = reader.u16le();
399
+
400
+ const compressed = Boolean(flags & BITMAP_COMPRESSION);
401
+ if (compressed && !(flags & NO_BITMAP_COMPRESSION_HDR)) {
402
+ // TS_CD_HEADER: the main body size is authoritative, and the eight header
403
+ // bytes are counted in bitmapLength.
404
+ reader.u16le(); // cbCompFirstRowSize, always zero
405
+ const mainBodySize = reader.u16le();
406
+ reader.u16le(); // cbScanWidth
407
+ reader.u16le(); // cbUncompressedSize
408
+ bitmapLength = mainBodySize;
409
+ }
410
+
411
+ rects.push({
412
+ left,
413
+ top,
414
+ right,
415
+ bottom,
416
+ width,
417
+ height,
418
+ bitsPerPixel,
419
+ compressed,
420
+ data: reader.raw(Math.min(bitmapLength, reader.remaining)),
421
+ });
422
+ }
423
+
424
+ return rects;
425
+ }
426
+
427
+ export function parsePaletteUpdate(reader: Reader) {
428
+ reader.u16le(); // updateType, repeated
429
+ reader.u16le(); // pad
430
+ const count = reader.u32le();
431
+ const palette = new Uint8Array(256 * 3);
432
+
433
+ for (let i = 0; i < count && i < 256 && reader.remaining >= 3; i++) {
434
+ palette[i * 3] = reader.u8();
435
+ palette[i * 3 + 1] = reader.u8();
436
+ palette[i * 3 + 2] = reader.u8();
437
+ }
438
+ return palette;
439
+ }
440
+
441
+ function parseColorPointer(reader: Reader, xorBpp) {
442
+ const cacheIndex = reader.u16le();
443
+ const hotSpotX = reader.u16le();
444
+ const hotSpotY = reader.u16le();
445
+ const width = reader.u16le();
446
+ const height = reader.u16le();
447
+ const andMaskLength = reader.u16le();
448
+ const xorMaskLength = reader.u16le();
449
+ const xorMask = reader.raw(Math.min(xorMaskLength, reader.remaining)).slice();
450
+ const andMask = reader.raw(Math.min(andMaskLength, reader.remaining)).slice();
451
+
452
+ return {
453
+ kind: 'bitmap',
454
+ cacheIndex,
455
+ hotSpotX,
456
+ hotSpotY,
457
+ width,
458
+ height,
459
+ xorBpp: xorBpp || 24,
460
+ xorMask,
461
+ andMask,
462
+ };
463
+ }
464
+
465
+ export function parsePointerUpdate(reader: Reader) {
466
+ const messageType = reader.u16le();
467
+ reader.u16le(); // pad
468
+
469
+ if (messageType === POINTER_MESSAGE.POSITION) {
470
+ return { kind: 'position', x: reader.u16le(), y: reader.u16le() };
471
+ }
472
+
473
+ if (messageType === POINTER_MESSAGE.SYSTEM) {
474
+ const systemPointerType = reader.u32le();
475
+ return { kind: 'system', hidden: systemPointerType === 0x00000000 };
476
+ }
477
+
478
+ if (messageType === POINTER_MESSAGE.CACHED) {
479
+ return { kind: 'cached', cacheIndex: reader.u16le() };
480
+ }
481
+
482
+ if (messageType === POINTER_MESSAGE.COLOR) {
483
+ return parseColorPointer(reader, 24);
484
+ }
485
+
486
+ if (messageType === POINTER_MESSAGE.POINTER) {
487
+ const xorBpp = reader.u16le();
488
+ return parseColorPointer(reader, xorBpp);
489
+ }
490
+
491
+ if (messageType === POINTER_MESSAGE.LARGE) {
492
+ // Large pointers are only sent when advertised; treat as unsupported.
493
+ return { kind: 'unsupported', messageType };
494
+ }
495
+
496
+ return { kind: 'unsupported', messageType };
497
+ }