bun-memory 1.1.35 → 1.1.37

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.
@@ -10,6 +10,8 @@ import Memory from 'bun-memory';
10
10
  import ClientDLLJSON from './offsets/client_dll.json';
11
11
  import OffsetsJSON from './offsets/offsets.json';
12
12
 
13
+ const Iterations = 1e6;
14
+
13
15
  // Load the needed offsets as bigints… It's ugly but… IntelliSense! 🫠…
14
16
  const Client = {
15
17
  ...Object.fromEntries(Object.entries(ClientDLLJSON['client.dll'].classes).map(([class_, { fields }]) => [class_, Object.fromEntries(Object.entries(fields).map(([field, value]) => [field, BigInt(value)]))])),
@@ -32,11 +34,12 @@ if (ClientPtr === undefined) {
32
34
  // Warmup…
33
35
  console.log('Warming up…');
34
36
 
35
- for (let i = 0; i < 1e6; i++) {
37
+ for (let i = 0; i < Iterations; i++) {
36
38
  const GlobalVarsPtr = cs2.u64(ClientPtr + Client.Other.dwGlobalVars);
37
39
  /* */ const CurTime = cs2.f32(GlobalVarsPtr + 0x30n);
38
40
 
39
41
  const lPlayerControllerPtr = cs2.u64(ClientPtr + Client.Other.dwLocalPlayerController);
42
+ /* */ const lPlayerName = cs2.string(lPlayerControllerPtr + Client.CBasePlayerController.m_iszPlayerName, 32);
40
43
 
41
44
  const lPlayerPawnPtr = cs2.u64(ClientPtr + Client.Other.dwLocalPlayerPawn);
42
45
  /* */ const lHealth = cs2.u32(lPlayerPawnPtr + Client.C_BaseEntity.m_iHealth);
@@ -52,75 +55,92 @@ const EntityListScratch = new BigUint64Array(0x200 / 0x08);
52
55
  const EntityClassInfoNames = new Map<bigint, string>();
53
56
 
54
57
  // Start the test…
55
- console.log('Starting the test…');
58
+ for (let i = 0; i < 10; i++) {
59
+ console.log('Starting the test…');
56
60
 
57
- const performance1 = performance.now();
61
+ const start = performance.now();
58
62
 
59
- const EntityListPtr = cs2.u64(ClientPtr + Client.Other.dwEntityList);
63
+ const EntityListPtr = cs2.u64(ClientPtr + Client.Other.dwEntityList);
60
64
 
61
- for (let i = 0; i < 1e6; i++) {
62
- try {
63
- // Traverse the entity list and store it in `BaseEntityPtrs`…
64
- cs2.read(EntityListPtr + 0x10n, EntityListScratch);
65
+ for (let j = 0; j < Iterations; j++) {
66
+ try {
67
+ // Traverse the entity list and store it in `BaseEntityPtrs`…
68
+ void cs2.read(EntityListPtr + 0x10n, EntityListScratch);
65
69
 
66
- // Traverse each of the potential 64 entity chunks…
67
- for (let i = 0; i < 0x40; i++) {
68
- const EntityChunkPtr = EntityListScratch[i];
70
+ // Traverse each of the potential 64 entity chunks…
71
+ for (let k = 0; k < 0x40; k++) {
72
+ const EntityChunkPtr = EntityListScratch[k];
69
73
 
70
- if (EntityChunkPtr === 0n) {
71
- continue;
72
- }
74
+ if (EntityChunkPtr === 0n) {
75
+ continue;
76
+ }
73
77
 
74
- cs2.read(EntityChunkPtr, EntityChunkScratch);
78
+ void cs2.read(EntityChunkPtr, EntityChunkScratch);
75
79
 
76
- // Traverse each of the potential 512 entities within this chunk…
77
- // for (let j = 0, l = 0; j < 0x200; j++, l += 0x0f) {
78
- for (let l = 0; l < 0x1e00; l += 0x0f) {
79
- const BaseEntityPtr = EntityChunkScratch[l];
80
+ // Traverse each of the potential 512 entities within this chunk…
81
+ for (let l = 0; l < 0x1e00; l += 0x0f) {
82
+ const BaseEntityPtr = EntityChunkScratch[l];
80
83
 
81
- if (BaseEntityPtr === 0n) {
82
- continue;
83
- }
84
+ if (BaseEntityPtr === 0n) {
85
+ continue;
86
+ }
84
87
 
85
- const EntityClassInfoPtr = EntityChunkScratch[l + 0x01];
88
+ const EntityClassInfoPtr = EntityChunkScratch[l + 0x01];
86
89
 
87
- let Name = EntityClassInfoNames.get(EntityClassInfoPtr);
90
+ let Name = EntityClassInfoNames.get(EntityClassInfoPtr);
88
91
 
89
- if (Name === undefined) {
90
- const SchemaClassInfoDataPtr = cs2.u64(EntityClassInfoPtr + 0x30n);
91
- /* */ const NamePtr = cs2.u64(SchemaClassInfoDataPtr + 0x08n);
92
- /* */ Name = cs2.buffer(NamePtr, 0x20).toString();
92
+ if (Name === undefined) {
93
+ const SchemaClassInfoDataPtr = cs2.u64(EntityClassInfoPtr + 0x30n);
94
+ /* */ const NamePtr = cs2.u64(SchemaClassInfoDataPtr + 0x08n);
95
+ // /* */ Name = cs2.buffer(NamePtr, 0x20).toString();
96
+ /* */ Name = cs2.string(NamePtr, 0x40);
93
97
 
94
- EntityClassInfoNames.set(EntityClassInfoPtr, Name);
95
- }
98
+ EntityClassInfoNames.set(EntityClassInfoPtr, Name);
99
+ }
96
100
 
97
- let BaseEntityPtrs_ = BaseEntityPtrs.get(Name);
101
+ let BaseEntityPtrs_ = BaseEntityPtrs.get(Name);
98
102
 
99
- if (BaseEntityPtrs_ === undefined) {
100
- BaseEntityPtrs_ = [];
103
+ if (BaseEntityPtrs_ === undefined) {
104
+ BaseEntityPtrs_ = [];
101
105
 
102
- BaseEntityPtrs.set(Name, BaseEntityPtrs_);
103
- }
106
+ BaseEntityPtrs.set(Name, BaseEntityPtrs_);
107
+ }
104
108
 
105
- BaseEntityPtrs_.push(BaseEntityPtr);
109
+ BaseEntityPtrs_.push(BaseEntityPtr);
110
+ }
106
111
  }
107
- }
108
112
 
109
- // ! —————————————————————————————————————————————————————————————————————————————————————————————
110
- // ! YOUR CODE GOES HERE…
111
- // ! —————————————————————————————————————————————————————————————————————————————————————————————
112
-
113
- // ! —————————————————————————————————————————————————————————————————————————————————————————————
114
- // ! YOUR CODE ENDS HERE…
115
- // ! —————————————————————————————————————————————————————————————————————————————————————————————
116
- } finally {
117
- // Clear the entity list…
118
- for (const BaseEntityPtrs_ of BaseEntityPtrs.values()) {
119
- BaseEntityPtrs_.length = 0;
113
+ // ! —————————————————————————————————————————————————————————————————————————————————————————————
114
+ // ! YOUR CODE GOES HERE…
115
+ // ! —————————————————————————————————————————————————————————————————————————————————————————————
116
+
117
+ // // Log our entities…
118
+ // console.log(
119
+ // 'Entities found this tick: %O',
120
+ // Object.fromEntries([...BaseEntityPtrs.entries()].map(([Name, { length }]) => [Name, length])) //
121
+ // );
122
+
123
+ // ! —————————————————————————————————————————————————————————————————————————————————————————————
124
+ // ! YOUR CODE ENDS HERE…
125
+ // ! —————————————————————————————————————————————————————————————————————————————————————————————
126
+ } finally {
127
+ // Clear the entity list…
128
+ for (const BaseEntityPtrs_ of BaseEntityPtrs.values()) {
129
+ BaseEntityPtrs_.length = 0;
130
+ }
120
131
  }
121
132
  }
122
- }
123
133
 
124
- const performance2 = performance.now();
134
+ const end = performance.now();
135
+
136
+ const total = end - start;
137
+ const average = total / Iterations;
125
138
 
126
- console.log('Test completed in %fms…', (performance2 - performance1).toFixed(2));
139
+ console.log(
140
+ 'Completed %d iterations in %ss, averaging %sms (%sµs) each…', //
141
+ Iterations,
142
+ (total / 1_000).toFixed(2),
143
+ average.toFixed(2),
144
+ (average * 1_000).toFixed(2)
145
+ );
146
+ }
@@ -1,4 +1,4 @@
1
- import { FFIType, dlopen } from 'bun:ffi';
1
+ import { FFIType, dlopen, ptr } from 'bun:ffi';
2
2
  import { sleep } from 'bun';
3
3
 
4
4
  import Memory from 'bun-memory';
@@ -32,12 +32,14 @@ const Client = {
32
32
  const cs2 = new Memory('cs2.exe');
33
33
 
34
34
  // Get the base for client.dll…
35
- const ClientPtr = cs2.modules['client.dll']?.base;
35
+ const client = cs2.modules['client.dll'];
36
36
 
37
- if (ClientPtr === undefined) {
38
- throw new TypeError('ClientPtr must not be undefined.');
37
+ if (client === undefined) {
38
+ throw new TypeError('client must not be undefined.');
39
39
  }
40
40
 
41
+ const ClientPtr = client.base;
42
+
41
43
  // Create a cache for class name strings… 🫠…
42
44
  const Cache_Names = new Map<bigint, string>();
43
45
 
@@ -97,7 +99,7 @@ async function tick(ClientPtr: bigint) {
97
99
  if (Name === undefined) {
98
100
  const SchemaClassInfoDataPtr = cs2.u64(EntityClassInfoPtr + 0x30n);
99
101
  /* */ const NamePtr = cs2.u64(SchemaClassInfoDataPtr + 0x08n);
100
- /* */ Name = cs2.cString(NamePtr, 0x80).toString();
102
+ /* */ Name = cs2.string(NamePtr, 0x80);
101
103
 
102
104
  Cache_Names.set(EntityClassInfoPtr, Name);
103
105
  }
package/package.json CHANGED
@@ -22,7 +22,7 @@
22
22
  "url": "git://github.com/obscuritysrl/bun-memory.git"
23
23
  },
24
24
  "type": "module",
25
- "version": "1.1.35",
25
+ "version": "1.1.37",
26
26
  "main": "./index.ts",
27
27
  "keywords": [
28
28
  "bun",
package/structs/Memory.ts CHANGED
@@ -4,21 +4,21 @@ import type { Module, NetworkUtlVector, Point, QAngle, Quaternion, Region, RGB,
4
4
  import Win32Error from './Win32Error';
5
5
 
6
6
  const {
7
- symbols: { CloseHandle, CreateToolhelp32Snapshot, GetLastError, Module32FirstW, Module32NextW, OpenProcess, Process32FirstW, Process32NextW, ReadProcessMemory, VirtualQueryEx, WriteProcessMemory },
7
+ symbols: { CloseHandle, CreateToolhelp32Snapshot, GetLastError, Module32FirstW, Module32NextW, OpenProcess, Process32FirstW, Process32NextW, ReadProcessMemory, VirtualProtectEx, WriteProcessMemory },
8
8
  } = dlopen('kernel32.dll', {
9
9
  CloseHandle: { args: [FFIType.u64], returns: FFIType.bool },
10
10
  CreateToolhelp32Snapshot: { args: [FFIType.u32, FFIType.u32], returns: FFIType.u64 },
11
+ GetCurrentProcess: { args: [], returns: FFIType.ptr },
11
12
  GetLastError: { returns: FFIType.u32 },
12
- IsWow64Process2: { args: [FFIType.u64, FFIType.ptr, FFIType.ptr], returns: FFIType.bool },
13
13
  Module32FirstW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
14
14
  Module32NextW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
15
15
  OpenProcess: { args: [FFIType.u32, FFIType.bool, FFIType.u32], returns: FFIType.u64 },
16
16
  Process32FirstW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
17
17
  Process32NextW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
18
- ReadProcessMemory: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64_fast, FFIType.ptr], returns: FFIType.bool },
18
+ ReadProcessMemory: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64, FFIType.ptr], returns: FFIType.bool },
19
19
  VirtualProtectEx: { args: [FFIType.u64, FFIType.u64, FFIType.u64, FFIType.u32, FFIType.ptr], returns: FFIType.bool },
20
20
  VirtualQueryEx: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64], returns: FFIType.u64 },
21
- WriteProcessMemory: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64_fast, FFIType.ptr], returns: FFIType.bool },
21
+ WriteProcessMemory: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64, FFIType.ptr], returns: FFIType.bool },
22
22
  });
23
23
 
24
24
  /**
@@ -28,6 +28,7 @@ const {
28
28
  *
29
29
  * Many scalar reads utilize `TypedArray` scratches to avoid a second FFI hop, such as calling `bun:ffi.read.*`.
30
30
  *
31
+ * @todo Add call method for calling functions in remote process.
31
32
  * @todo Add support for 32 or 64-bit processes using IsWow64Process2 (Windows 10+).
32
33
  * @todo When adding 32-bit support, several u64 will need changed to u64_fast.
33
34
  *
@@ -109,15 +110,6 @@ class Memory {
109
110
  throw new Error(`Process not found: ${identifier}…`);
110
111
  }
111
112
 
112
- /**
113
- * Memory protection flags for safe and unsafe regions.
114
- * Used to filter readable/writable memory areas.
115
- */
116
- private static readonly MemoryProtections = {
117
- 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 */,
118
- Unsafe: 0x100 /* PAGE_GUARD */ | 0x01 /* PAGE_NOACCESS */,
119
- };
120
-
121
113
  /**
122
114
  * Regex patterns for matching hex strings and wildcards in memory scans.
123
115
  * Used by the pattern method.
@@ -168,12 +160,12 @@ class Memory {
168
160
  private readonly Scratch16 = new Uint8Array(0x10);
169
161
  private readonly Scratch16Float32Array = new Float32Array(this.Scratch16.buffer, this.Scratch16.byteOffset, 0x04);
170
162
 
171
- private readonly ScratchMemoryBasicInformation = Buffer.allocUnsafe(0x30 /* sizeof(MEMORY_BASIC_INFORMATION) */);
172
163
  private readonly ScratchModuleEntry32W = Buffer.allocUnsafe(0x438 /* sizeof(MODULEENTRY32W) */);
