bun-memory 1.1.25 → 1.1.27
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 +28 -11
- package/example/benchmark.ts +107 -0
- package/example/trigger-bot.ts +27 -36
- package/package.json +5 -1
- package/runtime/extensions.ts +1 -1
- package/structs/Memory.ts +295 -82
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@ Blazing fast, high-performance Windows process memory manipulation for Bun.
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
11
|
- Attach to processes by name or PID
|
|
12
|
-
- Read and write all primitive types, arrays, buffers, and common structures
|
|
13
12
|
- Efficient, allocation-free operations using user-provided buffers (scratches)
|
|
14
13
|
- Module enumeration and pointer chain resolution
|
|
14
|
+
- Read and write all primitive types, arrays, buffers, and common structures
|
|
15
15
|
- Typed helpers for vectors, matrices, colors, and more
|
|
16
16
|
|
|
17
17
|
## Requirements
|
|
@@ -56,7 +56,7 @@ cs2.close();
|
|
|
56
56
|
|
|
57
57
|
See the code and type definitions for full details. All methods are documented with concise examples.
|
|
58
58
|
|
|
59
|
-
## Example: Efficient
|
|
59
|
+
## Example: Efficient Scratch Reuse
|
|
60
60
|
|
|
61
61
|
```ts
|
|
62
62
|
const buffer = Buffer.allocUnsafe(256);
|
|
@@ -64,6 +64,12 @@ cs2.read(0x12345678n, buffer); // Fills buffer in-place
|
|
|
64
64
|
// …use buffer…
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
+
```ts
|
|
68
|
+
const array = new Float32Array(32);
|
|
69
|
+
cs2.read(0x12345678n, array); // Fills array in-place
|
|
70
|
+
// …use buffer…
|
|
71
|
+
```
|
|
72
|
+
|
|
67
73
|
## Example: Pointer Chains
|
|
68
74
|
|
|
69
75
|
```ts
|
|
@@ -73,15 +79,10 @@ const address = cs2.follow(0x10000000n, [0x10n, 0x20n]);
|
|
|
73
79
|
## Example: Searching Memory
|
|
74
80
|
|
|
75
81
|
```ts
|
|
76
|
-
const needle = Buffer.from([
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
const needle = new Uint32Array([0x01, 0x02, 0x03]);
|
|
82
|
+
const needle = Buffer.from([0x01, 0x02, 0x03]);
|
|
83
|
+
// const needle = new Uint8Array([0x01, 0x02, 0x03]);
|
|
84
|
+
// const needle = new Uint32Array([0x012345, 0x123456, 0x234567]);
|
|
85
|
+
// …etc…
|
|
85
86
|
const address = cs2.indexOf(needle, 0x10000000n, 0x1000);
|
|
86
87
|
if (address !== -1n) {
|
|
87
88
|
console.log(`Found at 0x${address.toString(16)}`);
|
|
@@ -92,13 +93,29 @@ if (address !== -1n) {
|
|
|
92
93
|
|
|
93
94
|
```ts
|
|
94
95
|
const array = cs2.f32Array(0x12345678n, 4); // Float32Array of length 4
|
|
96
|
+
// const array = cs2.u64Array(0x12345678n, 4);
|
|
97
|
+
// const array = cs2.vector3Array(0x12345678n, 4);
|
|
98
|
+
// …etc…
|
|
95
99
|
cs2.i32Array(0x12345678n, new Int32Array([1, 2, 3, 4]));
|
|
100
|
+
cs2.u64Array(0x12345678n, new BigUint64Array([1, 2, 3, 4]));
|
|
101
|
+
cs2.vector3Array(0x12345678n, [{ x: 1, y: 2, z: 3 }]);
|
|
96
102
|
```
|
|
97
103
|
|
|
98
104
|
## Example: Using Scratches (Recommended)
|
|
99
105
|
|
|
100
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.
|
|
101
107
|
|
|
108
|
+
```ts
|
|
109
|
+
const array = new BigUint64Array(0xf000 / 0x08);
|
|
110
|
+
|
|
111
|
+
while (true) {
|
|
112
|
+
cs2.read(0x10000000n, array); // Updates array without allocations
|
|
113
|
+
for (const element of array) {
|
|
114
|
+
// …use element…
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
102
119
|
```ts
|
|
103
120
|
const buffer = Buffer.allocUnsafe(256);
|
|
104
121
|
const array = new Uint64Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 8);
|
|
@@ -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));
|
package/example/trigger-bot.ts
CHANGED
|
@@ -21,21 +21,12 @@ const {
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
// Load the needed offsets as bigints…
|
|
24
|
-
const
|
|
25
|
-
Object.entries(ClientDLLJSON['client.dll'].classes).map(([class_, { fields }]) => [class_, Object.fromEntries(Object.entries(fields).map(([field, value]) => [field, BigInt(value)]))])
|
|
26
|
-
)
|
|
27
|
-
|
|
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 +
|
|
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
|
+
"version": "1.1.27",
|
|
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
|
}
|
package/runtime/extensions.ts
CHANGED
|
@@ -190,7 +190,7 @@ constructors.forEach(
|
|
|
190
190
|
({ prototype }) =>
|
|
191
191
|
!Object.getOwnPropertyDescriptor(prototype, 'ptr') &&
|
|
192
192
|
Object.defineProperty(prototype, 'ptr', {
|
|
193
|
-
configurable:
|
|
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 {
|
|
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 =
|
|
56
|
+
const hSnapshot = CreateToolhelp32Snapshot(dwFlags, th32ProcessID);
|
|
54
57
|
|
|
55
58
|
if (hSnapshot === -1n) {
|
|
56
|
-
throw new Win32Error('CreateToolhelp32Snapshot',
|
|
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 =
|
|
65
|
+
const bProcess32FirstW = Process32FirstW(hSnapshot, lppe);
|
|
63
66
|
|
|
64
67
|
if (!bProcess32FirstW) {
|
|
65
|
-
|
|
68
|
+
CloseHandle(hSnapshot);
|
|
66
69
|
|
|
67
|
-
throw new Win32Error('Process32FirstW',
|
|
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 =
|
|
87
|
+
const hProcess = OpenProcess(desiredAccess, inheritHandle, th32ProcessID);
|
|
85
88
|
|
|
86
89
|
if (hProcess === 0n) {
|
|
87
|
-
|
|
90
|
+
CloseHandle(hSnapshot);
|
|
88
91
|
|
|
89
|
-
throw new Win32Error('OpenProcess',
|
|
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
|
-
|
|
102
|
+
CloseHandle(hSnapshot);
|
|
100
103
|
|
|
101
104
|
return;
|
|
102
|
-
} while (
|
|
105
|
+
} while (Process32NextW(hSnapshot, lppe));
|
|
103
106
|
|
|
104
|
-
|
|
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 = !!
|
|
186
|
+
const bVirtualQueryEx = !!VirtualQueryEx(this.hProcess, lpAddress, lpBuffer, dwLength);
|
|
184
187
|
|
|
185
188
|
if (!bVirtualQueryEx) {
|
|
186
|
-
throw new Win32Error('VirtualQueryEx',
|
|
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 && !!
|
|
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
|
|
341
|
+
const lpBuffer = ptr(scratch);
|
|
248
342
|
const nSize = scratch.byteLength;
|
|
249
343
|
const numberOfBytesRead = 0x00n;
|
|
250
344
|
|
|
251
|
-
const bReadProcessMemory =
|
|
345
|
+
const bReadProcessMemory = ReadProcessMemory(this.hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesRead);
|
|
252
346
|
|
|
253
347
|
if (!bReadProcessMemory) {
|
|
254
|
-
throw new Win32Error('ReadProcessMemory',
|
|
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
|
|
373
|
+
const bWriteProcessMemory = WriteProcessMemory(this.hProcess, lpBaseAddress, lpBuffer, nSize, numberOfBytesWritten);
|
|
278
374
|
|
|
279
|
-
if (!
|
|
280
|
-
throw new Win32Error('WriteProcessMemory',
|
|
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
|
/**
|