bun-memory 1.1.28 → 1.1.30

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
@@ -11,6 +11,7 @@ Blazing fast, high-performance Windows process memory manipulation for Bun.
11
11
  - Attach to processes by name or PID
12
12
  - Efficient, allocation-free operations using user-provided buffers (scratches)
13
13
  - Module enumeration and pointer chain resolution
14
+ - Pattern search with wildcards (`**` and `??`)
14
15
  - Read and write all primitive types, arrays, buffers, and common structures
15
16
  - Typed helpers for vectors, matrices, colors, and more
16
17
 
@@ -27,7 +28,7 @@ bun add bun-memory
27
28
 
28
29
  ## Quick Start
29
30
 
30
- ❗ **Important**: [Example: Using Scratches (Recommended)](#example-using-scratches-recommended).
31
+ ❗ **Important**: [Example: Using Scratches (Recommended)](#example-using-scratches-recommended)
31
32
 
32
33
  ```ts
33
34
  import Memory from 'bun-memory';
@@ -61,26 +62,43 @@ See the code and type definitions for full details. All methods are documented w
61
62
  ## Example: Efficient Scratch Reuse
62
63
 
63
64
  ```ts
65
+ // Reuse buffers and arrays for fast, allocation-free memory operations
64
66
  const buffer = Buffer.allocUnsafe(256);
65
- cs2.read(0x12345678n, buffer); // Fills buffer in-place
67
+ void cs2.read(0x12345678n, buffer); // Fills buffer in-place
66
68
  // …use buffer…
67
69
  ```
68
70
 
69
71
  ```ts
72
+ // Typed arrays work the same way
70
73
  const array = new Float32Array(32);
71
- cs2.read(0x12345678n, array); // Fills array in-place
74
+ void cs2.read(0x12345678n, array); // Fills array in-place
72
75
  // …use buffer…
73
76
  ```
74
77
 
78
+ ## Example: Pattern Search
79
+
80
+ ```ts
81
+ // Find a byte pattern in memory (supports wildcards: ** and ??)
82
+ const needle = 'deadbeef';
83
+ // const needle = 'de**beef';
84
+ // const needle = 'de????ef';
85
+ const address = cs2.pattern(needle, 0x10000000n, 0x1000);
86
+ if (address !== -1n) {
87
+ console.log(`Found at 0x${address.toString(16)}`);
88
+ }
89
+ ```
90
+
75
91
  ## Example: Pointer Chains
76
92
 
77
93
  ```ts
94
+ // Follow a pointer chain to resolve nested addresses
78
95
  const address = cs2.follow(0x10000000n, [0x10n, 0x20n]);
79
96
  ```
80
97
 
81
98
  ## Example: Searching Memory
82
99
 
83
100
  ```ts
101
+ // Search for a buffer or array in memory
84
102
  const needle = Buffer.from([0x01, 0x02, 0x03]);
85
103
  // const needle = new Uint8Array([0x01, 0x02, 0x03]);
86
104
  // const needle = new Uint32Array([0x012345, 0x123456, 0x234567]);
@@ -94,6 +112,7 @@ if (address !== -1n) {
94
112
  ## Example: Typed Arrays
95
113
 
96
114
  ```ts
115
+ // Read or write arrays of numbers and structures
97
116
  const array = cs2.f32Array(0x12345678n, 4); // Float32Array of length 4
98
117
  // const array = cs2.u64Array(0x12345678n, 4);
99
118
  // const array = cs2.vector3Array(0x12345678n, 4);
@@ -105,9 +124,9 @@ cs2.vector3Array(0x12345678n, [{ x: 1, y: 2, z: 3 }]);
105
124
 
106
125
  ## Example: Using Scratches (Recommended)
107
126
 
108
- Scratches let you reuse buffers and typed arrays for repeated memory operations, avoiding unnecessary allocations and maximizing performance. This is the most efficient way to read or write large or frequent data.
109
-
110
127
  ```ts
128
+ // Scratches let you reuse buffers and arrays for repeated memory operations
129
+ // This avoids allocations and maximizes performance
111
130
  const array = new BigUint64Array(0xf000 / 0x08);
112
131
 
113
132
  while (true) {
@@ -132,7 +151,6 @@ while (true) {
132
151
 
133
152
  ## Notes
134
153
 
135
- - Pattern scanning is temporarily disabled.
136
154
  - Windows only. Bun runtime required.
137
155
 
138
156
  ---
@@ -35,14 +35,22 @@ console.log('Warming up…');
35
35
  for (let i = 0; i < 1e6; i++) {
36
36
  const GlobalVarsPtr = cs2.u64(ClientPtr + Client.Other.dwGlobalVars);
37
37
  /* */ const CurTime = cs2.f32(GlobalVarsPtr + 0x30n);
38
+
39
+ const lPlayerControllerPtr = cs2.u64(ClientPtr + Client.Other.dwLocalPlayerController);
40
+
41
+ const lPlayerPawnPtr = cs2.u64(ClientPtr + Client.Other.dwLocalPlayerPawn);
42
+ /* */ const lHealth = cs2.u32(lPlayerPawnPtr + Client.C_BaseEntity.m_iHealth);
43
+ /* */ const lTeamNum = cs2.u8(lPlayerPawnPtr + Client.C_BaseEntity.m_iTeamNum);
38
44
  }
39
45
 
40
46
  // Create caches and scratches to optimize performance…
41
- const Cache_Names = new Map<bigint, string>();
47
+ const BaseEntityPtrs = new Map<string, bigint[]>();
42
48
 
43
49
  const EntityChunkScratch = new BigUint64Array(0xf000 / 0x08);
44
50
  const EntityListScratch = new BigUint64Array(0x200 / 0x08);
45
51
 
52
+ const EntityClassInfoNames = new Map<bigint, string>();
53
+
46
54
  // Start the test…
47
55
  console.log('Starting the test…');
48
56
 
@@ -51,53 +59,64 @@ const performance1 = performance.now();
51
59
  const EntityListPtr = cs2.u64(ClientPtr + Client.Other.dwEntityList);
52
60
 
53
61
  for (let i = 0; i < 1e6; i++) {
54
- const EntityClassInfoNames = new Map<string, bigint[]>();
55
- const EntityClassInfoPtrs = new Map<bigint, bigint[]>();
62
+ try {
63
+ // Traverse the entity list and store it in `BaseEntityPtrs`…
64
+ cs2.read(EntityListPtr + 0x10n, EntityListScratch);
56
65
 
57
- // Traverse the entity list
58
- cs2.read(EntityListPtr + 0x10n, EntityListScratch);
66
+ // Traverse each of the potential 64 entity chunks
67
+ for (let i = 0; i < 0x40; i++) {
68
+ const EntityChunkPtr = EntityListScratch[i];
59
69
 
60
- for (let i = 0; i < 0x40; i++) {
61
- const EntityChunkPtr = EntityListScratch[i];
62
-
63
- if (EntityChunkPtr === 0n) {
64
- continue;
65
- }
70
+ if (EntityChunkPtr === 0n) {
71
+ continue;
72
+ }
66
73
 
67
- cs2.read(EntityChunkPtr, EntityChunkScratch);
74
+ cs2.read(EntityChunkPtr, EntityChunkScratch);
68
75
 
69
- for (let j = 0, l = 0; j < 0x200; j++, l += 0x0f) {
70
- const BaseEntityPtr = EntityChunkScratch[l];
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];
71
80
 
72
- if (BaseEntityPtr === 0n) {
73
- continue;
74
- }
81
+ if (BaseEntityPtr === 0n) {
82
+ continue;
83
+ }
75
84
 
76
- const EntityClassInfoPtr = EntityChunkScratch[l + 0x01];
85
+ const EntityClassInfoPtr = EntityChunkScratch[l + 0x01];
77
86
 
78
- let BaseEntityPtrs = EntityClassInfoPtrs.get(EntityClassInfoPtr);
87
+ let Name = EntityClassInfoNames.get(EntityClassInfoPtr);
79
88
 
80
- if (BaseEntityPtrs === undefined) {
81
- BaseEntityPtrs = [];
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();
82
93
 
83
- EntityClassInfoPtrs.set(EntityClassInfoPtr, BaseEntityPtrs);
84
- }
94
+ EntityClassInfoNames.set(EntityClassInfoPtr, Name);
95
+ }
85
96
 
86
- BaseEntityPtrs.push(BaseEntityPtr);
87
- }
97
+ let BaseEntityPtrs_ = BaseEntityPtrs.get(Name);
88
98
 
89
- for (const [EntityClassInfoPtr, BaseEntityPtrs] of EntityClassInfoPtrs) {
90
- let Name = Cache_Names.get(EntityClassInfoPtr);
99
+ if (BaseEntityPtrs_ === undefined) {
100
+ BaseEntityPtrs_ = [];
91
101
 
92
- if (Name === undefined) {
93
- const SchemaClassInfoDataPtr = cs2.u64(EntityClassInfoPtr + 0x30n);
94
- /* */ const NamePtr = cs2.u64(SchemaClassInfoDataPtr + 0x08n);
95
- /* */ Name = cs2.cString(NamePtr, 0x2a).toString();
102
+ BaseEntityPtrs.set(Name, BaseEntityPtrs_);
103
+ }
96
104
 
97
- Cache_Names.set(EntityClassInfoPtr, Name);
105
+ BaseEntityPtrs_.push(BaseEntityPtr);
98
106
  }
107
+ }
99
108
 
100
- EntityClassInfoNames.set(Name, BaseEntityPtrs);
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;
101
120
  }
102
121
  }
103
122
  }
@@ -38,6 +38,23 @@ if (ClientPtr === undefined) {
38
38
  throw new TypeError('ClientPtr must not be undefined.');
39
39
  }
40
40
 
41
+ // Get client.dll…
42
+ const client = cs2.modules['client.dll'];
43
+
44
+ if (client === undefined) {
45
+ throw new Error('client must not be undefined…');
46
+ }
47
+
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
+ }
57
+
41
58
  // Create a cache for class name strings… 🫠…
42
59
  const Cache_Names = new Map<bigint, string>();
43
60
 
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.28",
25
+ "version": "1.1.30",
26
26
  "main": "./index.ts",
27
27
  "keywords": [
28
28
  "bun",
@@ -182,7 +182,7 @@ declare global {
182
182
  /**
183
183
  * Installs the `ptr` property on all supported binary view prototypes.
184
184
  *
185
- * The property is non-enumerable and configurable. The getter calls `ptr(this)`.
185
+ * The property is non-enumerable and non-configurable. The getter calls `ptr(this)`.
186
186
  */
187
187
  const constructors = [ArrayBuffer, BigInt64Array, BigUint64Array, Buffer, DataView, Float32Array, Float64Array, Int16Array, Int32Array, Int8Array, SharedArrayBuffer, Uint16Array, Uint32Array, Uint8Array, Uint8ClampedArray] as const;
188
188
 
package/structs/Memory.ts CHANGED
@@ -109,11 +109,24 @@ class Memory {
109
109
  throw new Error(`Process not found: ${identifier}…`);
110
110
  }
111
111
 
112
+ /**
113
+ * Memory protection flags for safe and unsafe regions.
114
+ * Used to filter readable/writable memory areas.
115
+ */
112
116
  private static readonly MemoryProtections = {
113
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 */,
114
118
  Unsafe: 0x100 /* PAGE_GUARD */ | 0x01 /* PAGE_NOACCESS */,
115
119
  };
116
120
 
121
+ /**
122
+ * Regex patterns for matching hex strings and wildcards in memory scans.
123
+ * Used by the pattern method.
124
+ */
125
+ private static readonly Patterns = {
126
+ MatchAll: /(?:[0-9A-Fa-f]{2})+/g,
127
+ Test: /^(?:\*{2}|[0-9A-Fa-f]{2}|\?{2})+$/,
128
+ };
129
+
117
130
  /**
118
131
  * Map of loaded modules in the process, keyed by module name.
119
132
  * @example
@@ -124,6 +137,10 @@ class Memory {
124
137
  */
125
138
  private _modules: { [key: string]: Module };
126
139
 
140
+ /**
141
+ * Scratch buffers and typed views for temporary FFI reads/writes.
142
+ * Used internally for efficient memory access and conversions.
143
+ */
127
144
  private readonly Scratch1 = new Uint8Array(0x01);
128
145
  private readonly Scratch1Int8Array = new Int8Array(this.Scratch1.buffer, this.Scratch1.byteOffset, 0x01);
129
146
 
@@ -324,6 +341,75 @@ class Memory {
324
341
  return address + (offsets[last] ?? 0n);
325
342
  }
326
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
+
327
413
  /**
328
414
  * Reads memory into a buffer.
329
415
  * @param address Address to read from.