bun-memory 1.1.34 → 1.1.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -52,7 +52,8 @@ cs2.close();
52
52
  ## API Highlights
53
53
 
54
54
  - `follow(address, offsets)` — Follow a pointer chain
55
- - `pattern(needle, address, length)` — Find a byte pattern in memory (supports wildcards)
55
+ - `indexOf(needle, address, length, [all])` — Search for a buffer or array in memory (returns all matches if all=true)
56
+ - `pattern(needle, address, length, [all])` — Find a byte pattern in memory (supports wildcards, returns all matches if all=true)
56
57
  - `read(address, scratch)` — Read memory into a scratch (no allocations)
57
58
  - `write(address, scratch)` — Write a scratch to memory
58
59
  - Module map: `memory.modules['client.dll']`
@@ -83,10 +84,16 @@ void cs2.read(0x12345678n, array); // Fills array in-place
83
84
  const needle = 'deadbeef';
84
85
  // const needle = 'de**beef';
85
86
  // const needle = 'de????ef';
87
+ // Find first match
86
88
  const address = cs2.pattern(needle, 0x10000000n, 0x1000);
87
89
  if (address !== -1n) {
88
90
  console.log(`Found at 0x${address.toString(16)}`);
89
91
  }
92
+ // Find all matches
93
+ const allAddresses = cs2.pattern(needle, 0x10000000n, 0x1000, true);
94
+ for (const addr of allAddresses) {
95
+ console.log(`Found at 0x${addr.toString(16)}`);
96
+ }
90
97
  ```
91
98
 
92
99
  ## Example: Pointer Chains
@@ -103,11 +110,16 @@ const address = cs2.follow(0x10000000n, [0x10n, 0x20n]);
103
110
  const needle = Buffer.from([0x01, 0x02, 0x03]);
104
111
  // const needle = new Uint8Array([0x01, 0x02, 0x03]);
105
112
  // const needle = new Uint32Array([0x012345, 0x123456, 0x234567]);
106
- // …etc…
113
+ // Find first match
107
114
  const address = cs2.indexOf(needle, 0x10000000n, 0x1000);
108
115
  if (address !== -1n) {
109
116
  console.log(`Found at 0x${address.toString(16)}`);
110
117
  }
118
+ // Find all matches
119
+ const allAddresses = cs2.indexOf(needle, 0x10000000n, 0x1000, true);
120
+ for (const addr of allAddresses) {
121
+ console.log(`Found at 0x${addr.toString(16)}`);
122
+ }
111
123
  ```
112
124
 
113
125
  ## Example: Typed Arrays
