bun-memory 1.1.25 → 1.1.26

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.
@@ -0,0 +1,107 @@
1
+ // ! Warning:
2
+ // ! This benchmark may take 1-2+ minutes. It repeatedly traverses the entire entity list in
3
+ // ! Counter-Strike 2, storing it into `EntityClassInfoNames`…
4
+
5
+ import Memory from 'bun-memory';
6
+
7
+ // Get the latest client_dll.json and offsets.json from:
8
+ // https://github.com/a2x/cs2-dumper/tree/main/output
9
+
10
+ import ClientDLLJSON from './offsets/client_dll.json';
11
+ import OffsetsJSON from './offsets/offsets.json';
12
+
13
+ // Load the needed offsets as bigints… It's ugly but… IntelliSense! 🫠…
14
+ const Client = {
15
+ ...Object.fromEntries(Object.entries(ClientDLLJSON['client.dll'].classes).map(([class_, { fields }]) => [class_, Object.fromEntries(Object.entries(fields).map(([field, value]) => [field, BigInt(value)]))])),
16
+ Other: Object.fromEntries(Object.entries(OffsetsJSON['client.dll']).map(([key, value]) => [key, BigInt(value)])),
17
+ } as {
18
+ [Class in keyof (typeof ClientDLLJSON)['client.dll']['classes']]: { [Field in keyof (typeof ClientDLLJSON)['client.dll']['classes'][Class]['fields']]: bigint };
19
+ } & { Other: { [K in keyof (typeof OffsetsJSON)['client.dll']]: bigint } };
20
+
21
+ // Open a handle to cs2.exe…
22
+ const cs2 = new Memory('cs2.exe');
23
+
24
+ // Get the base for client.dll…
25
+ const ClientPtr = cs2.modules['client.dll']?.base;
26
+
27
+ // Make sure client.dll is loaded…
28
+ if (ClientPtr === undefined) {
29
+ throw new TypeError('ClientPtr must not be undefined.');
30
+ }
31
+
32
+ // Warmup…
33
+ console.log('Warming up…');
34
+
35
+ for (let i = 0; i < 1e6; i++) {
36
+ const GlobalVarsPtr = cs2.u64(ClientPtr + Client.Other.dwGlobalVars);
37
+ /* */ const CurTime = cs2.f32(GlobalVarsPtr + 0x30n);
38
+ }
39
+
40
+ // Create caches and scratches to optimize performance…
41
+ const Cache_Names = new Map<bigint, string>();
42
+
43
+ const EntityChunkScratch = new BigUint64Array(0xf000 / 0x08);
44
+ const EntityListScratch = new BigUint64Array(0x200 / 0x08);
45
+
46
+ // Start the test…
47
+ console.log('Starting the test…');
48
+
49
+ const performance1 = performance.now();
50
+
51
+ const EntityListPtr = cs2.u64(ClientPtr + Client.Other.dwEntityList);
52
+
53
+ for (let i = 0; i < 1e6; i++) {
54
+ const EntityClassInfoNames = new Map<string, bigint[]>();
55
+ const EntityClassInfoPtrs = new Map<bigint, bigint[]>();
56
+
57
+ // Traverse the entity list…
58
+ cs2.read(EntityListPtr + 0x10n, EntityListScratch);
59
+
60
+ for (let i = 0; i < 0x40; i++) {
61
+ const EntityChunkPtr = EntityListScratch[i];
62
+
63
+ if (EntityChunkPtr === 0n) {
64
+ continue;
65
+ }
66
+
67
+ cs2.read(EntityChunkPtr, EntityChunkScratch);
68
+
69
+ for (let j = 0, l = 0; j < 0x200; j++, l += 0x0f) {
70
+ const BaseEntityPtr = EntityChunkScratch[l];
71
+
72
+ if (BaseEntityPtr === 0n) {
73
+ continue;
74
+ }
75
+
76
+ const EntityClassInfoPtr = EntityChunkScratch[l + 0x01];
77
+
78
+ let BaseEntityPtrs = EntityClassInfoPtrs.get(EntityClassInfoPtr);
79
+
80
+ if (BaseEntityPtrs === undefined) {
81
+ BaseEntityPtrs = [];
82
+
83
+ EntityClassInfoPtrs.set(EntityClassInfoPtr, BaseEntityPtrs);
84
+ }
85
+
86
+ BaseEntityPtrs.push(BaseEntityPtr);
87
+ }
88
+
89
+ for (const [EntityClassInfoPtr, BaseEntityPtrs] of EntityClassInfoPtrs) {
90
+ let Name = Cache_Names.get(EntityClassInfoPtr);
91
+
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();
96
+
97
+ Cache_Names.set(EntityClassInfoPtr, Name);
98
+ }
99
+
100
+ EntityClassInfoNames.set(Name, BaseEntityPtrs);
101
+ }
102
+ }
103
+ }
104
+
105
+ const performance2 = performance.now();
106
+
107
+ console.log('Test completed in %fms…', (performance2 - performance1).toFixed(2));
@@ -21,21 +21,12 @@ const {
21
21
  });