173
164
 
174
- private static TextDecoderUTF16 = new TextDecoder('utf-16');
175
165
  private static TextDecoderUTF8 = new TextDecoder('utf-8');
176
166
 
167
+ private static TextEncoderUTF8 = new TextEncoder('utf-8');
168
+
177
169
  private readonly hProcess: bigint;
178
170
  private readonly th32ProcessID: number;
179
171
 
@@ -190,43 +182,6 @@ class Memory {
190
182
  return this._modules;
191
183
  }
192
184
 
193
- /**
194
- * Returns memory regions in a given address range.
195
- * @param address Start address.
196
- * @param length Number of bytes to scan.
197
- * @returns Array of memory regions.
198
- */
199
- private regions(address: bigint, length: bigint | number): Region[] {
200
- const dwLength = 0x30; /* sizeof(MEMORY_BASIC_INFORMATION) */
201
- let lpAddress = BigInt(address); // prettier-ignore
202
- const lpBuffer = this.ScratchMemoryBasicInformation;
203
-
204
- const bVirtualQueryEx = !!VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength);
205
-
206
- if (!bVirtualQueryEx) {
207
- throw new Win32Error('VirtualQueryEx', GetLastError());
208
- }
209
-
210
- const end = lpAddress + BigInt(length);
211
- const result: Region[] = [];
212
-
213
- do {
214
- const baseAddress = lpBuffer.readBigUInt64LE();
215
- const protect = lpBuffer.readUInt32LE(36);
216
- const regionSize = lpBuffer.readBigUInt64LE(24);
217
- const state = lpBuffer.readUInt32LE(32);
218
- const type = lpBuffer.readUInt32LE(40);
219
-
220
- if ((protect & Memory.MemoryProtections.Safe) !== 0 && (protect & Memory.MemoryProtections.Unsafe) === 0 && state === 0x1000 /* MEM_COMMIT */) {
221
- result.push({ base: baseAddress, protect, size: regionSize, state, type });
222
- }
223
-
224
- lpAddress = baseAddress + regionSize;
225
- } while (lpAddress < end && !!VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength));
226
-
227
- return result;
228
- }
229
-
230
185
  /**
231
186
  * Closes the process handle.
232
187
  * @example
@@ -332,7 +287,7 @@ class Memory {
332
287
  public read<T extends Scratch>(address: bigint, scratch: T): T {
333
288
  const lpBaseAddress = address;
334
289
  const lpBuffer = ptr(scratch);
335
- const nSize = scratch.byteLength;
290
+ const nSize = BigInt(scratch.byteLength);
336
291
  const numberOfBytesRead = 0x00n;
337
292
 
338
293
  const bReadProcessMemory = ReadProcessMemory(this.hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesRead);
@@ -348,25 +303,57 @@ class Memory {
348
303
  * Writes a buffer to memory.
349
304
  * @param address Address to write to.
350
305
  * @param scratch Buffer to write.
306
+ * @param force If true, temporarily changes the page protection to PAGE_EXECUTE_READWRITE using `VirtualProtectEx` while performing the write.
351
307
  * @returns This instance.
352
- * @todo Add a `force: boolean` option that uses `VirtualProtectEx` for a temporary protection change when set to `true`.
353
- * @todo Consider inlining the call in the if to cut a binding… I hate the idea… 🫠…
354
308
  * @example
355
309
  * ```ts
356
310
  * const cs2 = new Memory('cs2.exe');
357
311
  * cs2.write(0x12345678n, new Uint8Array([1,2,3,4]));
312
+ * // Force a write by temporarily changing memory protection
313
+ * cs2.write(0x12345678n, new Uint8Array([1,2,3,4]), true);
358
314
  * ```
359
315
  */
