bun-memory 1.1.27 → 1.1.29

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,6 +28,8 @@ bun add bun-memory
27
28
 
28
29
  ## Quick Start
29
30
 
31
+ ❗ **Important**: [Example: Using Scratches (Recommended)](#example-using-scratches-recommended)
32
+
30
33
  ```ts
31
34
  import Memory from 'bun-memory';
32
35
 
@@ -59,26 +62,43 @@ See the code and type definitions for full details. All methods are documented w
59
62
  ## Example: Efficient Scratch Reuse
60
63
 
61
64
  ```ts
65
+ // Reuse buffers and arrays for fast, allocation-free memory operations
62
66
  const buffer = Buffer.allocUnsafe(256);
63
- cs2.read(0x12345678n, buffer); // Fills buffer in-place
67
+ void cs2.read(0x12345678n, buffer); // Fills buffer in-place
64
68
  // …use buffer…
65
69
  ```
66
70
 
67
71
  ```ts
72
+ // Typed arrays work the same way
68
73
  const array = new Float32Array(32);
69
- cs2.read(0x12345678n, array); // Fills array in-place
74
+ void cs2.read(0x12345678n, array); // Fills array in-place
70
75
  // …use buffer…
71
76
  ```
72
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
+
73
91
  ## Example: Pointer Chains
74
92
 
75
93
  ```ts
94
+ // Follow a pointer chain to resolve nested addresses
76
95
  const address = cs2.follow(0x10000000n, [0x10n, 0x20n]);
77
96
  ```
78
97
 
79
98
  ## Example: Searching Memory
80
99
 
81
100
  ```ts
101
+ // Search for a buffer or array in memory
82
102
  const needle = Buffer.from([0x01, 0x02, 0x03]);
83
103
  // const needle = new Uint8Array([0x01, 0x02, 0x03]);
84
104
  // const needle = new Uint32Array([0x012345, 0x123456, 0x234567]);
@@ -92,6 +112,7 @@ if (address !== -1n) {
92
112
  ## Example: Typed Arrays
93
113
 
94
114
  ```ts
115
+ // Read or write arrays of numbers and structures
95
116
  const array = cs2.f32Array(0x12345678n, 4); // Float32Array of length 4
96
117
  // const array = cs2.u64Array(0x12345678n, 4);
97
118
  // const array = cs2.vector3Array(0x12345678n, 4);
@@ -103,9 +124,9 @@ cs2.vector3Array(0x12345678n, [{ x: 1, y: 2, z: 3 }]);
103
124
 
104
125
  ## Example: Using Scratches (Recommended)
105
126
 
106
- 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.
107
-
108
127
  ```ts
128
+ // Scratches let you reuse buffers and arrays for repeated memory operations
129
+ // This avoids allocations and maximizes performance
109
130
  const array = new BigUint64Array(0xf000 / 0x08);
110
131
 
111
132
  while (true) {
@@ -121,7 +142,7 @@ const buffer = Buffer.allocUnsafe(256);
121
142
  const array = new Uint64Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 8);
122
143
 
123
144
  while (true) {
124
- cs2.read(0x10000000n, buffer); // Updates array & buffer without allocations
145
+ cs2.read(0x10000000n, buffer); // Updates both array & buffer without allocations
125
146
  for (const element of array) {
126
147
  // …use element…
127
148
  }
@@ -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
  }
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.27",
25
+ "version": "1.1.29",
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.