22
22
 
23
23
  // Load the needed offsets as bigints…
24
- const { C_BaseEntity, C_BasePlayerPawn, C_BasePlayerWeapon, C_CSPlayerPawn, C_CSPlayerPawnBase, C_CSWeaponBaseGun, CBasePlayerController, CCSPlayer_WeaponServices, CCSWeaponBaseVData } = Object.fromEntries(
25
- Object.entries(ClientDLLJSON['client.dll'].classes).map(([class_, { fields }]) => [class_, Object.fromEntries(Object.entries(fields).map(([field, value]) => [field, BigInt(value)]))])
26
- ) as {
27
- [C in keyof (typeof ClientDLLJSON)['client.dll']['classes']]: { [F in keyof (typeof ClientDLLJSON)['client.dll']['classes'][C]['fields']]: bigint };
28
- };
29
-
30
- // Load the needed offsets as bigints…
31
- const {
32
- 'client.dll': { dwEntityList, dwGlobalVars, dwLocalPlayerController, dwLocalPlayerPawn },
33
- 'engine2.dll': {},
34
- } = Object.fromEntries(
35
- Object.entries(OffsetsJSON).map(([name, section]) => [name, Object.fromEntries(Object.entries(section).map(([key, value]) => [key, BigInt(value as number)]))]) //
36
- ) as {
37
- [M in keyof typeof OffsetsJSON]: { [K in keyof (typeof OffsetsJSON)[M]]: bigint };
38
- };
24
+ const Client = {
25
+ ...Object.fromEntries(Object.entries(ClientDLLJSON['client.dll'].classes).map(([class_, { fields }]) => [class_, Object.fromEntries(Object.entries(fields).map(([field, value]) => [field, BigInt(value)]))])),
26
+ Other: Object.fromEntries(Object.entries(OffsetsJSON['client.dll']).map(([key, value]) => [key, BigInt(value)])),
27
+ } as {
28
+ [Class in keyof (typeof ClientDLLJSON)['client.dll']['classes']]: { [Field in keyof (typeof ClientDLLJSON)['client.dll']['classes'][Class]['fields']]: bigint };
29
+ } & { Other: { [K in keyof (typeof OffsetsJSON)['client.dll']]: bigint } };
39
30
 
40
31
  // Open a handle to cs2.exe…
41
32
  const cs2 = new Memory('cs2.exe');