360
- private write(address: bigint, scratch: Scratch): this {
316
+ private write(address: bigint, scratch: Scratch, force: boolean = false): this {
361
317
  const lpBaseAddress = address;
362
318
  const lpBuffer = scratch.ptr;
363
- const nSize = scratch.byteLength;
319
+ const nSize = BigInt(scratch.byteLength);
364
320
  const numberOfBytesWritten = 0x00n;
365
321
 
366
- const bWriteProcessMemory = WriteProcessMemory(this.hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesWritten);
322
+ if (!force) {
323
+ const bWriteProcessMemory = WriteProcessMemory(this.hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesWritten);
324
+
325
+ if (!bWriteProcessMemory) {
326
+ throw new Win32Error('WriteProcessMemory', GetLastError());
327
+ }
328
+
329
+ return this;
330
+ }
331
+
332
+ const dwSize = nSize;
333
+ const flNewProtect = 0x40; /* PAGE_EXECUTE_READWRITE */
334
+ const lpflOldProtect = Buffer.allocUnsafe(0x04);
335
+
336
+ const bVirtualProtectEx = VirtualProtectEx(this.hProcess, lpBaseAddress, dwSize, flNewProtect, lpflOldProtect.ptr);
337
+
338
+ if (!bVirtualProtectEx) {
339
+ throw new Win32Error('VirtualProtectEx', GetLastError());
340
+ }
341
+
342
+ try {
343
+ const bWriteProcessMemory = WriteProcessMemory(this.hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesWritten);
344
+
345
+ if (!bWriteProcessMemory) {
346
+ throw new Win32Error('WriteProcessMemory', GetLastError());
347
+ }
348
+ } finally {
349
+ const flNewProtect2 = lpflOldProtect.readUInt32LE(0x00);
350
+ const lpflOldProtect2 = Buffer.allocUnsafe(0x04);
351
+
352
+ const bVirtualProtectEx2 = VirtualProtectEx(this.hProcess, lpBaseAddress, dwSize, flNewProtect2, lpflOldProtect2.ptr);
367
353
 
368
- if (!bWriteProcessMemory) {
369
- throw new Win32Error('WriteProcessMemory', GetLastError());
354
+ if (!bVirtualProtectEx2) {
355
+ throw new Win32Error('VirtualProtectEx', GetLastError());
356
+ }
370
357
  }
371
358
 
372
359
  return this;
@@ -395,7 +382,7 @@ class Memory {
395
382
 
396
383
  Scratch1[0x00] = value ? 1 : 0;
397
384
 
398
- this.write(address, Scratch1);
385
+ void this.write(address, Scratch1);
399
386
 
400
387
  return this;
401
388
  }
@@ -424,7 +411,7 @@ class Memory {
424
411
 
425
412
  const value = lengthOrValue;
426
413
 
427
- this.write(address, value);
414
+ void this.write(address, value);
428
415
 
429
416
  return this;
430
417
  }
@@ -447,14 +434,20 @@ class Memory {
447
434
  if (typeof lengthOrValue === 'number') {
448
435
  const scratch = new Uint8Array(lengthOrValue);
449
436
 
450
- this.read(address, scratch);
437
+ void this.read(address, scratch);
438
+
439
+ const indexOf = scratch.indexOf(0x00);
440
+
441
+ if (indexOf === -1) {
442
+ scratch[lengthOrValue - 1] = 0x00;
443
+ }
451
444
 
452
445
  return new CString(scratch.ptr);
453
446
  }
454
447
 
455
448
  const scratch = Buffer.from(lengthOrValue);
456
449
 
457
- this.write(address, scratch);
450
+ void this.write(address, scratch);
458
451
 
459
452
  return this;
460
453
  }
@@ -482,7 +475,7 @@ class Memory {
482
475
 
483
476
  Scratch4Float32Array[0x00] = value;
484
477
 
485
- this.write(address, Scratch4Float32Array);
478
+ void this.write(address, Scratch4Float32Array);
486
479
 
487
480
  return this;
488
481
  }
@@ -506,14 +499,14 @@ class Memory {
506
499
  const length = lengthOrValues;
507
500
  const scratch = new Float32Array(length);
508
501
 
509
- this.read(address, scratch);
502
+ void this.read(address, scratch);
510
503
 
511
504
  return scratch;
512
505
  }
513
506
 
514
507
  const values = lengthOrValues;
515
508
 
516
- this.write(address, values);
509
+ void this.write(address, values);
517
510
 
518
511
  return this;
519
512
  }
@@ -541,7 +534,7 @@ class Memory {
541
534
 
542
535
  Scratch8Float64Array[0x00] = value;
543
536
 
544
- this.write(address, Scratch8Float64Array);
537
+ void this.write(address, Scratch8Float64Array);
545
538
 
546
539
  return this;
547
540
  }
@@ -565,14 +558,14 @@ class Memory {
565
558
  const length = lengthOrValues;
566
559
  const scratch = new Float64Array(length);
567
560
 
568
- this.read(address, scratch);
561
+ void this.read(address, scratch);
569
562
 
570
563
  return scratch;
571
564
  }
572
565
 
573
566
  const values = lengthOrValues;
574
567
 
575
- this.write(address, values);
568
+ void this.write(address, values);
576
569
 
577
570
  return this;
578
571
  }
@@ -600,7 +593,7 @@ class Memory {
600
593
 
601
594
  Scratch2Int16Array[0x00] = value;
602
595
 
603
- this.write(address, Scratch2Int16Array);
596
+ void this.write(address, Scratch2Int16Array);
604
597
 
605
598
  return this;
606
599
  }
@@ -624,14 +617,14 @@ class Memory {
624
617
  const length = lengthOrValues;
625
618
  const scratch = new Int16Array(length);
626
619
 
627
- this.read(address, scratch);
620
+ void this.read(address, scratch);
628
621
 
629
622
  return scratch;
630
623
  }
631
624
 
632
625
  const values = lengthOrValues;
633
626
 
634
- this.write(address, values);
627
+ void this.write(address, values);
635
628
 
636
629
  return this;
637
630
  }