@@ -117,7 +129,6 @@ if (address !== -1n) {
117
129
  const array = cs2.f32Array(0x12345678n, 4); // Float32Array of length 4
118
130
  // const array = cs2.u64Array(0x12345678n, 4);
119
131
  // const array = cs2.vector3Array(0x12345678n, 4);
120
- // …etc…
121
132
  cs2.i32Array(0x12345678n, new Int32Array([1, 2, 3, 4]));
122
133
  cs2.u64Array(0x12345678n, new BigUint64Array([1, 2, 3, 4]));
123
134
  cs2.vector3Array(0x12345678n, [{ x: 1, y: 2, z: 3 }]);
@@ -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,28 +32,13 @@ 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;
36
-
37
- if (ClientPtr === undefined) {
38
- throw new TypeError('ClientPtr must not be undefined.');
39
- }
40
-
41
- // Get client.dll…
42
35
  const client = cs2.modules['client.dll'];
43
36
 
44
37
  if (client === undefined) {
45
- throw new Error('client must not be undefined');
38
+ throw new TypeError('client must not be undefined.');
46
39
  }
47
40
 
48
- // Byte pattern for AddNametag
49
- const needle = '40555356488dac24????????4881ec????????488bda488bf14885c9';
50
-
51
- // Find a byte pattern in memory (supports wildcards: ** and ??)…
52
- const addNametag = cs2.pattern(needle, client.base, client.size);
53
-
54
- if (addNametag !== -1n) {
55
- console.log(`Found at 0x${addNametag.toString(16)}`); // Found at 0x7ffc8964e490
56
- }
41
+ const ClientPtr = client.base;
57
42
 
58
43
  // Create a cache for class name strings… 🫠…
59
44
  const Cache_Names = new Map<bigint, string>();
@@ -114,7 +99,7 @@ async function tick(ClientPtr: bigint) {
114
99
  if (Name === undefined) {
115
100
  const SchemaClassInfoDataPtr = cs2.u64(EntityClassInfoPtr + 0x30n);
116
101
  /* */ const NamePtr = cs2.u64(SchemaClassInfoDataPtr + 0x08n);
117
- /* */ Name = cs2.cString(NamePtr, 0x80).toString();
102
+ /* */ Name = cs2.string(NamePtr, 0x80);
118
103
 
119
104
  Cache_Names.set(EntityClassInfoPtr, Name);
120
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.34",
25
+ "version": "1.1.36",
26
26
  "main": "./index.ts",
27
27
  "keywords": [
28
28
  "bun",
package/structs/Memory.ts CHANGED
@@ -7,7 +7,10 @@ const {
7
7
  symbols: { CloseHandle, CreateToolhelp32Snapshot, GetLastError, Module32FirstW, Module32NextW, OpenProcess, Process32FirstW, Process32NextW, ReadProcessMemory, VirtualQueryEx, WriteProcessMemory },
8
8
  } = dlopen('kernel32.dll', {
9
9
  CloseHandle: { args: [FFIType.u64], returns: FFIType.bool },
10
+ CreateRemoteThread: { args: [FFIType.u64, FFIType.u64, FFIType.u64, FFIType.u64, FFIType.u64, FFIType.u32, FFIType.ptr], returns: FFIType.u64 },
10
11
  CreateToolhelp32Snapshot: { args: [FFIType.u32, FFIType.u32], returns: FFIType.u64 },
12
+ GetCurrentProcess: { args: [], returns: FFIType.ptr },
13
+ GetExitCodeThread: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
11
14
  GetLastError: { returns: FFIType.u32 },
12
15
  IsWow64Process2: { args: [FFIType.u64, FFIType.ptr, FFIType.ptr], returns: FFIType.bool },
13
16
  Module32FirstW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
@@ -16,8 +19,11 @@ const {
16
19
  Process32FirstW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
17
20
  Process32NextW: { args: [FFIType.u64, FFIType.ptr], returns: FFIType.bool },
18
21
  ReadProcessMemory: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64_fast, FFIType.ptr], returns: FFIType.bool },
22
+ VirtualAllocEx: { args: [FFIType.u64, FFIType.u64, FFIType.u64, FFIType.u32, FFIType.u32], returns: FFIType.u64 },
23
+ VirtualFreeEx: { args: [FFIType.u64, FFIType.u64, FFIType.u64, FFIType.u32], returns: FFIType.bool },
19
24
  VirtualProtectEx: { args: [FFIType.u64, FFIType.u64, FFIType.u64, FFIType.u32, FFIType.ptr], returns: FFIType.bool },
20
25
  VirtualQueryEx: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64], returns: FFIType.u64 },
26
+ WaitForSingleObject: { args: [FFIType.u64, FFIType.u32], returns: FFIType.u32 },
21
27
  WriteProcessMemory: { args: [FFIType.u64, FFIType.u64, FFIType.ptr, FFIType.u64_fast, FFIType.ptr], returns: FFIType.bool },
22
28
  });
23
29
 
@@ -28,6 +34,7 @@ const {
28
34
  *
29
35
  * Many scalar reads utilize `TypedArray` scratches to avoid a second FFI hop, such as calling `bun:ffi.read.*`.
30
36
  *
37
+ * @todo Add call method for calling functions in remote process.
31
38
  * @todo Add support for 32 or 64-bit processes using IsWow64Process2 (Windows 10+).
32
39
  * @todo When adding 32-bit support, several u64 will need changed to u64_fast.
33
40
  *
@@ -109,6 +116,8 @@ class Memory {
109
116
  throw new Error(`Process not found: ${identifier}…`);
110
117
  }
111
118
 
119
+ // public call(address: bigint, args: { type: FFIType, value: any }[] = [], timeout: number = 5_000): bigint {}
120
+
112
121
  /**
113
122
  * Memory protection flags for safe and unsafe regions.
114
123
  * Used to filter readable/writable memory areas.
@@ -124,7 +133,8 @@ class Memory {
124
133
  */
125
134
  private static readonly Patterns = {
126
135
  MatchAll: /(?:[0-9A-Fa-f]{2})+/g,
127
- Test: /^(?:\*{2}|[0-9A-Fa-f]{2}|\?{2})+$/,
136
+ Test: /^(?=.*[0-9A-Fa-f]{2})(?:\*{2}|\?{2}|[0-9A-Fa-f]{2})+$/,
137
+ // Test: /^(?:\*{2}|[0-9A-Fa-f]{2}|\?{2})+$/,
128
138
  };
129
139
 
130
140
  /**
@@ -170,9 +180,10 @@ class Memory {
170
180
  private readonly ScratchMemoryBasicInformation = Buffer.allocUnsafe(0x30 /* sizeof(MEMORY_BASIC_INFORMATION) */);
171
181
  private readonly ScratchModuleEntry32W = Buffer.allocUnsafe(0x438 /* sizeof(MODULEENTRY32W) */);
172
182
 
173
- private static TextDecoderUTF16 = new TextDecoder('utf-16');
174
183
  private static TextDecoderUTF8 = new TextDecoder('utf-8');
175
184
 
185
+ private static TextEncoderUTF8 = new TextEncoder('utf-8');
186
+
176
187
  private readonly hProcess: bigint;
177
188
  private readonly th32ProcessID: number;
178
189
 
@@ -189,43 +200,6 @@ class Memory {
189
200
  return this._modules;
190
201
  }
191
202
 
192
- /**
193
- * Returns memory regions in a given address range.
194
- * @param address Start address.
195
- * @param length Number of bytes to scan.
196
- * @returns Array of memory regions.
197
- */
198
- private regions(address: bigint, length: bigint | number): Region[] {
199
- const dwLength = 0x30; /* sizeof(MEMORY_BASIC_INFORMATION) */
200
- let lpAddress = BigInt(address); // prettier-ignore
201
- const lpBuffer = this.ScratchMemoryBasicInformation;
202
-
203
- const bVirtualQueryEx = !!VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength);
204
-
205
- if (!bVirtualQueryEx) {
206
- throw new Win32Error('VirtualQueryEx', GetLastError());
207
- }
208
-
209
- const end = lpAddress + BigInt(length);
210
- const result: Region[] = [];
211
-
212
- do {
213
- const baseAddress = lpBuffer.readBigUInt64LE();
214
- const protect = lpBuffer.readUInt32LE(36);
215
- const regionSize = lpBuffer.readBigUInt64LE(24);
216
- const state = lpBuffer.readUInt32LE(32);
217
- const type = lpBuffer.readUInt32LE(40);
218
-
219
- if ((protect & Memory.MemoryProtections.Safe) !== 0 && (protect & Memory.MemoryProtections.Unsafe) === 0 && state === 0x1000 /* MEM_COMMIT */) {
220
- result.push({ base: baseAddress, protect, size: regionSize, state, type });
221
- }
222
-
223
- lpAddress = baseAddress + regionSize;
224
- } while (lpAddress < end && !!VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength));
225
-
226
- return result;
227
- }
228
-
229
203
  /**
230
204
  * Closes the process handle.
231
205
  * @example
@@ -316,100 +290,6 @@ class Memory {
316
290
  return;
317
291
  }
318
292
 
319
- /**
320
- * Follows a pointer chain with offsets.
321
- * @param address Base address.
322
- * @param offsets Array of pointer offsets.
323
- * @returns Final address after following the chain, or -1n if any pointer is null.
324
- * @example
325
- * ```ts
326
- * const cs2 = new Memory('cs2.exe');
327
- * const myAddress = cs2.follow(0x10000000n, [0x10n, 0x20n]);
328
- * ```
329
- */
330
- public follow(address: bigint, offsets: readonly bigint[]): bigint {
331
- const last = offsets.length - 1;
332
-
333
- for (let i = 0; i < last; i++) {
334
- address = this.u64(address + offsets[i]);
335
-
336
- if (address === 0n) {
337
- return -1n;
338
- }
339
- }
340
-
341
- return address + (offsets[last] ?? 0n);
342
- }
343
-
344
- /**
345
- * Finds the address of a byte pattern in memory. `**` and `??` match any byte.
346
- * @param needle Hex string pattern to search for (e.g., 'deadbeed', 'dead**ef', 'dead??ef').
347
- * @param address Start address to search.
348
- * @param length Number of bytes to search.
349
- * @returns Address of the pattern if found, or -1n.
350
- * @example
351
- * ```ts
352
- * const cs2 = new Memory('cs2.exe');
353
- * const addr = cs2.pattern('dead**ef', 0x10000000n, 0x1000);
354
- * ```
355
- */
356
- public pattern(needle: string, address: bigint, length: number): bigint {
357
- const test = Memory.Patterns.Test.test(needle);
358
-
359
- if (!test) {
360
- return -1n;
361
- }
362
-
363
- const tokens = [...needle.matchAll(Memory.Patterns.MatchAll)]
364
- .map((match) => ({ buffer: Buffer.from(match[0], 'hex'), index: match.index >>> 1, length: match[0].length >>> 1 })) //
365
- .sort(({ length: a }, { length: b }) => b - a);
366
-
367
- if (tokens.length === 0) {
368
- return needle.length >>> 1 <= length ? address : -1n;
369
- }
370
-
371
- const anchor = tokens.shift()!;
372
-
373
- const haystack = this.buffer(address, length);
374
-
375
- const end = length - (needle.length >>> 1);
376
- let start = haystack.indexOf(anchor.buffer); // prettier-ignore
377
-
378
- if (start === -1) {
379
- return -1n;
380
- }
381
-
382
- outer: do {
383
- const base = start - anchor.index;
384
-
385
- if (base < 0) {
386
- continue;
387
- } else if (base > end) {
388
- return -1n;
389
- }
390
-
391
- for (const { buffer, index, length } of tokens) {
392
- const sourceEnd = base + index + length,
393
- sourceStart = base + index,
394
- target = buffer,
395
- targetEnd = length,
396
- targetStart = 0;
397
-
398
- const compare = haystack.compare(target, targetStart, targetEnd, sourceStart, sourceEnd);
399
-
400
- if (compare !== 0) {
401
- continue outer;
402
- }
403
- }
404
-
405
- return address + BigInt(base);
406
-
407
- // Finish…
408
- } while ((start = haystack.indexOf(anchor.buffer, start + 0x01)) !== -1);
409
-
410
- return -1n;
411
- }
412
-
413
293
  /**
414
294
  * Reads memory into a buffer.
415
295
  * @param address Address to read from.
@@ -540,7 +420,13 @@ class Memory {
540
420
  if (typeof lengthOrValue === 'number') {
541
421
  const scratch = new Uint8Array(lengthOrValue);
542
422
 
543
- this.read(address, scratch);
423
+ void this.read(address, scratch);
424
+
425
+ const indexOf = scratch.indexOf(0x00);
426
+
427
+ if (indexOf === -1) {
428
+ scratch[lengthOrValue - 1] = 0x00;
429
+ }
544
430
 
545
431
  return new CString(scratch.ptr);
546
432
  }
@@ -599,7 +485,7 @@ class Memory {
599
485
  const length = lengthOrValues;
600
486
  const scratch = new Float32Array(length);
601
487
 
602
- this.read(address, scratch);
488
+ void this.read(address, scratch);
603
489
 
604
490
  return scratch;
605
491
  }
@@ -658,7 +544,7 @@ class Memory {
658
544
  const length = lengthOrValues;
659
545
  const scratch = new Float64Array(length);
660
546
 
661
- this.read(address, scratch);
547
+ void this.read(address, scratch);
662
548
 
663
549
  return scratch;
664
550
  }
@@ -717,7 +603,7 @@ class Memory {
717
603
  const length = lengthOrValues;
718
604
  const scratch = new Int16Array(length);
719
605
 
720
- this.read(address, scratch);
606
+ void this.read(address, scratch);
721
607
 
722
608
  return scratch;
723
609
  }
@@ -776,7 +662,7 @@ class Memory {
776
662
  const length = lengthOrValues;
777
663
  const scratch = new Int32Array(length);
778
664
 
779
- this.read(address, scratch);
665
+ void this.read(address, scratch);
780
666
 
781
667
  return scratch;
782
668
  }
@@ -835,7 +721,7 @@ class Memory {
835
721
  const length = lengthOrValues;
836
722
  const scratch = new BigInt64Array(length);
837
723
 
838
- this.read(address, scratch);
724
+ void this.read(address, scratch);
839
725
 
840
726
  return scratch;
841
727
  }
@@ -894,7 +780,7 @@ class Memory {
894
780
  const length = lengthOrValues;
895
781
  const scratch = new Int8Array(length);
896
782
 
897
- this.read(address, scratch);
783
+ void this.read(address, scratch);
898
784
 
899
785
  return scratch;
900
786
  }
@@ -924,7 +810,7 @@ class Memory {
924
810
  if (values === undefined) {
925
811
  const scratch = new Float32Array(0x09);
926
812
 
927
- this.read(address, scratch);
813
+ void this.read(address, scratch);
928
814
 
929
815
  return scratch;
930
816
  }
@@ -956,7 +842,7 @@ class Memory {
956
842
  if (values === undefined) {
957
843
  const scratch = new Float32Array(0x0c);
958
844
 
959
- this.read(address, scratch);
845
+ void this.read(address, scratch);
960
846
 
961
847
  return scratch;
962
848
  }
@@ -988,7 +874,7 @@ class Memory {
988
874
  if (values === undefined) {
989
875
  const scratch = new Float32Array(0x10);
990
876
 
991
- this.read(address, scratch);
877
+ void this.read(address, scratch);
992
878
 
993
879
  return scratch;
994
880
  }
@@ -1024,7 +910,7 @@ class Memory {
1024
910
 
1025
911
  const scratch = new Uint32Array(size);
1026
912
 
1027
- this.read(elementsPtr, scratch);
913
+ void this.read(elementsPtr, scratch);
1028
914
 
1029
915
  return scratch;
1030
916
  }
@@ -1054,7 +940,7 @@ class Memory {
1054
940
  const Scratch8Float32Array = this.Scratch8Float32Array;
1055
941
 
1056
942
  if (value === undefined) {
1057
- this.read(address, Scratch8Float32Array);
943
+ void this.read(address, Scratch8Float32Array);
1058
944
 
1059
945
  const x = Scratch8Float32Array[0x00],
1060
946
  y = Scratch8Float32Array[0x01]; // prettier-ignore
@@ -1089,7 +975,7 @@ class Memory {
1089
975
  const length = lengthOrValues;
1090
976
  const scratch = new Float32Array(length * 2);
1091
977
 
1092
- this.read(address, scratch);
978
+ void this.read(address, scratch);
1093
979
 
1094
980
  const result = new Array<Vector2>(length);
1095
981
 
@@ -1152,7 +1038,7 @@ class Memory {
1152
1038
  const Scratch12Float32Array = this.Scratch12Float32Array;
1153
1039
 
1154
1040
  if (value === undefined) {
1155
- this.read(address, Scratch12Float32Array);
1041
+ void this.read(address, Scratch12Float32Array);
1156
1042
 
1157
1043
  const pitch = Scratch12Float32Array[0x00],
1158
1044
  roll = Scratch12Float32Array[0x02],
@@ -1189,7 +1075,7 @@ class Memory {
1189
1075
  const length = lengthOrValues;
1190
1076
  const scratch = new Float32Array(length * 0x03);
1191
1077
 
1192
- this.read(address, scratch);
1078
+ void this.read(address, scratch);
1193
1079
 
1194
1080
  const result = new Array<QAngle>(length);
1195
1081
 
@@ -1266,7 +1152,7 @@ class Memory {
1266
1152
  const Scratch16Float32Array = this.Scratch16Float32Array;
1267
1153
 
1268
1154
  if (value === undefined) {
1269
- this.read(address, Scratch16Float32Array);
1155
+ void this.read(address, Scratch16Float32Array);
1270
1156
 
1271
1157
  const w = Scratch16Float32Array[0x03],
1272
1158
  x = Scratch16Float32Array[0x00],
@@ -1305,7 +1191,7 @@ class Memory {
1305
1191
  const length = lengthOrValues;
1306
1192
  const scratch = new Float32Array(length * 0x04); // 4 * f32 per Quaternion
1307
1193
 
1308
- this.read(address, scratch);
1194
+ void this.read(address, scratch);
1309
1195
 
1310
1196
  const result = new Array<Quaternion>(length);
1311
1197
 
@@ -1384,7 +1270,7 @@ class Memory {
1384
1270
  const Scratch3 = this.Scratch3;
1385
1271
 
1386
1272
  if (value === undefined) {
1387
- this.read(address, Scratch3);
1273
+ void this.read(address, Scratch3);
1388
1274
 
1389
1275
  const r = Scratch3[0x00],
1390
1276
  g = Scratch3[0x01],
@@ -1446,7 +1332,7 @@ class Memory {
1446
1332
  const Scratch4 = this.Scratch4;
1447
1333
 
1448
1334
  if (value === undefined) {
1449
- this.read(address, Scratch4);
1335
+ void this.read(address, Scratch4);
1450
1336
 
1451
1337
  const r = Scratch4[0x00],
1452
1338
  g = Scratch4[0x01],
@@ -1492,6 +1378,43 @@ class Memory {
1492
1378
  return this;
1493
1379
  }
1494
1380
 
1381
+ /**
1382
+ * Reads or writes a UTF-8 string.
1383
+ * @param address Address to access.
1384
+ * @param lengthOrValue Length to read or string to write.
1385
+ * @returns The string at address, or this instance if writing.
1386
+ * @notice When writing, remember to null-terminate your string (e.g., 'hello\0').
1387
+ * @example
1388
+ * ```ts
1389
+ * const cs2 = new Memory('cs2.exe');
1390
+ * const myString = cs2.string(0x12345678n, 16);
1391
+ * cs2.string(0x12345678n, 'hello\0');
1392
+ * ```
1393
+ */
1394
+ public string(address: bigint, length: number): string;
1395
+ public string(address: bigint, value: string): this;
1396
+ public string(address: bigint, lengthOrValue: number | string): string | this {
1397
+ if (typeof lengthOrValue === 'number') {
1398
+ const scratch = new Uint8Array(lengthOrValue);
1399
+
1400
+ void this.read(address, scratch);
1401
+
1402
+ const indexOf = scratch.indexOf(0x00);
1403
+
1404
+ return Memory.TextDecoderUTF8.decode(
1405
+ scratch.subarray(0, indexOf !== -1 ? indexOf : lengthOrValue) //
1406
+ );
1407
+
1408
+ // return new CString(scratch.ptr).valueOf();
1409
+ }
1410
+
1411
+ const scratch = Memory.TextEncoderUTF8.encode(lengthOrValue);
1412
+
1413
+ this.write(address, scratch);
1414
+
1415
+ return this;
1416
+ }
1417
+
1495
1418
  /**
1496
1419
  * Reads or writes a 16-bit unsigned integer.
1497
1420
  * @param address Address to access.
@@ -1539,7 +1462,7 @@ class Memory {
1539
1462
  const length = lengthOrValues;
1540
1463
  const scratch = new Uint16Array(length);
1541
1464
 
1542
- this.read(address, scratch);
1465
+ void this.read(address, scratch);
1543
1466
 
1544
1467
  return scratch;
1545
1468
  }
@@ -1598,7 +1521,7 @@ class Memory {
1598
1521
  const length = lengthOrValues;
1599
1522
  const scratch = new Uint32Array(length);
1600
1523
 
1601
- this.read(address, scratch);
1524
+ void this.read(address, scratch);
1602
1525
 
1603
1526
  return scratch;
1604
1527
  }
@@ -1657,7 +1580,7 @@ class Memory {
1657
1580
  const length = lengthOrValues;
1658
1581
  const scratch = new BigUint64Array(length);
1659
1582
 
1660
- this.read(address, scratch);
1583
+ void this.read(address, scratch);
1661
1584
 
1662
1585
  return scratch;
1663
1586
  }
@@ -1716,7 +1639,7 @@ class Memory {
1716
1639
  const length = lengthOrValues;
1717
1640
  const scratch = new Uint8Array(length);
1718
1641
 
1719
- this.read(address, scratch);
1642
+ void this.read(address, scratch);
1720
1643
 
1721
1644
  return scratch;
1722
1645
  }
@@ -1854,7 +1777,7 @@ class Memory {
1854
1777
  const Scratch12Float32Array = this.Scratch12Float32Array;
1855
1778
 
1856
1779
  if (value === undefined) {
1857
- this.read(address, Scratch12Float32Array);
1780
+ void this.read(address, Scratch12Float32Array);
1858
1781
 
1859
1782
  const x = Scratch12Float32Array[0x00],
1860
1783
  y = Scratch12Float32Array[0x01],
@@ -1891,7 +1814,7 @@ class Memory {
1891
1814
  const length = lengthOrValues;
1892
1815
  const scratch = new Float32Array(length * 0x03);
1893
1816
 
1894
- this.read(address, scratch);
1817
+ void this.read(address, scratch);
1895
1818
 
1896
1819
  const result = new Array<Vector3>(length);
1897
1820
 
@@ -2002,30 +1925,190 @@ class Memory {
2002
1925
 
2003
1926
  // Public utility methods…
2004
1927
 
1928
+ /**
1929
+ * Follows a pointer chain with offsets.
1930
+ * @param address Base address.
1931
+ * @param offsets Array of pointer offsets.
1932
+ * @returns Final address after following the chain, or -1n if any pointer is null.
1933
+ * @example
1934
+ * ```ts
1935
+ * const cs2 = new Memory('cs2.exe');
1936
+ * const myAddress = cs2.follow(0x10000000n, [0x10n, 0x20n]);
1937
+ * ```
1938
+ */
1939
+ public follow(address: bigint, offsets: readonly bigint[]): bigint {
1940
+ const last = offsets.length - 1;
1941
+
1942
+ for (let i = 0; i < last; i++) {
1943
+ address = this.u64(address + offsets[i]);
1944
+
1945
+ if (address === 0n) {
1946
+ return -1n;
1947
+ }
1948
+ }
1949
+
1950
+ return address + (offsets[last] ?? 0n);
1951
+ }
1952
+
2005
1953
  /**
2006
1954
  * Finds the address of a buffer within a memory region.
2007
- * @param needle Buffer to search for.
1955
+ * @param needle Buffer or typed array to search for.
2008
1956
  * @param address Start address.
2009
1957
  * @param length Number of bytes to search.
2010
- * @returns Address of the buffer if found, or -1n.
1958
+ * @param all If true, returns all matches as an array. If false or omitted, returns the first match or -1n.
1959
+ * @returns Address of the buffer if found, or -1n. If all is true, returns an array of addresses.
2011
1960
  * @example
2012
1961
  * ```ts
2013
1962
  * const cs2 = new Memory('cs2.exe');
2014
- * const myAddress = cs2.indexOf(new Uint8Array([1,2,3]), 0x10000000n, 100);
1963
+ * const needle = Buffer.from('Hello world!');
1964
+ * // const needle = Buffer.from([0x01, 0x02, 0x03]);
1965
+ * // const needle = new Uint8Array([0x01, 0x02, 0x03]);
1966
+ * // const needle = new Float32Array([0x01, 0x02, 0x03]);
1967
+ * // Find first match
1968
+ * const address = cs2.indexOf(needle, 0x10000000n, 100);
1969
+ * // Find all matches
1970
+ * const allAddressess = cs2.indexOf(needle, 0x10000000n, 100, true);
2015
1971
  * ```
2016
1972
  */