@@ -57,26 +48,26 @@ async function tick(ClientPtr: bigint) {
57
48
  ticks++;
58
49
 
59
50
  // Read relevant info from memory…
60
- const GlobalVarsPtr = cs2.u64(ClientPtr + dwGlobalVars);
51
+ const GlobalVarsPtr = cs2.u64(ClientPtr + Client.Other.dwGlobalVars);
61
52
  /* */ const CurTime = cs2.f32(GlobalVarsPtr + 0x30n);
62
53
 
63
- const Local_PlayerControllerPtr = cs2.u64(ClientPtr + dwLocalPlayerController);
64
- /* */ const Local_TickBase = cs2.u32(Local_PlayerControllerPtr + CBasePlayerController.m_nTickBase);
65
-
66
- const Local_PlayerPawnPtr = cs2.u64(ClientPtr + dwLocalPlayerPawn);
67
- /* */ const Local_FlashOverlayAlpha = cs2.f32(Local_PlayerPawnPtr + C_CSPlayerPawnBase.m_flFlashOverlayAlpha);
68
- /* */ const Local_IDEntIndex = cs2.i32(Local_PlayerPawnPtr + C_CSPlayerPawn.m_iIDEntIndex);
69
- /* */ const Local_IsScoped = cs2.i32(Local_PlayerPawnPtr + C_CSPlayerPawn.m_bIsScoped);
70
- /* */ const Local_Player_WeaponServicesPtr = cs2.u64(Local_PlayerPawnPtr + C_BasePlayerPawn.m_pWeaponServices);
71
- /* */ const Local_NextAttack = cs2.f32(Local_Player_WeaponServicesPtr + CCSPlayer_WeaponServices.m_flNextAttack);
72
- /* */ const Local_TeamNum = cs2.u8(Local_PlayerPawnPtr + C_BaseEntity.m_iTeamNum);
73
- /* */ const Local_WeaponBasePtr = cs2.u64(Local_PlayerPawnPtr + C_CSPlayerPawn.m_pClippingWeapon);
74
- /* */ const Local_Clip1 = cs2.i32(Local_WeaponBasePtr + C_BasePlayerWeapon.m_iClip1);
75
- /* */ const Local_NextPrimaryAttackTick = cs2.u32(Local_WeaponBasePtr + C_BasePlayerWeapon.m_nNextPrimaryAttackTick);
76
- /* */ const Local_WeaponBaseVDataPtr = cs2.u64(Local_WeaponBasePtr + C_BaseEntity.m_nSubclassID + 0x08n);
77
- /* */ const Local_IsFullAuto = cs2.bool(Local_WeaponBaseVDataPtr + CCSWeaponBaseVData.m_bIsFullAuto);
78
- /* */ const Local_WeaponType = cs2.i32(Local_WeaponBaseVDataPtr + CCSWeaponBaseVData.m_WeaponType);
79
- /* */ const Local_ZoomLevel = cs2.i32(Local_WeaponBasePtr + C_CSWeaponBaseGun.m_zoomLevel);
54
+ const Local_PlayerControllerPtr = cs2.u64(ClientPtr + Client.Other.dwLocalPlayerController);
55
+ /* */ const Local_TickBase = cs2.u32(Local_PlayerControllerPtr + Client.CBasePlayerController.m_nTickBase);
56
+
57
+ const Local_PlayerPawnPtr = cs2.u64(ClientPtr + Client.Other.dwLocalPlayerPawn);
58
+ /* */ const Local_FlashOverlayAlpha = cs2.f32(Local_PlayerPawnPtr + Client.C_CSPlayerPawnBase.m_flFlashOverlayAlpha);
59
+ /* */ const Local_IDEntIndex = cs2.i32(Local_PlayerPawnPtr + Client.C_CSPlayerPawn.m_iIDEntIndex);
60
+ /* */ const Local_IsScoped = cs2.i32(Local_PlayerPawnPtr + Client.C_CSPlayerPawn.m_bIsScoped);
61
+ /* */ const Local_Player_WeaponServicesPtr = cs2.u64(Local_PlayerPawnPtr + Client.C_BasePlayerPawn.m_pWeaponServices);
62
+ /* */ const Local_NextAttack = cs2.f32(Local_Player_WeaponServicesPtr + Client.CCSPlayer_WeaponServices.m_flNextAttack);
63
+ /* */ const Local_TeamNum = cs2.u8(Local_PlayerPawnPtr + Client.C_BaseEntity.m_iTeamNum);
64
+ /* */ const Local_WeaponBasePtr = cs2.u64(Local_PlayerPawnPtr + Client.C_CSPlayerPawn.m_pClippingWeapon);
65
+ /* */ const Local_Clip1 = cs2.i32(Local_WeaponBasePtr + Client.C_BasePlayerWeapon.m_iClip1);
66
+ /* */ const Local_NextPrimaryAttackTick = cs2.u32(Local_WeaponBasePtr + Client.C_BasePlayerWeapon.m_nNextPrimaryAttackTick);
67
+ /* */ const Local_WeaponBaseVDataPtr = cs2.u64(Local_WeaponBasePtr + Client.C_BaseEntity.m_nSubclassID + 0x08n);
68
+ /* */ const Local_IsFullAuto = cs2.bool(Local_WeaponBaseVDataPtr + Client.CCSWeaponBaseVData.m_bIsFullAuto);
69
+ /* */ const Local_WeaponType = cs2.i32(Local_WeaponBaseVDataPtr + Client.CCSWeaponBaseVData.m_WeaponType);
70
+ /* */ const Local_ZoomLevel = cs2.i32(Local_WeaponBasePtr + Client.C_CSWeaponBaseGun.m_zoomLevel);
80
71
 
81
72
  // Conditions where we should not fire…
82
73
  if (CurTime < Local_NextAttack) {
@@ -96,7 +87,7 @@ async function tick(ClientPtr: bigint) {
96
87
  // Weapon types: https://swiftlys2.net/sdk/cs2/types/csweapontype
97
88
 
98
89
  // Get the entity that we're aiming at from the entity list…
99
- const EntityListPtr = cs2.u64(ClientPtr + dwEntityList);
90
+ const EntityListPtr = cs2.u64(ClientPtr + Client.Other.dwEntityList);
100
91
  /* */ const EntityChunkPtr = cs2.u64(EntityListPtr + (BigInt(Local_IDEntIndex) >> 0x09n) * 0x08n + 0x10n);
101
92
  /* */ const BaseEntityPtr = cs2.u64(EntityChunkPtr + (BigInt(Local_IDEntIndex) & 0x1ffn) * 0x78n);
102
93
  /* */ const EntityClassInfoPtr = cs2.u64(EntityChunkPtr + (BigInt(Local_IDEntIndex) & 0x1ffn) * 0x78n + 0x08n);
@@ -117,7 +108,7 @@ async function tick(ClientPtr: bigint) {
117
108
  }
118
109
 
119
110
  const PlayerPawnPtr = BaseEntityPtr;
120
- /* */ const TeamNum = cs2.u8(PlayerPawnPtr + BigInt(C_BaseEntity.m_iTeamNum));
111
+ /* */ const TeamNum = cs2.u8(PlayerPawnPtr + Client.C_BaseEntity.m_iTeamNum);
121
112
 
122
113
  if (TeamNum === Local_TeamNum) {
123
114
  return;
@@ -131,7 +122,7 @@ async function tick(ClientPtr: bigint) {
131
122
  // If the gun is automatic, hold the trigger until we're no longer aiming at them…
132
123
  do {
133
124
  await sleep(random() * Delay + Delay);
134
- } while (Local_IDEntIndex === cs2.u32(Local_PlayerPawnPtr + C_CSPlayerPawn.m_iIDEntIndex) && Local_IsFullAuto);
125
+ } while (Local_IDEntIndex === cs2.u32(Local_PlayerPawnPtr + Client.C_CSPlayerPawn.m_iIDEntIndex) && Local_IsFullAuto);
135
126
 
136
127
  // Release the trigger…
137
128
  mouse_event(0x04, 0x00, 0x00, 0x00, 0n);
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.25",
25
+ "version": "1.1.26",
26
26
  "main": "./index.ts",
27
27
  "keywords": [
28
28
  "bun",
@@ -46,5 +46,9 @@
46
46
  ],
47
47
  "engines": {
48
48
  "bun": ">=1.1.0"
49
+ },
50
+ "scripts": {
51
+ "run:benchmark": "bun ./example/benchmark.ts",
52
+ "run:trigger-bot": "bun ./example/trigger-bot.ts"
49
53
  }
50
54
  }
@@ -190,7 +190,7 @@ constructors.forEach(
190
190
  ({ prototype }) =>
191
191
  !Object.getOwnPropertyDescriptor(prototype, 'ptr') &&
192
192
  Object.defineProperty(prototype, 'ptr', {
193
- configurable: true,
193
+ configurable: false,
194
194
  enumerable: false,
195
195
  /**
196
196
  * Returns a native pointer to the underlying memory.
package/structs/Memory.ts CHANGED
@@ -1,9 +1,11 @@
1
- import { CString, FFIType, dlopen } from 'bun:ffi';
1
+ import { CString, FFIType, dlopen, ptr } from 'bun:ffi';
2
2
 
3
3
  import type { Module, NetworkUtlVector, Point, QAngle, Quaternion, Region, RGB, RGBA, Scratch, UPtr, UPtrArray, Vector2, Vector3, Vector4 } from '../types/Memory';
4
4
  import Win32Error from './Win32Error';
5
5
 
6
- const { symbols: Kernel32 } = dlopen('kernel32.dll', {
6
+ const {
7
+ symbols: { CloseHandle, CreateToolhelp32Snapshot, GetLastError, Module32FirstW, Module32NextW, OpenProcess, Process32FirstW, Process32NextW, ReadProcessMemory, VirtualQueryEx, WriteProcessMemory },
8
+ } = dlopen('kernel32.dll', {
7
9
  CloseHandle: { args: [FFIType.u64], returns: FFIType.bool },
8
10
  CreateToolhelp32Snapshot: { args: [FFIType.u32, FFIType.u32], returns: FFIType.u64 },
9
11
  GetLastError: { returns: FFIType.u32 },
@@ -27,6 +29,7 @@ const { symbols: Kernel32 } = dlopen('kernel32.dll', {
27
29
  * Many scalar reads utilize `TypedArray` scratches to avoid a second FFI hop, such as calling `bun:ffi.read.*`.
28
30
  *
29
31
  * @todo Add support for 32 or 64-bit processes using IsWow64Process2 (Windows 10+).
32
+ * @todo When adding 32-bit support, several u64 will need changed to u64_fast.
30
33
  *
31
34
  * @example
32
35
  * ```ts
@@ -50,21 +53,21 @@ class Memory {
50
53
  const dwFlags = 0x00000002; /* TH32CS_SNAPPROCESS */
51
54
  const th32ProcessID = 0;
52
55
 
53
- const hSnapshot = Kernel32.CreateToolhelp32Snapshot(dwFlags, th32ProcessID);
56
+ const hSnapshot = CreateToolhelp32Snapshot(dwFlags, th32ProcessID);
54
57
 
55
58
  if (hSnapshot === -1n) {
56
- throw new Win32Error('CreateToolhelp32Snapshot', Kernel32.GetLastError());
59
+ throw new Win32Error('CreateToolhelp32Snapshot', GetLastError());
57
60
  }
58
61
 
59
62
  const lppe = Buffer.allocUnsafe(0x238 /* sizeof(PROCESSENTRY32) */);
60
63
  /* */ lppe.writeUInt32LE(0x238 /* sizeof(PROCESSENTRY32) */);
61
64
 
62
- const bProcess32FirstW = Kernel32.Process32FirstW(hSnapshot, lppe);
65
+ const bProcess32FirstW = Process32FirstW(hSnapshot, lppe);
63
66
 
64
67
  if (!bProcess32FirstW) {
65
- Kernel32.CloseHandle(hSnapshot);
68
+ CloseHandle(hSnapshot);
66
69
 
67
- throw new Win32Error('Process32FirstW', Kernel32.GetLastError());
70
+ throw new Win32Error('Process32FirstW', GetLastError());
68
71
  }
69
72
 
70
73
  do {
@@ -81,12 +84,12 @@ class Memory {
81
84
  const desiredAccess = 0x001f0fff; /* PROCESS_ALL_ACCESS */
82
85
  const inheritHandle = false;
83
86
 
84
- const hProcess = Kernel32.OpenProcess(desiredAccess, inheritHandle, th32ProcessID);
87
+ const hProcess = OpenProcess(desiredAccess, inheritHandle, th32ProcessID);
85
88
 
86
89
  if (hProcess === 0n) {
87
- Kernel32.CloseHandle(hSnapshot);
90
+ CloseHandle(hSnapshot);
88
91
 
89
- throw new Win32Error('OpenProcess', Kernel32.GetLastError());
92
+ throw new Win32Error('OpenProcess', GetLastError());
90
93
  }
91
94
 
92
95
  this._modules = {};
@@ -96,12 +99,12 @@ class Memory {
96
99
 
97
100
  this.refresh();
98
101
 
99
- Kernel32.CloseHandle(hSnapshot);
102
+ CloseHandle(hSnapshot);
100
103
 
101
104
  return;
102
- } while (Kernel32.Process32NextW(hSnapshot, lppe));
105
+ } while (Process32NextW(hSnapshot, lppe));
103
106
 
104
- Kernel32.CloseHandle(hSnapshot);
107
+ CloseHandle(hSnapshot);
105
108
 
106
109
  throw new Error(`Process not found: ${identifier}…`);
107
110
  }
@@ -180,10 +183,10 @@ class Memory {
180
183
  let lpAddress = BigInt(address); // prettier-ignore
181
184
  const lpBuffer = this.ScratchMemoryBasicInformation;
182
185
 
183
- const bVirtualQueryEx = !!Kernel32.VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength);
186
+ const bVirtualQueryEx = !!VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength);
184
187
 
185
188
  if (!bVirtualQueryEx) {
186
- throw new Win32Error('VirtualQueryEx', Kernel32.GetLastError());
189
+ throw new Win32Error('VirtualQueryEx', GetLastError());
187
190
  }
188
191
 
189
192
  const end = lpAddress + BigInt(length);
@@ -201,11 +204,101 @@ class Memory {
201
204
  }
202
205
 
203
206
  lpAddress = baseAddress + regionSize;
204
- } while (lpAddress < end && !!Kernel32.VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength));
207
+ } while (lpAddress < end && !!VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength));
205
208
 
206
209
  return result;
207
210
  }