@@ -659,7 +652,7 @@ class Memory {
659
652
 
660
653
  Scratch4Int32Array[0x00] = value;
661
654
 
662
- this.write(address, Scratch4Int32Array);
655
+ void this.write(address, Scratch4Int32Array);
663
656
 
664
657
  return this;
665
658
  }
@@ -683,14 +676,14 @@ class Memory {
683
676
  const length = lengthOrValues;
684
677
  const scratch = new Int32Array(length);
685
678
 
686
- this.read(address, scratch);
679
+ void this.read(address, scratch);
687
680
 
688
681
  return scratch;
689
682
  }
690
683
 
691
684
  const values = lengthOrValues;
692
685
 
693
- this.write(address, values);
686
+ void this.write(address, values);
694
687
 
695
688
  return this;
696
689
  }
@@ -718,7 +711,7 @@ class Memory {
718
711
 
719
712
  Scratch8BigInt64Array[0x00] = value;
720
713
 
721
- this.write(address, Scratch8BigInt64Array);
714
+ void this.write(address, Scratch8BigInt64Array);
722
715
 
723
716
  return this;
724
717
  }
@@ -742,14 +735,14 @@ class Memory {
742
735
  const length = lengthOrValues;
743
736
  const scratch = new BigInt64Array(length);
744
737
 
745
- this.read(address, scratch);
738
+ void this.read(address, scratch);
746
739
 
747
740
  return scratch;
748
741
  }