2017
- public indexOf(needle: Scratch, address: bigint, length: number): bigint {
1973
+ public indexOf(needle: Scratch, address: bigint, length: number): bigint;
1974
+ public indexOf(needle: Scratch, address: bigint, length: number, all: false): bigint;
1975
+ public indexOf(needle: Scratch, address: bigint, length: number, all: true): bigint[];
1976
+ public indexOf(needle: Scratch, address: bigint, length: number, all: boolean = false): bigint | bigint[] {
2018
1977
  const haystack = Buffer.allocUnsafe(length);
2019
1978
 
2020
- this.read(address, haystack);
2021
-
2022
1979
  const needleBuffer = ArrayBuffer.isView(needle) //
2023
1980
  ? Buffer.from(needle.buffer, needle.byteOffset, needle.byteLength)
2024
1981
  : Buffer.from(needle);
2025
1982
 
2026
- const indexOf = haystack.indexOf(needleBuffer);
1983
+ void this.read(address, haystack);
1984
+
1985
+ if (!all) {
1986
+ const indexOf = haystack.indexOf(needleBuffer);
1987
+
1988
+ return indexOf !== -1 ? BigInt(indexOf) + address : -1n;
1989
+ }
1990
+
1991
+ const results: bigint[] = [];
1992
+
1993
+ let start = haystack.indexOf(needleBuffer);
1994
+
1995
+ if (start === -1) {
1996
+ return results;
1997
+ }
1998
+
1999
+ do {
2000
+ results.push(address + BigInt(start));
2001
+ } while ((start = haystack.indexOf(needleBuffer, start + 0x01)) !== -1);
2002
+
2003
+ return results;
2004
+ }
2005
+
2006
+ /**
2007
+ * Finds the address of a byte pattern in memory. `**` and `??` match any byte.
2008
+ * @param needle Hex string pattern to search for (e.g., 'deadbeed', 'dead**ef', 'dead??ef').
2009
+ * @param address Start address to search.
2010
+ * @param length Number of bytes to search.
2011
+ * @param all If true, returns all matches as an array. If false or omitted, returns the first match or -1n.
2012
+ * @returns Address of the pattern if found, or -1n. If all is true, returns an array of addresses.
2013
+ * @example
2014
+ * ```ts
2015
+ * const cs2 = new Memory('cs2.exe');
2016
+ * // Find first match
2017
+ * const address = cs2.pattern('dead**ef', 0x10000000n, 0x1000);
2018
+ * // Find all matches
2019
+ * const allAddresses = cs2.pattern('dead**ef', 0x10000000n, 0x1000, true);
2020
+ * ```
2021
+ */
2022
+ public pattern(needle: string, address: bigint, length: number): bigint;
2023
+ public pattern(needle: string, address: bigint, length: number, all: false): bigint;
2024
+ public pattern(needle: string, address: bigint, length: number, all: true): bigint[];
2025
+ public pattern(needle: string, address: bigint, length: number, all: boolean = false): bigint | bigint[] {
2026
+ const test = Memory.Patterns.Test.test(needle);
2027
+
2028
+ if (!test) {
2029
+ return !all ? -1n : [];
2030
+ }
2031
+
2032
+ // The RegExp test ensures that we have at least one token…
2033
+
2034
+ const tokens = [...needle.matchAll(Memory.Patterns.MatchAll)]
2035
+ .map((match) => ({ buffer: Buffer.from(match[0], 'hex'), index: match.index >>> 1, length: match[0].length >>> 1 })) //
2036
+ .sort(({ length: a }, { length: b }) => b - a);
2037
+
2038
+ const anchor = tokens.shift()!;
2039
+
2040
+ const haystack = this.buffer(address, length);
2041
+
2042
+ const end = length - (needle.length >>> 1);
2043
+ let start = haystack.indexOf(anchor.buffer); // prettier-ignore
2044
+
2045
+ if (start === -1) {
2046
+ return !all ? -1n : [];
2047
+ }
2048
+
2049
+ if (!all) {
2050
+ outer: do {
2051
+ const base = start - anchor.index;
2052
+
2053
+ if (base < 0) {
2054
+ continue;
2055
+ }
2056
+
2057
+ if (base > end) {
2058
+ return -1n;
2059
+ }
2060
+
2061
+ for (const { buffer, index, length } of tokens) {
2062
+ const sourceEnd = base + index + length,
2063
+ sourceStart = base + index,
2064
+ target = buffer,
2065
+ targetEnd = length,
2066
+ targetStart = 0; // prettier-ignore
2067
+
2068
+ const compare = haystack.compare(target, targetStart, targetEnd, sourceStart, sourceEnd);
2069
+
2070
+ if (compare !== 0) {
2071
+ continue outer;
2072
+ }
2073
+ }
2074
+
2075
+ return address + BigInt(base);
2076
+ } while ((start = haystack.indexOf(anchor.buffer, start + 0x01)) !== -1);
2077
+
2078
+ return -1n;
2079
+ }
2080
+
2081
+ const results: bigint[] = [];
2082
+
2083
+ outer: do {
2084
+ const base = start - anchor.index;
2085
+
2086
+ if (base < 0) {
2087
+ continue;
2088
+ }
2089
+
2090
+ if (base > end) {
2091
+ return results;
2092
+ }
2093
+
2094
+ for (const { buffer, index, length } of tokens) {
2095
+ const sourceEnd = base + index + length,
2096
+ sourceStart = base + index,
2097
+ target = buffer,
2098
+ targetEnd = length,
2099
+ targetStart = 0; // prettier-ignore
2100
+
2101
+ const compare = haystack.compare(target, targetStart, targetEnd, sourceStart, sourceEnd);
2102
+
2103
+ if (compare !== 0) {
2104
+ continue outer;
2105
+ }
2106
+ }
2107
+
2108
+ results.push(address + BigInt(base));
2109
+ } while ((start = haystack.indexOf(anchor.buffer, start + 0x01)) !== -1);
2027
2110
 
2028
- return indexOf !== -1 ? BigInt(indexOf) + address : -1n;
2111
+ return results;
2029
2112
  }
2030
2113
  }
2031
2114