208
211
 
212
+ /**
213
+ * Closes the process handle.
214
+ * @example
215
+ * ```ts
216
+ * const cs2 = new Memory('cs2.exe');
217
+ * cs2.close();
218
+ * ```
219
+ */
220
+ public close(): void {
221
+ CloseHandle(this.hProcess);
222
+
223
+ return;
224
+ }
225
+
226
+ /**
227
+ * Disposes resources held by this Memory instance.
228
+ * Called automatically when using `using` blocks.
229
+ * @example
230
+ * ```ts
231
+ * using const mem = new Memory('cs2.exe');
232
+ * // mem is disposed at the end of the block
233
+ * ```
234
+ */
235
+ public [Symbol.dispose](): void {
236
+ this.close();
237
+
238
+ return;
239
+ }
240
+
241
+ /**
242
+ * Asynchronously disposes resources held by this Memory instance.
243
+ * Use in `await using` blocks for async cleanup.
244
+ * @example
245
+ * ```ts
246
+ * await using const mem = new Memory('cs2.exe');
247
+ * // mem is disposed asynchronously at the end of the block
248
+ * ```
249
+ */
250
+ public [Symbol.asyncDispose](): Promise<void> {
251
+ this.close();
252
+
253
+ return Promise.resolve();
254
+ }
255
+
256
+ /**
257
+ * Refreshes the module list for the process.
258
+ * @example
259
+ * ```ts
260
+ * const cs2 = new Memory('cs2.exe');
261
+ * cs2.refresh();
262
+ * ```
263
+ */
264
+ public refresh(): void {
265
+ const dwFlags = 0x00000008 /* TH32CS_SNAPMODULE */ | 0x00000010; /* TH32CS_SNAPMODULE32 */
266
+
267
+ const hSnapshot = CreateToolhelp32Snapshot(dwFlags, this.th32ProcessID)!;
268
+
269
+ if (hSnapshot === -1n) {
270
+ throw new Win32Error('CreateToolhelp32Snapshot', GetLastError());
271
+ }
272
+
273
+ this.ScratchModuleEntry32W.writeUInt32LE(0x438 /* sizeof(MODULEENTRY32W) */);
274
+
275
+ const lpme = this.ScratchModuleEntry32W;
276
+
277
+ const bModule32FirstW = Module32FirstW(hSnapshot, lpme);
278
+
279
+ if (!bModule32FirstW) {
280
+ CloseHandle(hSnapshot);
281
+
282
+ throw new Win32Error('Module32FirstW', GetLastError());
283
+ }
284
+
285
+ const modules: Memory['_modules'] = {};
286
+
287
+ do {
288
+ const modBaseAddr = lpme.readBigUInt64LE(0x18);
289
+ const modBaseSize = lpme.readUInt32LE(0x20);
290
+ const szModule = lpme.toString('utf16le', 0x30, 0x230).replace(/\0+$/, '');
291
+
292
+ modules[szModule] = Object.freeze({ base: modBaseAddr, name: szModule, size: modBaseSize });
293
+ } while (Module32NextW(hSnapshot, lpme));
294
+
295
+ CloseHandle(hSnapshot);
296
+
297
+ this._modules = Object.freeze(modules);
298
+
299
+ return;
300
+ }
301
+
209
302
  /**
210
303
  * Follows a pointer chain with offsets.
211
304
  * @param address Base address.
@@ -236,6 +329,7 @@ class Memory {
236
329
  * @param address Address to read from.
237
330
  * @param scratch Buffer to fill.
238
331
  * @returns The filled buffer.
332
+ * @todo Consider inlining the call in the if to cut a binding… I hate the idea… 🫠…
239
333
  * @example
240
334
  * ```ts
241
335
  * const cs2 = new Memory('cs2.exe');
@@ -244,14 +338,14 @@ class Memory {
244
338
  */