749
742
 
750
743
  const values = lengthOrValues;
751
744
 
752
- this.write(address, values);
745
+ void this.write(address, values);
753
746
 
754
747
  return this;
755
748
  }
@@ -777,7 +770,7 @@ class Memory {
777
770
 
778
771
  Scratch1Int8Array[0x00] = value;
779
772
 
780
- this.write(address, Scratch1Int8Array);
773
+ void this.write(address, Scratch1Int8Array);
781
774
 
782
775
  return this;
783
776
  }
@@ -801,14 +794,14 @@ class Memory {
801
794
  const length = lengthOrValues;
802
795
  const scratch = new Int8Array(length);
803
796
 
804
- this.read(address, scratch);
797
+ void this.read(address, scratch);
805
798
 
806
799
  return scratch;
807
800
  }
808
801
 
809
802
  const values = lengthOrValues;
810
803
 
811
- this.write(address, values);
804
+ void this.write(address, values);
812
805
 
813
806
  return this;
814
807
  }
@@ -831,7 +824,7 @@ class Memory {
831
824
  if (values === undefined) {
832
825
  const scratch = new Float32Array(0x09);
833
826
 
834
- this.read(address, scratch);
827
+ void this.read(address, scratch);
835
828
 
836
829
  return scratch;
837
830
  }
@@ -840,7 +833,7 @@ class Memory {
840
833
  throw new RangeError('values.length must be 9.');
841
834
  }
842
835
 
843
- this.write(address, values);
836
+ void this.write(address, values);
844
837
 
845
838
  return this;
846
839
  }
@@ -863,7 +856,7 @@ class Memory {
863
856
  if (values === undefined) {
864
857
  const scratch = new Float32Array(0x0c);
865
858
 
866
- this.read(address, scratch);
859
+ void this.read(address, scratch);
867
860
 
868
861
  return scratch;
869
862
  }
@@ -872,7 +865,7 @@ class Memory {
872
865
  throw new RangeError('values.length must be 12.');
873
866
  }
874
867
 
875
- this.write(address, values);
868
+ void this.write(address, values);
876
869
 
877
870
  return this;
878
871
  }
