bun-memory 1.1.13 → 1.1.15

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
@@ -1,6 +1,7 @@
1
1
  # bun-memory
2
2
 
3
- High-performance Windows process memory utilities for [Bun](https://bun.sh) using `bun:ffi` and Win32 APIs.
3
+ High-performance Windows process memory utilities for [Bun](https://bun.sh) using `bun:ffi` and
4
+ Win32 APIs.
4
5
 
5
6
  ## Features
6
7
 
@@ -32,40 +33,59 @@ See [example/trigger-bot.ts](example/trigger-bot.ts) for a real-world example fo
32
33
  import Memory from 'bun-memory';
33
34
 
34
35
  // Attach to process by name…
35
- const memory = new Memory('notepad.exe');
36
+ const memory = new Memory('cs2.exe');
36
37
  // …or PID…
37
38
  const memory = new Memory(1_234);
38
39
 
39
40
  // Access loaded modules…
40
41
  const modules = memory.modules;
41
- const mainModule = modules['notepad.exe'];
42
- console.log(`Base address: 0x${mainModule.modBaseAddr.toString(16)}`);
43
- console.log(`Size: ${mainModule.modBaseSize} bytes`);
42
+
43
+ const client = modules['client.dll'];
44
+
45
+ console.log(`Base address: 0x${client.base.toString(16)}`);
46
+ console.log(`Size: ${client.size} bytes`);
44
47
 
45
48
  // Read a 32-bit integer…
46
- const value = memory.i32(0x12345678n);
49
+ const value = memory.i32(client.base + 0x12345678n);
47
50
 
48
51
  // Write a float…
49
- memory.f32(0x12345678n, 3.14159);
52
+ memory.f32(client.base + 0x12345678n, 3.14159);
50
53
 
51
54
  // Clean up…
52
55
  memory.close();
53
56
  ```
54
57
 
55
- ### Pattern Scanning
56
-
57
- Pattern scanning is temporarily disabled but will return shortly.
58
-
59
- ```ts
60
- const offset = memory.findPattern('aa??bbccdd??ff', mainModule.modBaseAddr, mainModule.modBaseSize);
61
- const value = memory.bool(offset + 0x1234n);
62
- memory.close();
63
- ```
64
-
65
- ### Efficient Buffer Reads
66
-
67
- There are many ways to use `scratch`es. Scratches are great for avoiding allocation costs by reusing a
68
- preexisting array, buffer, string, etc.
58
+ ### API — Typed Reads / Writes
59
+
60
+ A `Memory` instance exposes typed helpers for reading and writing process memory. Pairs indicate
61
+ scalar and array variants; entries without a pair are scalar-only or array-only.
62
+
63
+ - bool
64
+ - cString
65
+ - f32 / f32Array
66
+ - f64 / f64Array
67
+ - i16 / i16Array
68
+ - i32 / i32Array
69
+ - i64 / i64Array
70
+ - i8 / i8Array
71
+ - matrix3x3
72
+ - matrix3x4
73
+ - matrix4x4
74
+ - networkUtlVector
75
+ - qAngle / qAngleArray
76
+ - quaternion / quaternionArray
77
+ - u16 / u16Array
78
+ - u32 / u32Array
79
+ - u64 / u64Array
80
+ - u8 / u8Array
81
+ - vector2 / vector2Array
82
+ - vector3 / vector3Array
83
+ - vector4 / vector4Array
84
+
85
+ ### Efficient Reads / Writes Using Scratches
86
+
87
+ There are many ways to use `scratch`es. Scratches are great for avoiding allocation costs by reusing
88
+ a preexisting array, buffer, string, etc.
69
89
 
70
90
  ```ts
71
91
  const handles = new Uint32Array(0x100);
@@ -110,6 +130,21 @@ const scratch = Buffer.allocUnsafe(0x100);
110
130
  memory.read(myAddress, scratch);
111
131
  ```
112
132
 
133
+ ```ts
134
+ const scratch = new Uint32Array(0x10);
135
+ memory.read(myAddress, scratch);
136
+ ```
137
+
138
+ ### Pattern Scanning
139
+
140
+ Pattern scanning is temporarily disabled but will return shortly.
141
+
142
+ ```ts
143
+ const offset = memory.findPattern('aa??bbccdd??ff', mainModule.modBaseAddr, mainModule.modBaseSize);
144
+ const value = memory.bool(offset + 0x1234n);
145
+ memory.close();
146
+ ```
147
+
113
148
  ## Notes
114
149
 
115
150
  - Only works with Bun and Windows.
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.13",
25
+ "version": "1.1.15",
26
26
  "main": "./index.ts",
27
27
  "keywords": [
28
28
  "bun",
package/structs/Memory.ts CHANGED
@@ -1,10 +1,6 @@
1
- // TODO: Reintroduce findPattern(…)…
2
- // TODO: Reintroduce indexOf(…)…
3
- // TODO: String methods…
4
-
5
1
  import { CString, FFIType, dlopen, read } from 'bun:ffi';
6
2
 
7
- import type { Module, NetworkUtlVector, Quaternion, Region, Scratch, Vector2, Vector3 } from '../types/Memory';
3
+ import type { Module, NetworkUtlVector, QAngle, Quaternion, Region, Scratch, Vector2, Vector3, Vector4 } from '../types/Memory';
8
4
  import Win32Error from './Win32Error';
9
5
 
10
6
  const { f32, f64, i16, i32, i64, i8, u16, u32, u64, u8 } = read;
@@ -33,6 +29,8 @@ const { symbols: Kernel32 } = dlopen('kernel32.dll', {
33
29
  * This class allows reading from and writing to memory addresses in external processes,
34
30
  * supporting various data types including primitives, arrays, and custom structures like vectors and quaternions.
35
31
  *
32
+ * @todo Reimplement `findPattern(…)`.
33
+ *
36
34
  * @example
37
35
  * ```typescript
38
36
  * // Connect to a process by name
@@ -168,7 +166,16 @@ class Memory {
168
166
  private readonly ScratchMemoryBasicInformation = Buffer.allocUnsafe(0x30 /* sizeof(MEMORY_BASIC_INFORMATION) */);
169
167
  private readonly ScratchModuleEntry32W = Buffer.allocUnsafe(0x438 /* sizeof(MODULEENTRY32W) */);
170
168
 
169
+ /**
170
+ * Reusable UTF-16 decoder for interpreting raw process memory as UTF-16 text.
171
+ * Kept as a singleton to avoid repeated allocations in hot paths.
172
+ */
171
173
  private static readonly TextDecoderUTF16 = new TextDecoder('utf-16');
174
+
175
+ /**
176
+ * Reusable UTF-8 decoder for interpreting raw process memory as UTF-8 text.
177
+ * Kept as a singleton to avoid repeated allocations in hot paths.
178
+ */
172
179
  private static readonly TextDecoderUTF8 = new TextDecoder('utf-8');
173
180
 
174
181
  /**
@@ -314,7 +321,7 @@ class Memory {
314
321
  return;
315
322
  }
316
323
 
317
- // Public utility methods
324
+ // Public read / write methods
318
325
 
319
326
  /**
320
327
  * Closes the handle to the target process and releases resources.
@@ -933,6 +940,26 @@ class Memory {
933
940
  return this;
934
941
  }
935
942
 
943
+ public matrix3x4(address: bigint): Float32Array;
944
+ public matrix3x4(address: bigint, values: Float32Array): this;
945
+ public matrix3x4(address: bigint, values?: Float32Array): Float32Array | this {
946
+ if (values === undefined) {
947
+ const scratch = new Float32Array(0x0c);
948
+
949
+ this.read(address, scratch);
950
+
951
+ return scratch;
952
+ }
953
+
954
+ if (values.length !== 0x0c) {
955
+ throw new RangeError('values.length must be 12.');
956
+ }
957
+
958
+ this.write(address, values);
959
+
960
+ return this;
961
+ }
962
+
936
963
  /**
937
964
  * Reads a 4×4 matrix from memory or writes a 4×4 matrix to memory.
938
965
  *
@@ -1029,6 +1056,103 @@ class Memory {
1029
1056
  return this;
1030
1057
  }
1031
1058
 
1059
+ /**
1060
+ * Reads a set of Euler angles from memory or writes a set of Euler angles to memory.
1061
+ *
1062
+ * Angles are stored as three 32-bit floats in the order **pitch, yaw, roll**.
1063
+ *
1064
+ * @param address - Memory address to read from or write to
1065
+ * @param value - Optional `QAngle` to write. If omitted, performs a read operation
1066
+ * @returns The `QAngle` value when reading, or this `Memory` instance when writing
1067
+ *
1068
+ * @example
1069
+ * ```typescript
1070
+ * // Read current view angles
1071
+ * const view = memory.qAngle(0x12345678n);
1072
+ *
1073
+ * // Write new view angles (e.g., level the roll)
1074
+ * memory.qAngle(0x12345678n, { ...view, roll: 0 });
1075
+ * ```
1076
+ */
1077
+
1078
+ public qAngle(address: bigint): QAngle;
1079
+ public qAngle(address: bigint, value: QAngle): this;
1080
+ public qAngle(address: bigint, value?: QAngle): QAngle | this {
1081
+ if (value === undefined) {
1082
+ this.read(address, this.Scratch12);
1083
+
1084
+ const pitch = f32(this.Scratch12.ptr);
1085
+ const roll = f32(this.Scratch12.ptr, 0x08);
1086
+ const yaw = f32(this.Scratch12.ptr, 0x04);
1087
+
1088
+ return { pitch, roll, yaw };
1089
+ }
1090
+
1091
+ this.Scratch12Buffer.writeFloatLE(value.pitch);
1092
+ this.Scratch12Buffer.writeFloatLE(value.roll, 0x08);
1093
+ this.Scratch12Buffer.writeFloatLE(value.yaw, 0x04);
1094
+
1095
+ this.write(address, this.Scratch12);
1096
+
1097
+ return this;
1098
+ }
1099
+
1100
+ /**
1101
+ * Reads an array of Euler angles from memory or writes an array of Euler angles to memory.
1102
+ *
1103
+ * Each element is three 32-bit floats in the order **pitch, yaw, roll**.
1104
+ *
1105
+ * @param address - Memory address to read from or write to
1106
+ * @param lengthOrValues - Length of array to read, or array of `QAngle` values to write
1107
+ * @returns `QAngle[]` when reading, or this `Memory` instance when writing
1108
+ *
1109
+ * @example
1110
+ * ```typescript
1111
+ * // Read bone aim offsets
1112
+ * const bones = memory.qAngleArray(0x12345678n, 64);
1113
+ *
1114
+ * // Write new bone angles (e.g., reset all roll to zero)
1115
+ * bones.forEach(b => (b.roll = 0));
1116
+ * memory.qAngleArray(0x12345678n, bones);
1117
+ * ```
1118
+ */
1119
+ public qAngleArray(address: bigint, length: number): QAngle[];
1120
+ public qAngleArray(address: bigint, values: QAngle[]): this;
1121
+ public qAngleArray(address: bigint, lengthOrValues: QAngle[] | number): QAngle[] | this {
1122
+ if (typeof lengthOrValues === 'number') {
1123
+ const length = lengthOrValues;
1124
+ const scratch = new Float32Array(length * 0x03);
1125
+
1126
+ this.read(address, scratch);
1127
+
1128
+ const result = new Array<QAngle>(length);
1129
+
1130
+ for (let i = 0, j = 0; i < length; i++, j += 0x03) {
1131
+ const pitch = scratch[j];
1132
+ const yaw = scratch[j + 0x01];
1133
+ const roll = scratch[j + 0x02];
1134
+ result[i] = { pitch, yaw, roll };
1135
+ }
1136
+
1137
+ return result;
1138
+ }
1139
+
1140
+ const values = lengthOrValues;
1141
+ const scratch = new Float32Array(values.length * 0x03);
1142
+
1143
+ for (let i = 0, j = 0; i < values.length; i++, j += 0x03) {
1144
+ const qAngle = values[i];
1145
+
1146
+ scratch[j] = qAngle.pitch;
1147
+ scratch[j + 0x02] = qAngle.roll;
1148
+ scratch[j + 0x01] = qAngle.yaw;
1149
+ }
1150
+
1151
+ this.write(address, scratch);
1152
+
1153
+ return this;
1154
+ }
1155
+
1032
1156
  /**
1033
1157
  * Reads a quaternion (4D rotation) from memory or writes a quaternion to memory.
1034
1158
  * Quaternions are stored as four 32-bit floats: x, y, z, w.
@@ -1604,6 +1728,119 @@ class Memory {
1604
1728
 
1605
1729
  return this;
1606
1730
  }
1731
+
1732
+ /**
1733
+ * Reads a 4D vector from memory or writes a 4D vector to memory.
1734
+ *
1735
+ * Uses the same 16-byte layout as {@link Memory.quaternion}: four 32-bit floats
1736
+ * stored in the order **x, y, z, w**. Returned objects are shaped as `{ w, x, y, z }`.
1737
+ *
1738
+ * @param address - Memory address to read from or write to
1739
+ * @param value - Optional `Vector4` to write. If omitted, performs a read operation
1740
+ * @returns The `Vector4` value when reading, or this `Memory` instance when writing
1741
+ *
1742
+ * @example
1743
+ * ```typescript
1744
+ * // Read directional data in projective space
1745
+ * const value = memory.vector4(0x12345678n);
1746
+ *
1747
+ * // Write a vector4 value (e.g., identity quaternion)
1748
+ * memory.vector4(0x12345678n, { x: 0, y: 0, z: 0, w: 1 });
1749
+ * ```
1750
+ */
1751
+ public vector4(address: bigint): Vector4;
1752
+ public vector4(address: bigint, value: Vector4): this;
1753
+ public vector4(address: bigint, value?: Vector4): Vector4 | this {
1754
+ // TypeScript is funny sometimes, isn't it?… 🫠…
1755
+ if (value === undefined) {
1756
+ return this.quaternion(address);
1757
+ }
1758
+
1759
+ return this.quaternion(address, value);
1760
+ }
1761
+
1762
+ /**
1763
+ * Reads an array of 4D vectors from memory or writes an array of 4D vectors to memory.
1764
+ *
1765
+ * Each element uses the same 16-byte layout as {@link Memory.quaternionArray}:
1766
+ * four 32-bit floats stored in the order **x, y, z, w**.
1767
+ *
1768
+ * @param address - Memory address to read from or write to
1769
+ * @param lengthOrValues - Length of array to read, or array of `Vector4` values to write
1770
+ * @returns `Vector4[]` when reading, or this `Memory` instance when writing
1771
+ *
1772
+ * @example
1773
+ * ```typescript
1774
+ * // Read per-vertex tangent vectors (xyzw)
1775
+ * const tangents = memory.vector4Array(0x12345678n, 1024);
1776
+ *
1777
+ * // Write tangent vectors (eg. normalize w to 1.0)
1778
+ * tangents.forEach(v => (v.w = 1.0));
1779
+ * memory.vector4Array(0x12345678n, tangents);
1780
+ * ```
1781
+ */
1782
+ public vector4Array(address: bigint, length: number): Vector4[];
1783
+ public vector4Array(address: bigint, values: Vector4[]): this;
1784
+ public vector4Array(address: bigint, lengthOrValues: Vector4[] | number): Vector4[] | this {
1785
+ // TypeScript is funny sometimes, isn't it?… 🫠…
1786
+ if (typeof lengthOrValues === 'number') {
1787
+ return this.quaternionArray(address, lengthOrValues);
1788
+ }
1789
+
1790
+ return this.quaternionArray(address, lengthOrValues);
1791
+ }
1792
+
1793
+ // Public utility methods…
1794
+
1795
+ /**
1796
+ * Searches a memory range for a byte sequence and returns the absolute address of the first match.
1797
+ *
1798
+ * This method reads `length` bytes starting at `address` into a temporary buffer and performs a
1799
+ * subsequence search. No region or protection checking is performed; ensure the range is readable
1800
+ * before calling.
1801
+ *
1802
+ * The `needle` accepts any Scratch-compatible buffer or view (`Buffer`, `TypedArray`, or `DataView`).
1803
+ * Bytes are matched exactly as laid out in memory. :contentReference[oaicite:0]{index=0}
1804
+ *
1805
+ * @param needle - Byte sequence to search for (Scratch-compatible buffer or view)
1806
+ * @param address - Base memory address to begin searching (inclusive)
1807
+ * @param length - Number of bytes to scan
1808
+ * @returns Absolute address (`bigint`) of the first match, or `-1n` when not found
1809
+ *
1810
+ * @example
1811
+ * ```typescript
1812
+ * // Search a loaded module for a byte sequence
1813
+ * const client = memory.modules['client.dll'];
1814
+ *
1815
+ * const needle = Buffer.from([0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00]);
1816
+ * const matchAddress = memory.indexOf(needle, client.base, client.size);
1817
+ * ```
1818
+ *
1819
+ * @example
1820
+ * ```typescript
1821
+ * // Search using a Uint32Array needle (bytes are matched exactly as laid out in memory)
1822
+ * const needle = new Uint32Array([0xDEADBEEF, 0x11223344]);
1823
+ * const matchAddress = memory.indexOf(needle, client.base, client.size);
1824
+ * ```
1825
+ */
1826
+
1827
+ public indexOf(needle: Scratch, address: bigint, length: number): bigint {
1828
+ const haystackUint8Array = new Uint8Array(length);
1829
+
1830
+ this.read(address, haystackUint8Array);
1831
+
1832
+ const haystackBuffer = Buffer.from(haystackUint8Array.buffer, haystackUint8Array.byteOffset, haystackUint8Array.byteLength);
1833
+
1834
+ const needleUint8Array = ArrayBuffer.isView(needle) //
1835
+ ? new Uint8Array(needle.buffer, needle.byteOffset, needle.byteLength)
1836
+ : new Uint8Array(needle);
1837
+
1838
+ const needleBuffer = Buffer.from(needleUint8Array.buffer, needleUint8Array.byteOffset, needleUint8Array.byteLength);
1839
+
1840
+ const indexOf = haystackBuffer.indexOf(needleBuffer);
1841
+
1842
+ return indexOf !== -1 ? BigInt(indexOf) + address : -1n;
1843
+ }
1607
1844
  }
1608
1845
 
1609
1846
  export default Memory;
package/types/Memory.ts CHANGED
@@ -319,6 +319,68 @@ export type Quaternion = {
319
319
  z: number;
320
320
  };
321
321
 
322
+ /**
323
+ * Represents an orientation using Euler angles.
324
+ *
325
+ * `QAngle` stores three 32-bit floating-point angles that describe rotation
326
+ * in degrees around the principal axes: `pitch` (X), `yaw` (Y), and `roll` (Z).
327
+ * This format is common in Source-engine-style telemetry and many gameplay
328
+ * camera systems. For quaternions, see {@link Quaternion}.
329
+ *
330
+ * Typical uses include:
331
+ * - Camera/view rotations
332
+ * - Bone/attachment orientations
333
+ * - Aim and recoil calculations
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * // Read and tweak view angles
338
+ * const view: QAngle = memory.qAngle(0x12345678n);
339
+ * const leveled: QAngle = { pitch: 0, yaw: view.yaw, roll: 0 };
340
+ * memory.qAngle(0x12345678n, leveled);
341
+ * ```
342
+ */
343
+ export interface QAngle {
344
+ /**
345
+ * Rotation around the X axis, in degrees.
346
+ *
347
+ * Positive values pitch the nose downward in many right-handed game
348
+ * coordinate systems; verify the target engine’s convention before writing.
349
+ *
350
+ * @example
351
+ * ```typescript
352
+ * const aimDown: QAngle = { pitch: 10, yaw: 0, roll: 0 };
353
+ * ```
354
+ */
355
+ pitch: number;
356
+
357
+ /**
358
+ * Rotation around the Z axis, in degrees.
359
+ *
360
+ * Often used for banking/tilt effects. Some engines clamp or ignore roll for
361
+ * first-person cameras.
362
+ *
363
+ * @example
364
+ * ```typescript
365
+ * const slightBank: QAngle = { pitch: 0, yaw: 0, roll: 5 };
366
+ * ```
367
+ */
368
+ roll: number;
369
+
370
+ /**
371
+ * Rotation around the Y axis, in degrees.
372
+ *
373
+ * Positive values typically turn to the right (clockwise when viewed from
374
+ * above) in right-handed systems.
375
+ *
376
+ * @example
377
+ * ```typescript
378
+ * const turnRight: QAngle = { pitch: 0, yaw: 90, roll: 0 };
379
+ * ```
380
+ */
381
+ yaw: number;
382
+ }
383
+
322
384
  /**
323
385
  * Represents a memory region with its properties and protection flags.
324
386
  *
@@ -791,3 +853,32 @@ export type Vector3 = {
791
853
  */
792
854
  z: number;
793
855
  };
856
+
857
+ /**
858
+ * Represents a 4D vector with w, x, y, z components.
859
+ *
860
+ * Common use cases:
861
+ * - Homogeneous coordinates (w ≠ 0)
862
+ * - Colors with alpha (x = R, y = G, z = B, w = A)
863
+ * - Packed attributes and shader parameters
864
+ * - As a quaternion carrier when interoperating with APIs expecting {x,y,z,w}
865
+ *
866
+ * Memory layout used by {@link Memory.vector4} is four 32-bit floats stored in
867
+ * the order **x, y, z, w**, while this type exposes `{ w, x, y, z }` for
868
+ * consistency with {@link Quaternion}.
869
+ *
870
+ * @example
871
+ * ```typescript
872
+ * // RGBA color
873
+ * const red: Vector4 = { x: 255, y: 0, z: 0, w: 255 };
874
+ *
875
+ * // Homogeneous point (x, y, z, w)
876
+ * const p: Vector4 = { x: 10, y: 20, z: 30, w: 1 };
877
+ * ```
878
+ */
879
+ export type Vector4 = {
880
+ w: number;
881
+ x: number;
882
+ y: number;
883
+ z: number;
884
+ };