245
339
  public read<T extends Scratch>(address: bigint, scratch: T): T {
246
340
  const lpBaseAddress = address;
247
- const lpBuffer = scratch.ptr;
341
+ const lpBuffer = ptr(scratch);
248
342
  const nSize = scratch.byteLength;
249
343
  const numberOfBytesRead = 0x00n;
250
344
 
251
- const bReadProcessMemory = Kernel32.ReadProcessMemory(this.hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesRead);
345
+ const bReadProcessMemory = ReadProcessMemory(this.hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesRead);
252
346
 
253
347
  if (!bReadProcessMemory) {
254
- throw new Win32Error('ReadProcessMemory', Kernel32.GetLastError());
348
+ throw new Win32Error('ReadProcessMemory', GetLastError());
255
349
  }
256
350
 
257
351
  return scratch;
@@ -262,6 +356,8 @@ class Memory {
262
356
  * @param address Address to write to.
263
357
  * @param scratch Buffer to write.
264
358
  * @returns This instance.
359
+ * @todo Add a `force: boolean` option that uses `VirtualProtectEx` for a temporary protection change when set to `true`.
360
+ * @todo Consider inlining the call in the if to cut a binding… I hate the idea… 🫠…
265
361
  * @example
266
362
  * ```ts
267
363
  * const cs2 = new Memory('cs2.exe');
@@ -274,75 +370,15 @@ class Memory {
274
370
  const nSize = scratch.byteLength;
275
371
  const numberOfBytesWritten = 0x00n;
276
372
 
277
- const WriteProcessMemory = Kernel32.WriteProcessMemory(this.hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesWritten);
373
+ const bWriteProcessMemory = WriteProcessMemory(this.hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesWritten);
278
374
 
279
- if (!WriteProcessMemory) {
280
- throw new Win32Error('WriteProcessMemory', Kernel32.GetLastError());
375
+ if (!bWriteProcessMemory) {
376
+ throw new Win32Error('WriteProcessMemory', GetLastError());
281
377
  }
282
378
 
283
379
  return this;
284
380
  }
285
381
 
286
- /**
287
- * Closes the process handle.
288
- * @example
289
- * ```ts
290
- * const cs2 = new Memory('cs2.exe');
291
- * cs2.close();
292
- * ```
293
- */
294
- public close(): void {
295
- Kernel32.CloseHandle(this.hProcess);
296
-
297
- return;
298
- }
299
-
300
- /**
301
- * Refreshes the module list for the process.
302
- * @example
303
- * ```ts
304
- * const cs2 = new Memory('cs2.exe');
305
- * cs2.refresh();
306
- * ```
307
- */
308
- public refresh(): void {
309
- const dwFlags = 0x00000008 /* TH32CS_SNAPMODULE */ | 0x00000010; /* TH32CS_SNAPMODULE32 */
310
-
311
- const hSnapshot = Kernel32.CreateToolhelp32Snapshot(dwFlags, this.th32ProcessID)!;
312
-
313
- if (hSnapshot === -1n) {
314
- throw new Win32Error('CreateToolhelp32Snapshot', Kernel32.GetLastError());
315
- }
316
-
317
- this.ScratchModuleEntry32W.writeUInt32LE(0x438 /* sizeof(MODULEENTRY32W) */);
318
-
319
- const lpme = this.ScratchModuleEntry32W;
320
-
321
- const bModule32FirstW = Kernel32.Module32FirstW(hSnapshot, lpme);
322
-
323
- if (!bModule32FirstW) {
324
- Kernel32.CloseHandle(hSnapshot);
325
-
326
- throw new Win32Error('Module32FirstW', Kernel32.GetLastError());
327
- }
328
-
329
- const modules: Memory['_modules'] = {};
330
-
331
- do {
332
- const modBaseAddr = lpme.readBigUInt64LE(0x18);
333
- const modBaseSize = lpme.readUInt32LE(0x20);
334
- const szModule = lpme.toString('utf16le', 0x30, 0x230).replace(/\0+$/, '');
335
-
336
- modules[szModule] = Object.freeze({ base: modBaseAddr, name: szModule, size: modBaseSize });
337
- } while (Kernel32.Module32NextW(hSnapshot, lpme));
338
-
339
- Kernel32.CloseHandle(hSnapshot);
340
-
341
- this._modules = Object.freeze(modules);
342
-
343
- return;
344
- }
345
-
346
382
  /**
347
383
  * Reads or writes a boolean value.
348
384
  * @param address Address to access.
@@ -996,6 +1032,22 @@ class Memory {
996
1032
  return this;
997
1033
  }
998
1034
 
1035
+ public pointRaw(address: bigint): Float32Array;
1036
+ public pointRaw(address: bigint, values: Float32Array): this;
1037
+ public pointRaw(address: bigint, values?: Float32Array): Float32Array | this {
1038
+ if (values === undefined) {
1039
+ return this.f32Array(address, 0x02);
1040
+ }
1041
+
1042
+ if (values.length !== 0x02) {
1043
+ throw new RangeError('values.length must be 2.');
1044
+ }
1045
+
1046
+ this.write(address, values);
1047
+
1048
+ return this;
1049
+ }
1050
+
999
1051
  /**
1000
1052
  * Reads or writes a QAngle (object with pitch, yaw, roll).
1001
1053
  * @param address Address to access.
@@ -1059,6 +1111,7 @@ class Memory {
1059
1111
  const pitch = scratch[j];
1060
1112
  const yaw = scratch[j + 0x01];
1061
1113
  const roll = scratch[j + 0x02];
1114
+
1062
1115
  result[i] = { pitch, yaw, roll };
1063
1116
  }
1064
1117
 
@@ -1081,6 +1134,34 @@ class Memory {
1081
1134
  return this;
1082
1135
  }
1083
1136
 
1137
+ /**
1138
+ * Reads or writes a raw QAngle as a Float32Array.
1139
+ * @param address Address to access.
1140
+ * @param values Optional Float32Array to write.
1141
+ * @returns Float32Array read or this instance if writing.
1142
+ * @example
1143
+ * ```ts
1144
+ * const cs2 = new Memory('cs2.exe');
1145
+ * const raw = cs2.qAngleRaw(0x12345678n);
1146
+ * cs2.qAngleRaw(0x12345678n, new Float32Array([1,2,3]));
1147
+ * ```
1148
+ */
1149
+ public qAngleRaw(address: bigint): Float32Array;
1150
+ public qAngleRaw(address: bigint, values: Float32Array): this;
1151
+ public qAngleRaw(address: bigint, values?: Float32Array): Float32Array | this {
1152
+ if (values === undefined) {
1153
+ return this.f32Array(address, 0x03);
1154
+ }
1155
+
1156
+ if (values.length !== 0x03) {
1157
+ throw new RangeError('values.length must be 3.');
1158
+ }
1159
+
1160
+ this.write(address, values);
1161
+
1162
+ return this;
1163
+ }
1164
+
1084
1165
  /**
1085
1166
  * Reads or writes a Quaternion (object with w, x, y, z).
1086
1167
  * @param address Address to access.
@@ -1171,6 +1252,34 @@ class Memory {
1171
1252
  return this;
1172
1253
  }
1173
1254
 
1255
+ /**
1256
+ * Reads or writes a raw Quaternion as a Float32Array.
1257
+ * @param address Address to access.
1258
+ * @param values Optional Float32Array to write.
1259
+ * @returns Float32Array read or this instance if writing.
1260
+ * @example
1261
+ * ```ts
1262
+ * const cs2 = new Memory('cs2.exe');
1263
+ * const raw = cs2.quaternionRaw(0x12345678n);
1264
+ * cs2.quaternionRaw(0x12345678n, new Float32Array([1,0,0,0]));
1265
+ * ```
1266
+ */
1267
+ public quaternionRaw(address: bigint): Float32Array;
1268
+ public quaternionRaw(address: bigint, values: Float32Array): this;
1269
+ public quaternionRaw(address: bigint, values?: Float32Array): Float32Array | this {
1270
+ if (values === undefined) {
1271
+ return this.f32Array(address, 0x04);
1272
+ }
1273
+
1274
+ if (values.length !== 0x04) {
1275
+ throw new RangeError('values.length must be 4.');
1276
+ }
1277
+
1278
+ this.write(address, values);
1279
+
1280
+ return this;
1281
+ }
1282
+
1174
1283
  /**
1175
1284
  * Reads or writes an RGB color (object with r, g, b).
1176
1285
  * @param address Address to access.
@@ -1205,6 +1314,34 @@ class Memory {
1205
1314
  return this.write(address, Scratch3);
1206
1315
  }
1207
1316
 
1317
+ /**
1318
+ * Reads or writes a raw RGB value as a Uint8Array.
1319
+ * @param address Address to access.
1320
+ * @param values Optional buffer to write.
1321
+ * @returns Uint8Array read or this instance if writing.
1322
+ * @example
1323
+ * ```ts
1324
+ * const cs2 = new Memory('cs2.exe');
1325
+ * const raw = cs2.rgbRaw(0x12345678n);
1326
+ * cs2.rgbRaw(0x12345678n, new Uint8Array([255,0,0]));
1327
+ * ```
1328
+ */
1329
+ public rgbRaw(address: bigint): Uint8Array;
1330
+ public rgbRaw(address: bigint, values: Buffer | Uint8Array | Uint8ClampedArray): this;
1331
+ public rgbRaw(address: bigint, values?: Buffer | Uint8Array | Uint8ClampedArray): Uint8Array | this {
1332
+ if (values === undefined) {
1333
+ return this.u8Array(address, 0x03);
1334
+ }
1335
+
1336
+ if (values.length !== 0x03) {
1337
+ throw new RangeError('values.length must be 3.');
1338
+ }
1339
+
1340
+ this.write(address, values);
1341
+
1342
+ return this;
1343
+ }
1344
+
1208
1345
  /**
1209
1346
  * Reads or writes an RGBA color (object with r, g, b, a).
1210
1347
  * @param address Address to access.
@@ -1241,6 +1378,34 @@ class Memory {
1241
1378
  return this.write(address, Scratch4);
1242
1379
  }
1243
1380
 
1381
+ /**
1382
+ * Reads or writes a raw RGBA value as a Uint8Array.
1383
+ * @param address Address to access.
1384
+ * @param values Optional buffer to write.
1385
+ * @returns Uint8Array read or this instance if writing.
1386
+ * @example
1387
+ * ```ts
1388
+ * const cs2 = new Memory('cs2.exe');
1389
+ * const raw = cs2.rgbaRaw(0x12345678n);
1390
+ * cs2.rgbaRaw(0x12345678n, new Uint8Array([255,0,0,255]));
1391
+ * ```
1392
+ */
1393
+ public rgbaRaw(address: bigint): Uint8Array;
1394
+ public rgbaRaw(address: bigint, values: Buffer | Uint8Array | Uint8ClampedArray): this;
1395
+ public rgbaRaw(address: bigint, values?: Buffer | Uint8Array | Uint8ClampedArray): Uint8Array | this {
1396
+ if (values === undefined) {
1397
+ return this.u8Array(address, 0x04);
1398
+ }
1399
+
1400
+ if (values.length !== 0x04) {
1401
+ throw new RangeError('values.length must be 4.');
1402
+ }
1403
+
1404
+ this.write(address, values);
1405
+
1406
+ return this;
1407
+ }
1408
+
1244
1409
  /**
1245
1410
  * Reads or writes a 16-bit unsigned integer.
1246
1411
  * @param address Address to access.
@@ -1569,6 +1734,22 @@ class Memory {
1569
1734
  return this.pointArray(address, lengthOrValues);
1570
1735
  }
1571
1736
 
1737
+ public vector2Raw(address: bigint): Float32Array;
1738
+ public vector2Raw(address: bigint, values: Float32Array): this;
1739
+ public vector2Raw(address: bigint, values?: Float32Array): Float32Array | this {
1740
+ if (values === undefined) {
1741
+ return this.f32Array(address, 0x02);
1742
+ }
1743
+
1744
+ if (values.length !== 0x02) {
1745
+ throw new RangeError('values.length must be 2.');
1746
+ }
1747
+
1748
+ this.write(address, values);
1749
+
1750
+ return this;
1751
+ }
1752
+
1572
1753
  /**
1573
1754
  * Reads or writes a Vector3 (object with x, y, z).
1574
1755
  * @param address Address to access.
@@ -1655,6 +1836,22 @@ class Memory {
1655
1836
  return this;
1656
1837
  }
1657
1838
 
1839
+ public vector3Raw(address: bigint): Float32Array;
1840
+ public vector3Raw(address: bigint, values: Float32Array): this;
1841
+ public vector3Raw(address: bigint, values?: Float32Array): Float32Array | this {
1842
+ if (values === undefined) {
1843
+ return this.f32Array(address, 0x03);
1844
+ }
1845
+
1846
+ if (values.length !== 0x03) {
1847
+ throw new RangeError('values.length must be 3.');
1848
+ }
1849
+
1850
+ this.write(address, values);
1851
+
1852
+ return this;
1853
+ }
1854
+
1658
1855
  /**
1659
1856
  * Reads or writes a Vector4 (object with w, x, y, z).
1660
1857
  * @param address Address to access.
@@ -1701,6 +1898,22 @@ class Memory {
1701
1898
  return this.quaternionArray(address, lengthOrValues);
1702
1899
  }
1703
1900
 
1901
+ public vector4Raw(address: bigint): Float32Array;
1902
+ public vector4Raw(address: bigint, values: Float32Array): this;
1903
+ public vector4Raw(address: bigint, values?: Float32Array): Float32Array | this {
1904
+ if (values === undefined) {
1905
+ return this.f32Array(address, 0x04);
1906
+ }
1907
+
1908
+ if (values.length !== 0x04) {
1909
+ throw new RangeError('values.length must be 4.');
1910
+ }
1911
+
1912
+ this.write(address, values);
1913
+
1914
+ return this;
1915
+ }
1916
+
1704
1917
  // Public utility methods…
1705
1918
 
1706
1919
  /**