@@ -895,7 +888,7 @@ class Memory {
895
888
  if (values === undefined) {
896
889
  const scratch = new Float32Array(0x10);
897
890
 
898
- this.read(address, scratch);
891
+ void this.read(address, scratch);
899
892
 
900
893
  return scratch;
901
894
  }
@@ -904,7 +897,7 @@ class Memory {
904
897
  throw new RangeError('values.length must be 16.');
905
898
  }
906
899
 
907
- this.write(address, values);
900
+ void this.write(address, values);
908
901
 
909
902
  return this;
910
903
  }
@@ -931,14 +924,14 @@ class Memory {
931
924
 
932
925
  const scratch = new Uint32Array(size);
933
926
 
934
- this.read(elementsPtr, scratch);
927
+ void this.read(elementsPtr, scratch);
935
928
 
936
929
  return scratch;
937
930
  }
938
931
 
939
932
  this.u32(address, values.length);
940
933
 
941
- this.write(elementsPtr, values);
934
+ void this.write(elementsPtr, values);
942
935
 
943
936
  return this;
944
937
  }
@@ -961,7 +954,7 @@ class Memory {
961
954
  const Scratch8Float32Array = this.Scratch8Float32Array;
962
955
 
963
956
  if (value === undefined) {
964
- this.read(address, Scratch8Float32Array);
957
+ void this.read(address, Scratch8Float32Array);
965
958
 
966
959
  const x = Scratch8Float32Array[0x00],
967
960
  y = Scratch8Float32Array[0x01]; // prettier-ignore
@@ -972,7 +965,7 @@ class Memory {
972
965
  Scratch8Float32Array[0x00] = value.x;
973
966
  Scratch8Float32Array[0x01] = value.y;
974
967
 
975
- this.write(address, Scratch8Float32Array);
968
+ void this.write(address, Scratch8Float32Array);
976
969
 
977
970
  return this;
978
971
  }
@@ -996,7 +989,7 @@ class Memory {
996
989
  const length = lengthOrValues;
997
990
  const scratch = new Float32Array(length * 2);
998
991
 
999
- this.read(address, scratch);
992
+ void this.read(address, scratch);
1000
993
 
1001
994
  const result = new Array<Vector2>(length);
1002
995
 
@@ -1020,7 +1013,7 @@ class Memory {
1020
1013
  scratch[j + 0x01] = vector2.y;
1021
1014
  }
1022
1015
 
1023
- this.write(address, scratch);
1016
+ void this.write(address, scratch);
1024
1017
 
1025
1018
  return this;
1026
1019
  }
@@ -1036,7 +1029,7 @@ class Memory {
1036
1029
  throw new RangeError('values.length must be 2.');
1037
1030
  }
1038
1031
 
1039
- this.write(address, values);
1032
+ void this.write(address, values);
1040
1033
 
1041
1034
  return this;
1042
1035
  }
@@ -1059,7 +1052,7 @@ class Memory {
1059
1052
  const Scratch12Float32Array = this.Scratch12Float32Array;
1060
1053
 
1061
1054
  if (value === undefined) {
1062
- this.read(address, Scratch12Float32Array);
1055
+ void this.read(address, Scratch12Float32Array);
1063
1056
 
1064
1057
  const pitch = Scratch12Float32Array[0x00],
1065
1058
  roll = Scratch12Float32Array[0x02],
@@ -1072,7 +1065,7 @@ class Memory {
1072
1065
  Scratch12Float32Array[0x02] = value.roll;
1073
1066
  Scratch12Float32Array[0x01] = value.yaw;
1074
1067
 
1075
- this.write(address, Scratch12Float32Array);
1068
+ void this.write(address, Scratch12Float32Array);
1076
1069
 
1077
1070
  return this;
1078
1071
  }
@@ -1096,7 +1089,7 @@ class Memory {
1096
1089
  const length = lengthOrValues;
1097
1090
  const scratch = new Float32Array(length * 0x03);
1098
1091
 
1099
- this.read(address, scratch);
1092
+ void this.read(address, scratch);
1100
1093
 
1101
1094
  const result = new Array<QAngle>(length);
1102
1095
 
@@ -1122,7 +1115,7 @@ class Memory {
1122
1115
  scratch[j + 0x01] = qAngle.yaw;
1123
1116
  }
1124
1117
 
1125
- this.write(address, scratch);
1118
+ void this.write(address, scratch);
1126
1119
 
1127
1120
  return this;
1128
1121
  }
@@ -1150,7 +1143,7 @@ class Memory {
1150
1143
  throw new RangeError('values.length must be 3.');
1151
1144
  }
1152
1145
 
1153
- this.write(address, values);
1146
+ void this.write(address, values);
1154
1147
 
1155
1148
  return this;
1156
1149
  }
@@ -1173,7 +1166,7 @@ class Memory {
1173
1166
  const Scratch16Float32Array = this.Scratch16Float32Array;
1174
1167
 
1175
1168
  if (value === undefined) {
1176
- this.read(address, Scratch16Float32Array);
1169
+ void this.read(address, Scratch16Float32Array);
1177
1170
 
1178
1171
  const w = Scratch16Float32Array[0x03],
1179
1172
  x = Scratch16Float32Array[0x00],
@@ -1188,7 +1181,7 @@ class Memory {
1188
1181
  Scratch16Float32Array[0x01] = value.y;
1189
1182
  Scratch16Float32Array[0x02] = value.z;
1190
1183
 
1191
- this.write(address, Scratch16Float32Array);
1184
+ void this.write(address, Scratch16Float32Array);
1192
1185
 
1193
1186
  return this;
1194
1187
  }
@@ -1212,7 +1205,7 @@ class Memory {
1212
1205
  const length = lengthOrValues;
1213
1206
  const scratch = new Float32Array(length * 0x04); // 4 * f32 per Quaternion
1214
1207
 
1215
- this.read(address, scratch);
1208
+ void this.read(address, scratch);
1216
1209
 
1217
1210
  const result = new Array<Quaternion>(length);
1218
1211
 
@@ -1240,7 +1233,7 @@ class Memory {
1240
1233
  scratch[j + 0x02] = quaternion.z;
1241
1234
  }
1242
1235
 
1243
- this.write(address, scratch);
1236
+ void this.write(address, scratch);
1244
1237
 
1245
1238
  return this;
1246
1239
  }
@@ -1268,7 +1261,7 @@ class Memory {
1268
1261
  throw new RangeError('values.length must be 4.');
1269
1262
  }
1270
1263
 
1271
- this.write(address, values);
1264
+ void this.write(address, values);
1272
1265
 
1273
1266
  return this;
1274
1267
  }
@@ -1291,7 +1284,7 @@ class Memory {
1291
1284
  const Scratch3 = this.Scratch3;
1292
1285
 
1293
1286
  if (value === undefined) {
1294
- this.read(address, Scratch3);
1287
+ void this.read(address, Scratch3);
1295
1288
 
1296
1289
  const r = Scratch3[0x00],
1297
1290
  g = Scratch3[0x01],
@@ -1304,7 +1297,9 @@ class Memory {
1304
1297
  Scratch3[0x01] = value.g;
1305
1298
  Scratch3[0x02] = value.b;
1306
1299
 
1307
- return this.write(address, Scratch3);
1300
+ void this.write(address, Scratch3);
1301
+
1302
+ return this;
1308
1303
  }
1309
1304
 
1310
1305
  /**
@@ -1330,7 +1325,7 @@ class Memory {
1330
1325
  throw new RangeError('values.length must be 3.');
1331
1326
  }
1332
1327
 
1333
- this.write(address, values);
1328
+ void this.write(address, values);
1334
1329
 
1335
1330
  return this;
1336
1331
  }
@@ -1353,7 +1348,7 @@ class Memory {
1353
1348
  const Scratch4 = this.Scratch4;
1354
1349
 
1355
1350
  if (value === undefined) {
1356
- this.read(address, Scratch4);
1351
+ void this.read(address, Scratch4);
1357
1352
 
1358
1353
  const r = Scratch4[0x00],
1359
1354
  g = Scratch4[0x01],
@@ -1368,7 +1363,9 @@ class Memory {
1368
1363
  Scratch4[0x02] = value.b;
1369
1364
  Scratch4[0x03] = value.a;
1370
1365
 
1371
- return this.write(address, Scratch4);
1366
+ void this.write(address, Scratch4);
1367
+
1368
+ return this;
1372
1369
  }
1373
1370
 
1374
1371
  /**
@@ -1394,7 +1391,45 @@ class Memory {
1394
1391
  throw new RangeError('values.length must be 4.');
1395
1392
  }
1396
1393
 
1397
- this.write(address, values);
1394
+ void this.write(address, values);
1395
+
1396
+ return this;
1397
+ }
1398
+
1399
+ /**
1400
+ * Reads or writes a UTF-8 string.
1401
+ * @param address Address to access.
1402
+ * @param lengthOrValue Length to read or string to write.
1403
+ * @returns The string at address, or this instance if writing.
1404
+ * @notice When writing, remember to null-terminate your string (e.g., 'hello\0').
1405
+ * @todo Compare performance when using CString vs TextDecoder when reading…
1406
+ * @example
1407
+ * ```ts
1408
+ * const cs2 = new Memory('cs2.exe');
1409
+ * const myString = cs2.string(0x12345678n, 16);
1410
+ * cs2.string(0x12345678n, 'hello\0');
1411
+ * ```
1412
+ */
1413
+ public string(address: bigint, length: number): string;
1414
+ public string(address: bigint, value: string): this;
1415
+ public string(address: bigint, lengthOrValue: number | string): string | this {
1416
+ if (typeof lengthOrValue === 'number') {
1417
+ const scratch = new Uint8Array(lengthOrValue);
1418
+
1419
+ void this.read(address, scratch);
1420
+
1421
+ const indexOf = scratch.indexOf(0x00);
1422
+
1423
+ return Memory.TextDecoderUTF8.decode(
1424
+ scratch.subarray(0, indexOf !== -1 ? indexOf : lengthOrValue) //
1425
+ );
1426
+
1427
+ // return new CString(scratch.ptr).valueOf();
1428
+ }
1429
+
1430
+ const scratch = Memory.TextEncoderUTF8.encode(lengthOrValue);
1431
+
1432
+ void this.write(address, scratch);
1398
1433
 
1399
1434
  return this;
1400
1435
  }
@@ -1422,7 +1457,7 @@ class Memory {
1422
1457
 
1423
1458
  Scratch2Uint16Array[0x00] = value;
1424
1459
 
1425
- this.write(address, Scratch2Uint16Array);
1460
+ void this.write(address, Scratch2Uint16Array);
1426
1461
 
1427
1462
  return this;
1428
1463
  }
@@ -1446,14 +1481,14 @@ class Memory {
1446
1481
  const length = lengthOrValues;
1447
1482
  const scratch = new Uint16Array(length);
1448
1483
 
1449
- this.read(address, scratch);
1484
+ void this.read(address, scratch);
1450
1485
 
1451
1486
  return scratch;
1452
1487
  }
1453
1488
 
1454
1489
  const values = lengthOrValues;
1455
1490
 
1456
- this.write(address, values);
1491
+ void this.write(address, values);
1457
1492
 
1458
1493
  return this;
1459
1494
  }
@@ -1481,7 +1516,7 @@ class Memory {
1481
1516
 
1482
1517
  Scratch4Uint32Array[0x00] = value;
1483
1518
 
1484
- this.write(address, Scratch4Uint32Array);
1519
+ void this.write(address, Scratch4Uint32Array);
1485
1520
 
1486
1521
  return this;
1487
1522
  }
@@ -1505,14 +1540,14 @@ class Memory {
1505
1540
  const length = lengthOrValues;
1506
1541
  const scratch = new Uint32Array(length);
1507
1542
 
1508
- this.read(address, scratch);
1543
+ void this.read(address, scratch);
1509
1544
 
1510
1545
  return scratch;
1511
1546
  }
1512
1547
 
1513
1548
  const values = lengthOrValues;
1514
1549
 
1515
- this.write(address, values);
1550
+ void this.write(address, values);
1516
1551
 
1517
1552
  return this;
1518
1553
  }
@@ -1540,7 +1575,7 @@ class Memory {
1540
1575
 
1541
1576
  Scratch8BigUint64Array[0x00] = value;
1542
1577
 
1543
- this.write(address, Scratch8BigUint64Array);
1578
+ void this.write(address, Scratch8BigUint64Array);
1544
1579
 
1545
1580
  return this;
1546
1581
  }
@@ -1564,14 +1599,14 @@ class Memory {
1564
1599
  const length = lengthOrValues;
1565
1600
  const scratch = new BigUint64Array(length);
1566
1601
 
1567
- this.read(address, scratch);
1602
+ void this.read(address, scratch);
1568
1603
 
1569
1604
  return scratch;
1570
1605
  }
1571
1606
 
1572
1607
  const values = lengthOrValues;
1573
1608
 
1574
- this.write(address, values);
1609
+ void this.write(address, values);
1575
1610
 
1576
1611
  return this;
1577
1612
  }
@@ -1599,7 +1634,7 @@ class Memory {
1599
1634
 
1600
1635
  Scratch1[0x00] = value;
1601
1636
 
1602
- this.write(address, Scratch1);
1637
+ void this.write(address, Scratch1);
1603
1638
 
1604
1639
  return this;
1605
1640
  }
@@ -1623,14 +1658,14 @@ class Memory {
1623
1658
  const length = lengthOrValues;
1624
1659
  const scratch = new Uint8Array(length);
1625
1660
 
1626
- this.read(address, scratch);
1661
+ void this.read(address, scratch);
1627
1662
 
1628
1663
  return scratch;
1629
1664
  }
1630
1665
 
1631
1666
  const values = lengthOrValues;
1632
1667
 
1633
- this.write(address, values);
1668
+ void this.write(address, values);
1634
1669
 
1635
1670
  return this;
1636
1671
  }
@@ -1738,7 +1773,7 @@ class Memory {
1738
1773
  throw new RangeError('values.length must be 2.');
1739
1774
  }
1740
1775
 
1741
- this.write(address, values);
1776
+ void this.write(address, values);
1742
1777
 
1743
1778
  return this;
1744
1779
  }
