bun-memory 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,1301 @@
1
+ /**
2
+ * Windows process memory utilities for Bun using `bun:ffi` and Win32 APIs.
3
+ *
4
+ * This module exposes a {@link Memory} class that can attach to a running process by name
5
+ * and perform high-performance reads and writes directly against the target process'
6
+ * virtual address space. It wraps selected Kernel32 functions via FFI and provides
7
+ * strongly-typed helpers for common primitives and patterns.
8
+ *
9
+ * @remarks
10
+ * - Requires Windows (uses `kernel32.dll`).
11
+ * - Runs under Bun (uses `bun:ffi`).
12
+ * - Use with appropriate privileges; many operations require administrator rights.
13
+ */
14
+
15
+ import { dlopen, FFIType } from 'bun:ffi';
16
+
17
+ import Win32Error from './Win32Error';
18
+
19
+ /**
20
+ * Minimal Kernel32 FFI surface used by this module.
21
+ */
22
+
23
+ const { symbols: Kernel32 } = dlopen('kernel32.dll', {
24
+ CloseHandle: { args: [FFIType.u64], returns: FFIType.bool },
25
+ CreateToolhelp32Snapshot: { args: [FFIType.u32, FFIType.u32], returns: FFIType.u64 },
26
+ GetLastError: { returns: FFIType.u32 },
27
+ Module32FirstW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
28
+ Module32NextW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
29
+ OpenProcess: { args: [FFIType.u32, FFIType.bool, FFIType.u32], returns: FFIType.u64 },
30
+ Process32FirstW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
31
+ Process32NextW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
32
+ ReadProcessMemory: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64, FFIType.ptr], returns: FFIType.bool },
33
+ VirtualProtectEx: { args: [FFIType.u64, FFIType.u64, FFIType.u64, FFIType.u32, FFIType.ptr], returns: FFIType.bool },
34
+ VirtualQueryEx: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64], returns: FFIType.u64 },
35
+ WriteProcessMemory: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64, FFIType.ptr], returns: FFIType.bool },
36
+ });
37
+
38
+ /**
39
+ * Loaded module metadata captured via Toolhelp APIs.
40
+ */
41
+
42
+ type Module = {
43
+ modBaseAddr: bigint;
44
+ modBaseSize: number;
45
+ szModule: string;
46
+ };
47
+
48
+ /**
49
+ * Three-dimensional vector laid out as three 32‑bit floats (x, y, z).
50
+ */
51
+
52
+ type Vector3 = {
53
+ x: number;
54
+ y: number;
55
+ z: number;
56
+ };
57
+
58
+ /**
59
+ * Memory region information derived from `MEMORY_BASIC_INFORMATION`.
60
+ */
61
+
62
+ type Region = {
63
+ base: bigint;
64
+ protect: number;
65
+ size: bigint;
66
+ state: number;
67
+ type: number;
68
+ };
69
+
70
+ /**
71
+ * Attaches to a Windows process and provides safe, typed memory accessors.
72
+ *
73
+ * @example
74
+ * ```ts
75
+ * const memory = new Memory("strounter-cike.exe");
76
+ *
77
+ * const clientDLL = memory.modules['client.dll'];
78
+ *
79
+ * if(clientDLL === undefined) {
80
+ * // ...
81
+ * }
82
+ *
83
+ * // Write to your anmo...
84
+ * const ammoOffset = 0xabcdefn;
85
+ * memory.writeUInt32(clientDLL.modBaseAddr + ammoOffset, 9_999);
86
+ *
87
+ * // Read your health...
88
+ * const healthOffset = 0x123456n;
89
+ * const health = memory.readUInt32LE(clientDLL.modBaseAddr + healthOffset);
90
+ * console.log('You have %d health...', health);
91
+ *
92
+ * // Find an offset by pattern...
93
+ * const otherOffset = memory.findPattern('aa??bbccdd??ff', clientDLL.modBaseAddr, clientDLL.modBaseSize);
94
+ * const otherValue = memory.readBoolean(otherOffset + 0x1234n);
95
+ *
96
+ * memory.close();
97
+ * ```
98
+ */
99
+
100
+ class Memory {
101
+ /**
102
+ * Create a new {@link Memory} instance by process image name.
103
+ *
104
+ * @param name Fully-qualified process image name (e.g. `"notepad.exe"`).
105
+ * @throws {Win32Error} If enumerating processes or opening the process fails.
106
+ * @throws {Error} If the process cannot be found.
107
+ */
108
+ constructor(name: string) {
109
+ const dwFlags = 0x00000002; /* TH32CS_SNAPPROCESS */
110
+ const th32ProcessID = 0;
111
+
112
+ const hSnapshot = Kernel32.CreateToolhelp32Snapshot(dwFlags, th32ProcessID);
113
+
114
+ if (hSnapshot === -1n) {
115
+ throw new Win32Error('CreateToolhelp32Snapshot', Kernel32.GetLastError());
116
+ }
117
+
118
+ const lppe = Buffer.allocUnsafe(0x238 /* sizeof(PROCESSENTRY32) */);
119
+ /* */ lppe.writeUInt32LE(0x238 /* sizeof(PROCESSENTRY32) */);
120
+
121
+ const bProcess32FirstW = Kernel32.Process32FirstW(hSnapshot, lppe);
122
+
123
+ if (!bProcess32FirstW) {
124
+ Kernel32.CloseHandle(hSnapshot);
125
+
126
+ throw new Win32Error('Process32FirstW', Kernel32.GetLastError());
127
+ }
128
+
129
+ do {
130
+ const szExeFile = lppe.toString('utf16le', 0x2c, 0x234).replace(/\0+$/, '');
131
+
132
+ if (name === szExeFile) {
133
+ const desiredAccess = 0x001f0fff; /* PROCESS_ALL_ACCESS */
134
+ const inheritHandle = false;
135
+ const th32ProcessID = lppe.readUInt32LE(0x08);
136
+
137
+ const hProcess = Kernel32.OpenProcess(desiredAccess, inheritHandle, th32ProcessID);
138
+
139
+ if (hProcess === 0n) {
140
+ Kernel32.CloseHandle(hSnapshot);
141
+
142
+ throw new Win32Error('OpenProcess', Kernel32.GetLastError());
143
+ }
144
+
145
+ this.szModule = szExeFile;
146
+ this.hProcess = hProcess;
147
+ this.th32ProcessID = th32ProcessID;
148
+
149
+ this.refresh();
150
+
151
+ Kernel32.CloseHandle(hSnapshot);
152
+
153
+ return;
154
+ }
155
+ } while (Kernel32.Process32NextW(hSnapshot, lppe));
156
+
157
+ Kernel32.CloseHandle(hSnapshot);
158
+
159
+ throw new Error(`Process not found: ${name}…`);
160
+ }
161
+
162
+ // Properties…
163
+
164
+ private static readonly MemoryProtections = {
165
+ Safe: 0x10 /* PAGE_EXECUTE */ | 0x20 /* PAGE_EXECUTE_READ */ | 0x40 /* PAGE_EXECUTE_READWRITE */ | 0x80 /* PAGE_EXECUTE_WRITECOPY */ | 0x02 /* PAGE_READONLY */ | 0x04 /* PAGE_READWRITE */ | 0x08 /* PAGE_WRITECOPY */,
166
+ Unsafe: 0x100 /* PAGE_GUARD */ | 0x01 /* PAGE_NOACCESS */,
167
+ };
168
+
169
+ private static readonly PatternMatchAll = /([0-9A-Fa-f]{2})+/g;
170
+ private static readonly PatternTest = /^(?:[0-9A-Fa-f]{2}|\?{2})+$/;
171
+
172
+ private static readonly Scratch1 = Buffer.allocUnsafe(0x01);
173
+ private static readonly Scratch2 = Buffer.allocUnsafe(0x02);
174
+ private static readonly Scratch4 = Buffer.allocUnsafe(0x04);
175
+ private static readonly Scratch4_2 = Buffer.allocUnsafe(0x04);
176
+ private static readonly Scratch8 = Buffer.allocUnsafe(0x08);
177
+ private static readonly Scratch12 = Buffer.allocUnsafe(0x0c);
178
+
179
+ private readonly ScratchMemoryBasicInformation = Buffer.allocUnsafe(0x30 /* sizeof(MEMORY_BASIC_INFORMATION) */);
180
+ private readonly ScratchModuleEntry32W = Buffer.allocUnsafe(0x438 /* sizeof(MODULEENTRY32W) */);
181
+
182
+ private _modules!: { [key: string]: Module };
183
+
184
+ /**
185
+ * Native process handle returned by `OpenProcess`.
186
+ */
187
+
188
+ public readonly hProcess: bigint;
189
+ public readonly szModule: string;
190
+
191
+ /**
192
+ * Target process identifier (PID).
193
+ */
194
+
195
+ public readonly th32ProcessID: number;
196
+
197
+ public get modBaseAddr(): Memory['_modules'][string]['modBaseAddr'] {
198
+ return this.modules[this.szModule].modBaseAddr;
199
+ }
200
+
201
+ public get modBaseSize(): Memory['_modules'][string]['modBaseSize'] {
202
+ return this.modules[this.szModule].modBaseSize;
203
+ }
204
+
205
+ /**
206
+ * Snapshot of modules loaded in the target process.
207
+ *
208
+ * @remarks Call {@link refresh} to update this list.
209
+ */
210
+
211
+ public get modules(): Memory['_modules'] {
212
+ return this._modules;
213
+ }
214
+
215
+ // Methods…
216
+
217
+ /**
218
+ * Close the underlying process handle.
219
+ */
220
+
221
+ public close(): void {
222
+ Kernel32.CloseHandle(this.hProcess);
223
+
224
+ return;
225
+ }
226
+
227
+ /**
228
+ * Enumerate committed, readable/executable memory regions within the given range.
229
+ *
230
+ * @param address Start address for the query.
231
+ * @param length Number of bytes to cover from `address`.
232
+ * @returns Array of safe regions intersecting the requested range.
233
+ * @private
234
+ */
235
+
236
+ private regions(address: bigint | number, length: bigint | number): Region[] {
237
+ const dwLength = 0x30; /* sizeof(MEMORY_BASIC_INFORMATION) */
238
+ let lpAddress = BigInt(address); // prettier-ignore
239
+ const lpBuffer = this.ScratchMemoryBasicInformation;
240
+
241
+ const bVirtualQueryEx = !!Kernel32.VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength);
242
+
243
+ if (!bVirtualQueryEx) {
244
+ throw new Win32Error('VirtualQueryEx', Kernel32.GetLastError());
245
+ }
246
+
247
+ const end = lpAddress + BigInt(length);
248
+ const result: Region[] = [];
249
+
250
+ do {
251
+ const baseAddress = lpBuffer.readBigUInt64LE();
252
+ const protect = lpBuffer.readUInt32LE(36);
253
+ const regionSize = lpBuffer.readBigUInt64LE(24);
254
+ const state = lpBuffer.readUInt32LE(32);
255
+ const type = lpBuffer.readUInt32LE(40);
256
+
257
+ if ((protect & Memory.MemoryProtections.Safe) !== 0 && (protect & Memory.MemoryProtections.Unsafe) === 0 && state === 0x1000 /* MEM_COMMIT */) {
258
+ result.push({ base: baseAddress, protect, size: regionSize, state, type });
259
+ }
260
+
261
+ lpAddress = baseAddress + regionSize;
262
+ } while (lpAddress < end && !!Kernel32.VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength));
263
+
264
+ return result;
265
+ }
266
+
267
+ /**
268
+ * Refresh the list of loaded modules for the target process.
269
+ *
270
+ * @throws {Win32Error} If Toolhelp snapshots cannot be created or iterated.
271
+ */
272
+
273
+ public refresh(): void {
274
+ const dwFlags = 0x00000008 /* TH32CS_SNAPMODULE */ | 0x00000010; /* TH32CS_SNAPMODULE32 */
275
+
276
+ const hSnapshot = Kernel32.CreateToolhelp32Snapshot(dwFlags, this.th32ProcessID)!;
277
+
278
+ if (hSnapshot === -1n) {
279
+ throw new Win32Error('CreateToolhelp32Snapshot', Kernel32.GetLastError());
280
+ }
281
+
282
+ const lpme = this.ScratchModuleEntry32W;
283
+ /* */ lpme.writeUInt32LE(0x438 /* sizeof(MODULEENTRY32W) */);
284
+
285
+ const bModule32FirstW = Kernel32.Module32FirstW(hSnapshot, lpme);
286
+
287
+ if (!bModule32FirstW) {
288
+ Kernel32.CloseHandle(hSnapshot);
289
+
290
+ throw new Win32Error('Module32FirstW', Kernel32.GetLastError());
291
+ }
292
+
293
+ const modules: Memory['_modules'] = {};
294
+
295
+ do {
296
+ const modBaseAddr = lpme.readBigUInt64LE(0x18);
297
+ const modBaseSize = lpme.readUInt32LE(0x20);
298
+ const szModule = lpme.toString('utf16le', 0x30, 0x230).replace(/\0+$/, '');
299
+
300
+ modules[szModule] = { modBaseAddr, modBaseSize, szModule };
301
+ } while (Kernel32.Module32NextW(hSnapshot, lpme));
302
+
303
+ Kernel32.CloseHandle(hSnapshot);
304
+
305
+ this._modules = Object.freeze(modules);
306
+
307
+ return;
308
+ }
309
+
310
+ // Private Methods…
311
+
312
+ // QoL methods…
313
+
314
+ /**
315
+ * Scan memory for a hex signature with wildcard support.
316
+ *
317
+ * @param needle Hex pattern using pairs of hex digits; use `??` as a byte wildcard.
318
+ * Whitespace is not permitted (e.g. `"48895C24??48896C24??"`).
319
+ * @param address Start address to begin scanning.
320
+ * @param length Number of bytes to scan from `address`.
321
+ * @returns Address of the first match, or `-1n` if not found.
322
+ */
323
+
324
+ public findPattern(needle: string, address: bigint | number, length: bigint | number): bigint {
325
+ const { PatternMatchAll, PatternTest } = Memory;
326
+
327
+ address = BigInt(address);
328
+ length = BigInt(length);
329
+
330
+ const test = PatternTest.test(needle);
331
+
332
+ if (!test) {
333
+ return -1n;
334
+ }
335
+
336
+ const actualEnd = address + length;
337
+ const actualStart = address;
338
+
339
+ const needleLength = needle.length >>> 1;
340
+
341
+ const [anchor, ...tokens] = [...needle.matchAll(PatternMatchAll)] //
342
+ .map((match) => ({ buffer: Buffer.from(match[0], 'hex'), index: match.index >>> 1, length: match[0].length >>> 1 }))
343
+ .sort(({ buffer: { length: a } }, { buffer: { length: b } }) => b - a);
344
+
345
+ const regions = this.regions(address, length);
346
+
347
+ for (const region of regions) {
348
+ const regionEnd = region.base + region.size;
349
+ const regionStart = region.base;
350
+
351
+ const scanEnd = regionEnd < actualEnd ? regionEnd : actualEnd;
352
+ const scanStart = regionStart > actualStart ? regionStart : actualStart;
353
+
354
+ const scanLength = scanEnd - scanStart;
355
+
356
+ if (needleLength > scanLength) {
357
+ continue;
358
+ }
359
+
360
+ const haystack = this.readBuffer(scanStart, scanLength);
361
+
362
+ let indexOf = haystack.indexOf(anchor.buffer, anchor.index);
363
+
364
+ if (indexOf === -1) {
365
+ continue;
366
+ }
367
+
368
+ const lastStart = scanLength - BigInt(needleLength);
369
+
370
+ outer: do {
371
+ const matchStart = indexOf - anchor.index;
372
+
373
+ if (lastStart < matchStart) {
374
+ break;
375
+ }
376
+
377
+ for (const token of tokens) {
378
+ const sourceEnd = matchStart + token.index + token.length;
379
+ const sourceStart = matchStart + token.index;
380
+
381
+ const targetEnd = token.length;
382
+ const targetStart = 0;
383
+
384
+ const compare = haystack.compare(token.buffer, targetStart, targetEnd, sourceStart, sourceEnd);
385
+
386
+ if (compare !== 0) {
387
+ indexOf = haystack.indexOf(anchor.buffer, indexOf + 1);
388
+ continue outer;
389
+ }
390
+ }
391
+
392
+ return scanStart + BigInt(matchStart);
393
+ } while (indexOf !== -1);
394
+ }
395
+
396
+ return -1n;
397
+ }
398
+
399
+ /**
400
+ * Search memory for a sequence of bytes or a string.
401
+ *
402
+ * @param needle A `Uint8Array`, number, or string to locate.
403
+ * @param address Start address.
404
+ * @param length Number of bytes to search.
405
+ * @param encoding Optional encoding when `needle` is a string.
406
+ * @returns Address of the first match, or `-1n` if not found.
407
+ */
408
+
409
+ public indexOf(needle: Uint8Array | number | string, address: bigint | number, length: bigint | number, encoding?: BufferEncoding): bigint {
410
+ address = BigInt(address);
411
+ length = BigInt(length);
412
+
413
+ const regions = this.regions(address, length);
414
+
415
+ for (const { base, size } of regions) {
416
+ const address_ = address > base ? address : base;
417
+
418
+ const haystack = this.readBuffer(address_, base + size - address_);
419
+ const indexOf = haystack.indexOf(needle, 0, encoding);
420
+
421
+ if (indexOf === -1) {
422
+ continue;
423
+ }
424
+
425
+ return address_ + BigInt(indexOf);
426
+ }
427
+
428
+ return -1n;
429
+ }
430
+
431
+ // Read/write methods…
432
+
433
+ /**
434
+ * Read a contiguous block as a `BigInt64Array`.
435
+ *
436
+ * @param address Source address.
437
+ * @param length Element count (not bytes).
438
+ * @param scratch Optional destination buffer to avoid allocations.
439
+ * @returns View over the backing buffer as `BigInt64Array`.
440
+ */
441
+
442
+ public readBigInt64Array(address: bigint | number, length: number, scratch?: Buffer): BigInt64Array {
443
+ const buffer = this.readBuffer(address, length * 8, scratch);
444
+
445
+ const bigUInt64Array = new BigInt64Array(buffer.buffer, buffer.byteOffset, length);
446
+
447
+ return bigUInt64Array;
448
+ }
449
+
450
+ /**
451
+ * Read a signed 64‑bit big-endian integer.
452
+ * @param address Source address.
453
+ */
454
+
455
+ public readBigInt64BE(address: bigint | number): bigint {
456
+ return this.readBuffer(address, 8).readBigInt64BE();
457
+ }
458
+
459
+ /**
460
+ * Read a signed 64‑bit little-endian integer.
461
+ * @param address Source address.
462
+ */
463
+
464
+ public readBigInt64LE(address: bigint | number): bigint {
465
+ return this.readBuffer(address, 8).readBigInt64LE();
466
+ }
467
+
468
+ /**
469
+ * Read a contiguous block as a `BigUint64Array`.
470
+ *
471
+ * @param address Source address.
472
+ * @param length Element count (not bytes).
473
+ * @param scratch Optional destination buffer.
474
+ * @returns View over the backing buffer as `BigUint64Array`.
475
+ */
476
+
477
+ public readBigUint64Array(address: bigint | number, length: number, scratch?: Buffer): BigUint64Array {
478
+ const buffer = this.readBuffer(address, length * 8, scratch);
479
+
480
+ const bigUInt64Array = new BigUint64Array(buffer.buffer, buffer.byteOffset, length);
481
+
482
+ return bigUInt64Array;
483
+ }
484
+
485
+ /**
486
+ * Read an unsigned 64‑bit big-endian integer.
487
+ * @param address Source address.
488
+ */
489
+
490
+ public readBigUInt64BE(address: bigint | number): bigint {
491
+ return this.readBuffer(address, 0x08, Memory.Scratch8).readBigUInt64BE();
492
+ }
493
+
494
+ /**
495
+ * Read an unsigned 64‑bit little-endian integer.
496
+ * @param address Source address.
497
+ */
498
+
499
+ public readBigUInt64LE(address: bigint | number): bigint {
500
+ return this.readBuffer(address, 0x08, Memory.Scratch8).readBigUInt64LE();
501
+ }
502
+
503
+ /**
504
+ * Read a boolean value (non-zero -> `true`).
505
+ * @param address Source address.
506
+ */
507
+
508
+ public readBoolean(address: bigint | number): boolean {
509
+ return Boolean(this.readUInt8(address));
510
+ }
511
+
512
+ /**
513
+ * Read raw bytes from the target process.
514
+ *
515
+ * @param address Source address.
516
+ * @param length Number of bytes to read.
517
+ * @param scratch Optional Buffer to reuse for improved performance.
518
+ * @returns Buffer containing the bytes read.
519
+ * @throws {Win32Error} If `ReadProcessMemory` fails.
520
+ */
521
+
522
+ public readBuffer(address: bigint | number, length: bigint | number, scratch?: Buffer): Buffer {
523
+ const { hProcess } = this;
524
+
525
+ address = BigInt(address);
526
+ length = BigInt(length);
527
+
528
+ const lpBaseAddress = address;
529
+ const lpBuffer = scratch ?? Buffer.allocUnsafe(Number(length));
530
+ const nSize = length;
531
+ const numberOfBytesRead = 0x00n;
532
+
533
+ const bReadProcessMemory = Kernel32.ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesRead);
534
+
535
+ if (!bReadProcessMemory) {
536
+ throw new Win32Error('ReadProcessMemory', Kernel32.GetLastError());
537
+ }
538
+
539
+ return lpBuffer;
540
+ }
541
+
542
+ /**
543
+ * Read a 64‑bit big-endian IEEE-754 float.
544
+ * @param address Source address.
545
+ */
546
+
547
+ public readDoubleBE(address: bigint | number): number {
548
+ return this.readBuffer(address, 0x08, Memory.Scratch8).readDoubleBE();
549
+ }
550
+
551
+ /**
552
+ * Read a 64‑bit little-endian IEEE-754 float.
553
+ * @param address Source address.
554
+ */
555
+
556
+ public readDoubleLE(address: bigint | number): number {
557
+ return this.readBuffer(address, 0x08, Memory.Scratch8).readDoubleLE();
558
+ }
559
+
560
+ // + TODO: Implement scratch…
561
+
562
+ /**
563
+ * Read a contiguous block as a `Float32Array`.
564
+ *
565
+ * @param address Source address.
566
+ * @param length Element count (not bytes).
567
+ * @param scratch Optional Buffer to reuse for improved performance.
568
+ * @returns View over the backing buffer as `Float32Array`.
569
+ */
570
+
571
+ public readFloat32Array(address: bigint | number, length: number, scratch?: Buffer): Float32Array {
572
+ const buffer = this.readBuffer(address, length * 0x04, scratch);
573
+
574
+ const float32Array = new Float32Array(buffer.buffer, buffer.byteOffset, length);
575
+
576
+ return float32Array;
577
+ }
578
+
579
+ /**
580
+ * Read a 32‑bit big-endian IEEE-754 float.
581
+ * @param address Source address.
582
+ */
583
+
584
+ public readFloatBE(address: bigint | number): number {
585
+ return this.readBuffer(address, 0x04, Memory.Scratch4).readFloatBE();
586
+ }
587
+
588
+ /**
589
+ * Read a 32‑bit little-endian IEEE-754 float.
590
+ * @param address Source address.
591
+ */
592
+
593
+ public readFloatLE(address: bigint | number): number {
594
+ return this.readBuffer(address, 0x04, Memory.Scratch4).readFloatLE();
595
+ }
596
+
597
+ /**
598
+ * Read a 16‑bit big-endian signed integer.
599
+ * @param address Source address.
600
+ */
601
+
602
+ public readInt16BE(address: bigint | number): number {
603
+ return this.readBuffer(address, 0x02, Memory.Scratch2).readInt16BE();
604
+ }
605
+
606
+ /**
607
+ * Read a 16‑bit little-endian signed integer.
608
+ * @param address Source address.
609
+ */
610
+
611
+ public readInt16LE(address: bigint | number): number {
612
+ return this.readBuffer(address, 0x02, Memory.Scratch2).readInt16LE();
613
+ }
614
+
615
+ /**
616
+ * Read a 32‑bit big-endian signed integer.
617
+ * @param address Source address.
618
+ */
619
+
620
+ public readInt32BE(address: bigint | number): number {
621
+ return this.readBuffer(address, 0x04, Memory.Scratch4).readInt32BE();
622
+ }
623
+
624
+ /**
625
+ * Read a 32‑bit little-endian signed integer.
626
+ * @param address Source address.
627
+ */
628
+
629
+ public readInt32LE(address: bigint | number): number {
630
+ return this.readBuffer(address, 0x04, Memory.Scratch4).readInt32LE();
631
+ }
632
+
633
+ /**
634
+ * Read an 8‑bit signed integer.
635
+ * @param address Source address.
636
+ */
637
+
638
+ public readInt8(address: bigint | number): number {
639
+ return this.readBuffer(address, 0x01, Memory.Scratch1).readInt8();
640
+ }
641
+
642
+ // ? I don't have the brain-power for this right now… 🫠…
643
+
644
+ /**
645
+ * Read a big-endian signed integer of arbitrary byte length.
646
+ * @param address Source address.
647
+ * @param byteLength Number of bytes (1–6).
648
+ * @param scratch Optional Buffer to reuse for improved performance.
649
+ */
650
+
651
+ public readIntBE(address: bigint | number, byteLength: number, scratch: Buffer): number {
652
+ return this.readBuffer(address, byteLength, scratch).readIntBE(0, byteLength);
653
+ }
654
+
655
+ /**
656
+ * Read a little-endian signed integer of arbitrary byte length.
657
+ * @param address Source address.
658
+ * @param byteLength Number of bytes (1–6).
659
+ * @param scratch Optional Buffer to reuse for improved performance.
660
+ */
661
+
662
+ public readIntLE(address: bigint | number, byteLength: number, scratch?: Buffer): number {
663
+ return this.readBuffer(address, byteLength, scratch).readIntLE(0, byteLength);
664
+ }
665
+
666
+ // ? …
667
+
668
+ /**
669
+ * Read bytes directly into the provided scratch buffer.
670
+ * @param address Source address.
671
+ * @param scratch Destination buffer; its length determines the read size.
672
+ * @returns The same scratch buffer for chaining.
673
+ */
674
+
675
+ public readInto(address: bigint | number, scratch: Buffer): Buffer {
676
+ this.readBuffer(address, scratch.byteLength, scratch);
677
+
678
+ return scratch;
679
+ }
680
+
681
+ // + TODO: Implement scratch…
682
+
683
+ /**
684
+ * Read a `CUtlVector`-like structure of 32‑bit elements used in some networked engines.
685
+ *
686
+ * @param address Address of the vector base structure:
687
+ * - `+0x00` = size (uint32)
688
+ * - `+0x08` = pointer to elements
689
+ * @param scratch Optional Buffer to reuse for improved performance.
690
+ * @returns A `Uint32Array` of elements, or empty if size is 0 or pointer is null.
691
+ */
692
+
693
+ public readNetworkUtlVectorBase(address: bigint | number, scratch?: Buffer): Uint32Array {
694
+ address = BigInt(address);
695
+
696
+ const size = this.readUInt32LE(address);
697
+
698
+ if (size === 0) {
699
+ return new Uint32Array(0);
700
+ }
701
+
702
+ const elementsPtr = this.readBigUInt64LE(address + 0x08n);
703
+
704
+ if (elementsPtr === 0n) {
705
+ return new Uint32Array(0);
706
+ }
707
+
708
+ const elementsBuffer = this.readBuffer(elementsPtr, size * 0x04, scratch);
709
+
710
+ return new Uint32Array(elementsBuffer.buffer, elementsBuffer.byteOffset, size);
711
+ }
712
+
713
+ /**
714
+ * Read a UTF‑8 string up to `length` bytes or until the first NUL terminator.
715
+ * @param address Source address.
716
+ * @param length Maximum number of bytes to read.
717
+ * @param scratch Optional Buffer to reuse for improved performance.
718
+ */
719
+
720
+ public readString(address: bigint | number, length: number, scratch?: Buffer): string {
721
+ const buffer = this.readBuffer(address, length, scratch);
722
+
723
+ const indexOf = buffer.indexOf(0);
724
+
725
+ const end = indexOf !== -1 ? indexOf : buffer.length;
726
+ const start = 0;
727
+
728
+ return buffer.toString('utf8', start, end);
729
+ }
730
+
731
+ /**
732
+ * Read a 16‑bit big-endian unsigned integer.
733
+ * @param address Source address.
734
+ */
735
+
736
+ public readUInt16BE(address: bigint | number): number {
737
+ return this.readBuffer(address, 0x02, Memory.Scratch2).readUInt16BE();
738
+ }
739
+
740
+ /**
741
+ * Read a 16‑bit little-endian unsigned integer.
742
+ * @param address Source address.
743
+ */
744
+
745
+ public readUInt16LE(address: bigint | number): number {
746
+ return this.readBuffer(address, 0x02, Memory.Scratch2).readUInt16LE();
747
+ }
748
+
749
+ /**
750
+ * Read a 32‑bit big-endian unsigned integer.
751
+ * @param address Source address.
752
+ */
753
+
754
+ public readUInt32BE(address: bigint | number): number {
755
+ return this.readBuffer(address, 0x04, Memory.Scratch4).readUInt32BE();
756
+ }
757
+
758
+ /**
759
+ * Read a 32‑bit little-endian unsigned integer.
760
+ * @param address Source address.
761
+ */
762
+
763
+ public readUInt32LE(address: bigint | number): number {
764
+ return this.readBuffer(address, 0x04, Memory.Scratch4).readUInt32LE();
765
+ }
766
+
767
+ /**
768
+ * Read an 8‑bit unsigned integer.
769
+ * @param address Source address.
770
+ */
771
+
772
+ public readUInt8(address: bigint | number): number {
773
+ return this.readBuffer(address, 0x01, Memory.Scratch1).readUInt8();
774
+ }
775
+
776
+ // ? I don't have the brain-power for this right now… 🫠…
777
+
778
+ /**
779
+ * Read a big-endian unsigned integer of arbitrary byte length.
780
+ * @param address Source address.
781
+ * @param byteLength Number of bytes (1–6).
782
+ * @param scratch Optional Buffer to reuse for improved performance.
783
+ */
784
+
785
+ public readUIntBE(address: bigint | number, byteLength: number, scratch?: Buffer): number {
786
+ return this.readBuffer(address, byteLength, scratch).readUIntBE(0, byteLength);
787
+ }
788
+
789
+ /**
790
+ * Read a little-endian unsigned integer of arbitrary byte length.
791
+ * @param address Source address.
792
+ * @param byteLength Number of bytes (1–6).
793
+ * @param scratch Optional Buffer to reuse for improved performance.
794
+ */
795
+
796
+ public readUIntLE(address: bigint | number, byteLength: number, scratch?: Buffer): number {
797
+ return this.readBuffer(address, byteLength, scratch).readUIntLE(0, byteLength);
798
+ }
799
+
800
+ // ? …
801
+
802
+ /**
803
+ * Read a {@link Vector3} (three consecutive 32‑bit floats).
804
+ * @param address Source address.
805
+ * @returns A `{ x, y, z }` object.
806
+ */
807
+
808
+ public readVector3(address: bigint | number): Vector3 {
809
+ const buffer = this.readBuffer(address, 0x0c, Memory.Scratch12);
810
+
811
+ const x = buffer.readFloatLE();
812
+ const y = buffer.readFloatLE(0x04);
813
+ const z = buffer.readFloatLE(0x08);
814
+
815
+ return { x, y, z };
816
+ }
817
+
818
+ // ? …
819
+
820
+ /**
821
+ * Write a signed 64‑bit big-endian integer.
822
+ * @param address Destination address.
823
+ * @param value Value to write.
824
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
825
+ * @returns `this` for chaining.
826
+ * @throws {Win32Error} If the underlying write or protection change fails.
827
+ */
828
+
829
+ public writeBigInt64BE(address: bigint | number, value: bigint, force = false): this {
830
+ Memory.Scratch8.writeBigInt64BE(value);
831
+
832
+ this.writeBuffer(address, Memory.Scratch8, force);
833
+
834
+ return this;
835
+ }
836
+
837
+ /**
838
+ * Write a signed 64‑bit little-endian integer.
839
+ * @param address Destination address.
840
+ * @param value Value to write.
841
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
842
+ * @returns `this` for chaining.
843
+ * @throws {Win32Error} If the underlying write or protection change fails.
844
+ */
845
+
846
+ public writeBigInt64LE(address: bigint | number, value: bigint, force = false): this {
847
+ Memory.Scratch8.writeBigInt64LE(value);
848
+
849
+ this.writeBuffer(address, Memory.Scratch8, force);
850
+
851
+ return this;
852
+ }
853
+
854
+ /**
855
+ * Write an unsigned 64‑bit big-endian integer.
856
+ * @param address Destination address.
857
+ * @param value Value to write.
858
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
859
+ * @returns `this` for chaining.
860
+ * @throws {Win32Error} If the underlying write or protection change fails.
861
+ */
862
+
863
+ public writeBigUInt64BE(address: bigint | number, value: bigint, force = false): this {
864
+ Memory.Scratch8.writeBigUInt64BE(value);
865
+
866
+ this.writeBuffer(address, Memory.Scratch8, force);
867
+
868
+ return this;
869
+ }
870
+
871
+ /**
872
+ * Write an unsigned 64‑bit little-endian integer.
873
+ * @param address Destination address.
874
+ * @param value Value to write.
875
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
876
+ * @returns `this` for chaining.
877
+ * @throws {Win32Error} If the underlying write or protection change fails.
878
+ */
879
+
880
+ public writeBigUInt64LE(address: bigint | number, value: bigint, force = false): this {
881
+ Memory.Scratch8.writeBigUInt64LE(value);
882
+
883
+ this.writeBuffer(address, Memory.Scratch8, force);
884
+
885
+ return this;
886
+ }
887
+
888
+ /**
889
+ * Write a boolean as an 8‑bit value (0 or 1).
890
+ * @param address Destination address.
891
+ * @param value Value to write.
892
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
893
+ * @returns `this` for chaining.
894
+ * @throws {Win32Error} If the underlying write or protection change fails.
895
+ */
896
+
897
+ public writeBoolean(address: bigint | number, value: boolean, force = false): this {
898
+ Memory.Scratch1.writeUInt8(+value);
899
+
900
+ this.writeBuffer(address, Memory.Scratch1, force);
901
+
902
+ return this;
903
+ }
904
+
905
+ /**
906
+ * Write raw bytes to the target process.
907
+ *
908
+ * @param address Destination address.
909
+ * @param buffer Source buffer to write.
910
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` and restores
911
+ * the original protection after the write.
912
+ * @returns `this` for chaining.
913
+ * @throws {Win32Error} If the underlying write or protection change fails.
914
+ */
915
+
916
+ public writeBuffer(address: bigint | number, buffer: Buffer, force = false): this {
917
+ const { hProcess } = this;
918
+
919
+ address = BigInt(address);
920
+
921
+ const lpBaseAddress = address;
922
+ const lpBuffer = buffer;
923
+ // const lpNumberOfBytesWritten = 0n;
924
+ const nSize = BigInt(buffer.byteLength);
925
+
926
+ if (!force) {
927
+ const bWriteProcessMemory = Kernel32.WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, 0n);
928
+
929
+ if (!bWriteProcessMemory) {
930
+ throw new Win32Error('WriteProcessMemory', Kernel32.GetLastError());
931
+ }
932
+
933
+ return this;
934
+ }
935
+
936
+ const dwSize = BigInt(buffer.byteLength);
937
+ const flNewProtect = 0x40; /* PAGE_EXECUTE_READWRITE */
938
+ const lpAddress = address;
939
+ const lpflOldProtect = Memory.Scratch4;
940
+
941
+ const bVirtualProtectEx = !!Kernel32.VirtualProtectEx(hProcess, lpAddress, dwSize, flNewProtect, lpflOldProtect);
942
+
943
+ if (!bVirtualProtectEx) {
944
+ throw new Win32Error('VirtualProtectEx', Kernel32.GetLastError());
945
+ }
946
+
947
+ const bWriteProcessMemory = Kernel32.WriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, 0n);
948
+
949
+ if (!bWriteProcessMemory) {
950
+ Kernel32.VirtualProtectEx(hProcess, lpAddress, nSize, lpflOldProtect.readUInt32LE(), Memory.Scratch4_2);
951
+
952
+ throw new Win32Error('WriteProcessMemory', Kernel32.GetLastError());
953
+ }
954
+
955
+ Kernel32.VirtualProtectEx(hProcess, lpAddress, nSize, lpflOldProtect.readUInt32LE(), Memory.Scratch4_2);
956
+
957
+ return this;
958
+ }
959
+
960
+ /**
961
+ * Write a 64‑bit big-endian IEEE-754 float.
962
+ * @param address Destination address.
963
+ * @param value Value to write.
964
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
965
+ * @returns `this` for chaining.
966
+ * @throws {Win32Error} If the underlying write or protection change fails.
967
+ */
968
+
969
+ public writeDoubleBE(address: bigint | number, value: number, force = false): this {
970
+ Memory.Scratch8.writeDoubleBE(value);
971
+
972
+ this.writeBuffer(address, Memory.Scratch8, force);
973
+
974
+ return this;
975
+ }
976
+
977
+ /**
978
+ * Write a 64‑bit little-endian IEEE-754 float.
979
+ * @param address Destination address.
980
+ * @param value Value to write.
981
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
982
+ * @returns `this` for chaining.
983
+ * @throws {Win32Error} If the underlying write or protection change fails.
984
+ */
985
+
986
+ public writeDoubleLE(address: bigint | number, value: number, force = false): this {
987
+ Memory.Scratch8.writeDoubleLE(value);
988
+
989
+ this.writeBuffer(address, Memory.Scratch8, force);
990
+
991
+ return this;
992
+ }
993
+
994
+ /**
995
+ * Write a 32‑bit big-endian IEEE-754 float.
996
+ * @param address Destination address.
997
+ * @param value Value to write.
998
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
999
+ * @returns `this` for chaining.
1000
+ * @throws {Win32Error} If the underlying write or protection change fails.
1001
+ */
1002
+
1003
+ public writeFloatBE(address: bigint | number, value: number, force = false): this {
1004
+ Memory.Scratch4.writeFloatBE(value);
1005
+
1006
+ this.writeBuffer(address, Memory.Scratch4, force);
1007
+
1008
+ return this;
1009
+ }
1010
+
1011
+ /**
1012
+ * Write a 32‑bit little-endian IEEE-754 float.
1013
+ * @param address Destination address.
1014
+ * @param value Value to write.
1015
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1016
+ * @returns `this` for chaining.
1017
+ * @throws {Win32Error} If the underlying write or protection change fails.
1018
+ */
1019
+
1020
+ public writeFloatLE(address: bigint | number, value: number, force = false): this {
1021
+ Memory.Scratch4.writeFloatLE(value);
1022
+
1023
+ this.writeBuffer(address, Memory.Scratch4, force);
1024
+
1025
+ return this;
1026
+ }
1027
+
1028
+ /**
1029
+ * Write a 16‑bit big-endian signed integer.
1030
+ * @param address Destination address.
1031
+ * @param value Value to write.
1032
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1033
+ * @returns `this` for chaining.
1034
+ * @throws {Win32Error} If the underlying write or protection change fails.
1035
+ */
1036
+
1037
+ public writeInt16BE(address: bigint | number, value: number, force = false): this {
1038
+ Memory.Scratch2.writeInt16BE(value);
1039
+
1040
+ this.writeBuffer(address, Memory.Scratch2, force);
1041
+
1042
+ return this;
1043
+ }
1044
+
1045
+ /**
1046
+ * Write a 16‑bit little-endian signed integer.
1047
+ * @param address Destination address.
1048
+ * @param value Value to write.
1049
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1050
+ * @returns `this` for chaining.
1051
+ * @throws {Win32Error} If the underlying write or protection change fails.
1052
+ */
1053
+
1054
+ public writeInt16LE(address: bigint | number, value: number, force = false): this {
1055
+ Memory.Scratch2.writeInt16LE(value);
1056
+
1057
+ this.writeBuffer(address, Memory.Scratch2, force);
1058
+
1059
+ return this;
1060
+ }
1061
+
1062
+ /**
1063
+ * Write a 32‑bit big-endian signed integer.
1064
+ * @param address Destination address.
1065
+ * @param value Value to write.
1066
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1067
+ * @returns `this` for chaining.
1068
+ * @throws {Win32Error} If the underlying write or protection change fails.
1069
+ */
1070
+
1071
+ public writeInt32BE(address: bigint | number, value: number, force = false): this {
1072
+ Memory.Scratch4.writeInt32BE(value);
1073
+
1074
+ this.writeBuffer(address, Memory.Scratch4, force);
1075
+
1076
+ return this;
1077
+ }
1078
+
1079
+ /**
1080
+ * Write a 32‑bit little-endian signed integer.
1081
+ * @param address Destination address.
1082
+ * @param value Value to write.
1083
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1084
+ * @returns `this` for chaining.
1085
+ * @throws {Win32Error} If the underlying write or protection change fails.
1086
+ */
1087
+
1088
+ public writeInt32LE(address: bigint | number, value: number, force = false): this {
1089
+ Memory.Scratch4.writeInt32LE(value);
1090
+
1091
+ this.writeBuffer(address, Memory.Scratch4, force);
1092
+
1093
+ return this;
1094
+ }
1095
+
1096
+ /**
1097
+ * Write an 8‑bit signed integer.
1098
+ * @param address Destination address.
1099
+ * @param value Value to write.
1100
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1101
+ * @returns `this` for chaining.
1102
+ * @throws {Win32Error} If the underlying write or protection change fails.
1103
+ */
1104
+
1105
+ public writeInt8(address: bigint | number, value: number, force = false): this {
1106
+ Memory.Scratch1.writeInt8(value);
1107
+
1108
+ this.writeBuffer(address, Memory.Scratch1, force);
1109
+
1110
+ return this;
1111
+ }
1112
+
1113
+ // + TODO: Implement scratch…
1114
+
1115
+ /**
1116
+ * Write a big-endian signed integer of arbitrary byte length.
1117
+ * @param address Destination address.
1118
+ * @param value Value to write.
1119
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1120
+ * @returns `this` for chaining.
1121
+ * @throws {Win32Error} If the underlying write or protection change fails.
1122
+ */
1123
+
1124
+ public writeIntBE(address: bigint | number, value: number, byteLength: number, force = false): this {
1125
+ const buffer = Buffer.allocUnsafe(byteLength);
1126
+ /* */ buffer.writeIntBE(value, 0, byteLength);
1127
+
1128
+ this.writeBuffer(address, buffer, force);
1129
+
1130
+ return this;
1131
+ }
1132
+
1133
+ // + TODO: Implement scratch…
1134
+
1135
+ /**
1136
+ * Write a little-endian signed integer of arbitrary byte length.
1137
+ * @param address Destination address.
1138
+ * @param value Value to write.
1139
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1140
+ * @returns `this` for chaining.
1141
+ * @throws {Win32Error} If the underlying write or protection change fails.
1142
+ */
1143
+
1144
+ public writeIntLE(address: bigint | number, value: number, byteLength: number, force = false): this {
1145
+ const buffer = Buffer.allocUnsafe(byteLength);
1146
+ /* */ buffer.writeIntLE(value, 0, byteLength);
1147
+
1148
+ this.writeBuffer(address, buffer, force);
1149
+
1150
+ return this;
1151
+ }
1152
+
1153
+ // + TODO: Implement scratch…
1154
+
1155
+ /**
1156
+ * Write a UTF‑8 string (no terminator is appended).
1157
+ * @param address Destination address.
1158
+ * @param value Value to write.
1159
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1160
+ * @returns `this` for chaining.
1161
+ * @throws {Win32Error} If the underlying write or protection change fails.
1162
+ */
1163
+
1164
+ public writeString(address: bigint | number, value: string, force = false): this {
1165
+ const byteLength = Buffer.byteLength(value);
1166
+
1167
+ const buffer = Buffer.allocUnsafe(byteLength);
1168
+ /* */ buffer.write(value, 0, byteLength, 'utf8');
1169
+
1170
+ this.writeBuffer(address, buffer, force);
1171
+
1172
+ return this;
1173
+ }
1174
+
1175
+ /**
1176
+ * Write a 16‑bit big-endian unsigned integer.
1177
+ * @param address Destination address.
1178
+ * @param value Value to write.
1179
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1180
+ * @returns `this` for chaining.
1181
+ * @throws {Win32Error} If the underlying write or protection change fails.
1182
+ */
1183
+
1184
+ public writeUInt16BE(address: bigint | number, value: number, force = false): this {
1185
+ Memory.Scratch2.writeUInt16BE(value);
1186
+
1187
+ this.writeBuffer(address, Memory.Scratch2, force);
1188
+
1189
+ return this;
1190
+ }
1191
+
1192
+ /**
1193
+ * Write a 16‑bit little-endian unsigned integer.
1194
+ * @param address Destination address.
1195
+ * @param value Value to write.
1196
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1197
+ * @returns `this` for chaining.
1198
+ * @throws {Win32Error} If the underlying write or protection change fails.
1199
+ */
1200
+
1201
+ public writeUInt16LE(address: bigint | number, value: number, force = false): this {
1202
+ Memory.Scratch2.writeUInt16LE(value);
1203
+
1204
+ this.writeBuffer(address, Memory.Scratch2, force);
1205
+
1206
+ return this;
1207
+ }
1208
+
1209
+ /**
1210
+ * Write a 32‑bit big-endian unsigned integer.
1211
+ * @param address Destination address.
1212
+ * @param value Value to write.
1213
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1214
+ * @returns `this` for chaining.
1215
+ * @throws {Win32Error} If the underlying write or protection change fails.
1216
+ */
1217
+
1218
+ public writeUInt32BE(address: bigint | number, value: number, force = false): this {
1219
+ Memory.Scratch4.writeUInt32BE(value);
1220
+
1221
+ this.writeBuffer(address, Memory.Scratch4, force);
1222
+
1223
+ return this;
1224
+ }
1225
+
1226
+ /**
1227
+ * Write a 32‑bit little-endian unsigned integer.
1228
+ * @param address Destination address.
1229
+ * @param value Value to write.
1230
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1231
+ * @returns `this` for chaining.
1232
+ * @throws {Win32Error} If the underlying write or protection change fails.
1233
+ */
1234
+
1235
+ public writeUInt32LE(address: bigint | number, value: number, force = false): this {
1236
+ Memory.Scratch4.writeUInt32LE(value);
1237
+
1238
+ this.writeBuffer(address, Memory.Scratch4, force);
1239
+
1240
+ return this;
1241
+ }
1242
+
1243
+ /**
1244
+ * Write an 8‑bit unsigned integer.
1245
+ * @param address Destination address.
1246
+ * @param value Value to write.
1247
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1248
+ * @returns `this` for chaining.
1249
+ * @throws {Win32Error} If the underlying write or protection change fails.
1250
+ */
1251
+
1252
+ public writeUInt8(address: bigint | number, value: number, force = false): this {
1253
+ Memory.Scratch1.writeUInt8(value);
1254
+
1255
+ this.writeBuffer(address, Memory.Scratch1, force);
1256
+
1257
+ return this;
1258
+ }
1259
+
1260
+ // + TODO: Implement scratch…
1261
+
1262
+ /**
1263
+ * Write a big-endian unsigned integer of arbitrary byte length.
1264
+ * @param address Destination address.
1265
+ * @param value Value to write.
1266
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1267
+ * @returns `this` for chaining.
1268
+ * @throws {Win32Error} If the underlying write or protection change fails.
1269
+ */
1270
+
1271
+ public writeUIntBE(address: bigint | number, value: number, byteLength: number, force = false): this {
1272
+ const buffer = Buffer.allocUnsafe(byteLength);
1273
+ /* */ buffer.writeUIntBE(value, 0, byteLength);
1274
+
1275
+ this.writeBuffer(address, buffer, force);
1276
+
1277
+ return this;
1278
+ }
1279
+
1280
+ // + TODO: Implement scratch…
1281
+
1282
+ /**
1283
+ * Write a little-endian unsigned integer of arbitrary byte length.
1284
+ * @param address Destination address.
1285
+ * @param value Value to write.
1286
+ * @param force When true, temporarily enables `PAGE_EXECUTE_READWRITE` to bypass protection.
1287
+ * @returns `this` for chaining.
1288
+ * @throws {Win32Error} If the underlying write or protection change fails.
1289
+ */
1290
+
1291
+ public writeUIntLE(address: bigint | number, value: number, byteLength: number, force = false): this {
1292
+ const buffer = Buffer.allocUnsafe(byteLength);
1293
+ /* */ buffer.writeUIntLE(value, 0, byteLength);
1294
+
1295
+ this.writeBuffer(address, buffer, force);
1296
+
1297
+ return this;
1298
+ }
1299
+ }
1300
+
1301
+ export default Memory;