@@ -1761,7 +1796,7 @@ class Memory {
1761
1796
  const Scratch12Float32Array = this.Scratch12Float32Array;
1762
1797
 
1763
1798
  if (value === undefined) {
1764
- this.read(address, Scratch12Float32Array);
1799
+ void this.read(address, Scratch12Float32Array);
1765
1800
 
1766
1801
  const x = Scratch12Float32Array[0x00],
1767
1802
  y = Scratch12Float32Array[0x01],
@@ -1774,7 +1809,7 @@ class Memory {
1774
1809
  Scratch12Float32Array[0x01] = value.y;
1775
1810
  Scratch12Float32Array[0x02] = value.z;
1776
1811
 
1777
- this.write(address, Scratch12Float32Array);
1812
+ void this.write(address, Scratch12Float32Array);
1778
1813
 
1779
1814
  return this;
1780
1815
  }
@@ -1798,7 +1833,7 @@ class Memory {
1798
1833
  const length = lengthOrValues;
1799
1834
  const scratch = new Float32Array(length * 0x03);
1800
1835
 
1801
- this.read(address, scratch);
1836
+ void this.read(address, scratch);
1802
1837
 
1803
1838
  const result = new Array<Vector3>(length);
1804
1839
 
@@ -1824,7 +1859,7 @@ class Memory {
1824
1859
  scratch[j + 0x02] = vector3.z;
1825
1860
  }
1826
1861
 
1827
- this.write(address, scratch);
1862
+ void this.write(address, scratch);
1828
1863
 
1829
1864
  return this;
1830
1865
  }
@@ -1840,7 +1875,7 @@ class Memory {
1840
1875
  throw new RangeError('values.length must be 3.');
1841
1876
  }
1842
1877
 
1843
- this.write(address, values);
1878
+ void this.write(address, values);
1844
1879
 
1845
1880
  return this;
1846
1881
  }
@@ -1902,7 +1937,7 @@ class Memory {
1902
1937
  throw new RangeError('values.length must be 4.');
1903
1938
  }
1904
1939
 
1905
- this.write(address, values);
1940
+ void this.write(address, values);
1906
1941
 
1907
1942
  return this;
1908
1943
  }
@@ -1964,7 +1999,7 @@ class Memory {
1964
1999
  ? Buffer.from(needle.buffer, needle.byteOffset, needle.byteLength)
1965
2000
  : Buffer.from(needle);
1966
2001
 
1967
- this.read(address, haystack);
2002
+ void this.read(address, haystack);
1968
2003
 
1969
2004
  if (!all) {
1970
2005
  const indexOf = haystack.indexOf(needleBuffer);