@memoryprocess/bindings 0.0.3-beta.2 → 0.1.0-beta.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 JoShMiQueL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @memoryprocess/bindings
2
+
3
+ The raw N-API native layer of the [memoryprocess](https://github.com/memoryprocess/memoryprocess) toolkit: the `.node` binary plus a typed TypeScript interface over the stateless process-memory functions (open/close, read/write, VirtualQueryEx walk, pattern scan, thread ops, hooks primitives).
4
+
5
+ Most consumers want [`@memoryprocess/core`](https://www.npmjs.com/package/@memoryprocess/core) instead -- it wraps this layer in the `Process` class with typed reads/writes, CE-style scanning, and remote structs.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @memoryprocess/bindings
11
+ ```
12
+
13
+ Windows only (`"os": ["win32"]`), Node >= 22. Prebuilt binaries ship under `prebuilds/` for the CI-covered Node majors via `node-gyp-build`.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { native } from '@memoryprocess/bindings';
19
+
20
+ const { handle, pid } = native.openProcess('notepad.exe');
21
+ try {
22
+ const mods = native.getModules(handle, pid);
23
+ const buf = native.readBuffer(handle, Number(mods[0].base), 16);
24
+ } finally {
25
+ native.closeHandle(handle);
26
+ }
27
+ ```
28
+
29
+ Every function is stateless: it takes a `handle`/`pid`/`tid` argument and holds no shared native state. The full function surface is declared in `src/native.ts` (`Native` interface).
30
+
31
+ ## Binary resolution
32
+
33
+ At load time the addon resolves in order:
34
+
35
+ 1. `dist/memoryprocess.node` (bundled in the published tarball)
36
+ 2. `prebuilds/<platform>-<arch>/memoryprocess.node` via `node-gyp-build`
37
+ 3. `build/Release/memoryprocess.node` (local `node-gyp` build)
38
+
39
+ Future Node majors not covered by the shipped prebuilds will need a local `node-gyp rebuild` (`binding.gyp` + `native/` sources are included in the tarball), or wait for a new release.
40
+
41
+ ## Part of
42
+
43
+ [`memoryprocess`](https://github.com/memoryprocess/memoryprocess) -- a Windows process-memory toolkit (`@memoryprocess/{bindings,core,cli,mcp}`), a ground-up rewrite of `memoryjs`.
44
+
45
+ ## License
46
+
47
+ MIT
Binary file
package/native/module.cc CHANGED
@@ -21,6 +21,14 @@ static const double MAX_READ_BYTES = 512.0 * 1024.0 * 1024.0;
21
21
  // silently round. Harmless today (canonical user-space VA < 2^48, real HANDLE
22
22
  // values are small) but don't feed pointers >2^53 through these bindings.
23
23
 
24
+ // Converts a JS number|bigint to u64. Numbers are truncated via the double -
25
+ // callers document the >2^53 rounding caveat (see header note above).
26
+ static bool JsToU64(const Napi::Value& v, std::uint64_t& out) {
27
+ if (v.IsBigInt()) { bool ls; out = v.As<Napi::BigInt>().Uint64Value(&ls); return true; }
28
+ if (v.IsNumber()) { out = static_cast<std::uint64_t>(v.As<Napi::Number>().DoubleValue()); return true; }
29
+ return false;
30
+ }
31
+
24
32
  static bool CheckSize(Napi::Env env, double size) {
25
33
  if (!std::isfinite(size) || size < 0 || size > MAX_READ_BYTES) {
26
34
  Napi::RangeError::New(env, "size must be a finite number between 0 and 512MB")
@@ -133,7 +141,9 @@ Napi::Value GetRegionsSync(const Napi::CallbackInfo& info) {
133
141
 
134
142
  Napi::Value ReadBufferSync(const Napi::CallbackInfo& info) {
135
143
  Napi::Env env = info.Env();
136
- if (info.Length() < 3) { Napi::TypeError::New(env, "expected (handle,address,size)").ThrowAsJavaScriptException(); return env.Null(); }
144
+ if (info.Length() < 3 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsNumber()) {
145
+ Napi::TypeError::New(env, "expected (handle:number,address:number,size:number)").ThrowAsJavaScriptException(); return env.Null();
146
+ }
137
147
  HANDLE h = reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(info[0].As<Napi::Number>().DoubleValue()));
138
148
  auto addr = static_cast<std::uintptr_t>(info[1].As<Napi::Number>().DoubleValue());
139
149
  double sizeD = info[2].As<Napi::Number>().DoubleValue();
@@ -153,7 +163,9 @@ Napi::Value ReadBufferSync(const Napi::CallbackInfo& info) {
153
163
 
154
164
  Napi::Value WriteBufferSync(const Napi::CallbackInfo& info) {
155
165
  Napi::Env env = info.Env();
156
- if (info.Length() < 3) { Napi::TypeError::New(env, "expected (handle,address,buffer)").ThrowAsJavaScriptException(); return env.Null(); }
166
+ if (info.Length() < 3 || !info[0].IsNumber() || !info[1].IsNumber()) {
167
+ Napi::TypeError::New(env, "expected (handle:number,address:number,buffer:Buffer)").ThrowAsJavaScriptException(); return env.Null();
168
+ }
157
169
  HANDLE h = reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(info[0].As<Napi::Number>().DoubleValue()));
158
170
  auto addr = static_cast<std::uintptr_t>(info[1].As<Napi::Number>().DoubleValue());
159
171
  if (!info[2].IsBuffer()) { Napi::TypeError::New(env, "value must be a Buffer").ThrowAsJavaScriptException(); return env.Null(); }
@@ -169,7 +181,9 @@ Napi::Value WriteBufferSync(const Napi::CallbackInfo& info) {
169
181
  // trampoline stubs within rel32 range (+/-2GB) of a hook site.
170
182
  Napi::Value AllocMemorySync(const Napi::CallbackInfo& info) {
171
183
  Napi::Env env = info.Env();
172
- if (info.Length() < 3) { Napi::TypeError::New(env, "expected (handle,size,protection[,nearAddress])").ThrowAsJavaScriptException(); return env.Null(); }
184
+ if (info.Length() < 3 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsNumber()) {
185
+ Napi::TypeError::New(env, "expected (handle:number,size:number,protection:number[,nearAddress:number])").ThrowAsJavaScriptException(); return env.Null();
186
+ }
173
187
  HANDLE h = reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(info[0].As<Napi::Number>().DoubleValue()));
174
188
  auto size = static_cast<SIZE_T>(info[1].As<Napi::Number>().DoubleValue());
175
189
  DWORD prot = info[2].As<Napi::Number>().Uint32Value();
@@ -183,7 +197,9 @@ Napi::Value AllocMemorySync(const Napi::CallbackInfo& info) {
183
197
 
184
198
  Napi::Value FreeMemorySync(const Napi::CallbackInfo& info) {
185
199
  Napi::Env env = info.Env();
186
- if (info.Length() < 2) { Napi::TypeError::New(env, "expected (handle,address)").ThrowAsJavaScriptException(); return env.Null(); }
200
+ if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber()) {
201
+ Napi::TypeError::New(env, "expected (handle:number,address:number)").ThrowAsJavaScriptException(); return env.Null();
202
+ }
187
203
  HANDLE h = reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(info[0].As<Napi::Number>().DoubleValue()));
188
204
  auto addr = static_cast<std::uintptr_t>(info[1].As<Napi::Number>().DoubleValue());
189
205
  return Napi::Boolean::New(env, VirtualFreeEx(h, reinterpret_cast<LPVOID>(addr), 0, MEM_RELEASE) != 0);
@@ -191,7 +207,10 @@ Napi::Value FreeMemorySync(const Napi::CallbackInfo& info) {
191
207
 
192
208
  Napi::Value ProtectMemorySync(const Napi::CallbackInfo& info) {
193
209
  Napi::Env env = info.Env();
194
- if (info.Length() < 3) { Napi::TypeError::New(env, "expected (handle,address,size,prot)").ThrowAsJavaScriptException(); return env.Null(); }
210
+ if (info.Length() < 4 || !info[0].IsNumber() || !info[1].IsNumber() ||
211
+ !info[2].IsNumber() || !info[3].IsNumber()) {
212
+ Napi::TypeError::New(env, "expected (handle:number,address:number,size:number,prot:number)").ThrowAsJavaScriptException(); return env.Null();
213
+ }
195
214
  HANDLE h = reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(info[0].As<Napi::Number>().DoubleValue()));
196
215
  auto addr = static_cast<std::uintptr_t>(info[1].As<Napi::Number>().DoubleValue());
197
216
  auto size = static_cast<SIZE_T>(info[2].As<Napi::Number>().DoubleValue());
@@ -267,7 +286,9 @@ Napi::Value ResumeThreadSync(const Napi::CallbackInfo& info) {
267
286
  // "int32","uint32","int64","uint64","float","double","pointer"
268
287
  Napi::Value ReadValueSync(const Napi::CallbackInfo& info) {
269
288
  Napi::Env env = info.Env();
270
- if (info.Length() < 3) { Napi::TypeError::New(env, "expected (handle,address,type)").ThrowAsJavaScriptException(); return env.Null(); }
289
+ if (info.Length() < 3 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsString()) {
290
+ Napi::TypeError::New(env, "expected (handle:number,address:number,type:string)").ThrowAsJavaScriptException(); return env.Null();
291
+ }
271
292
  HANDLE h = reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(info[0].As<Napi::Number>().DoubleValue()));
272
293
  auto addr = static_cast<std::uintptr_t>(info[1].As<Napi::Number>().DoubleValue());
273
294
  std::string type = info[2].As<Napi::String>().Utf8Value();
@@ -292,19 +313,39 @@ Napi::Value ReadValueSync(const Napi::CallbackInfo& info) {
292
313
 
293
314
  Napi::Value WriteValueSync(const Napi::CallbackInfo& info) {
294
315
  Napi::Env env = info.Env();
295
- if (info.Length() < 4) { Napi::TypeError::New(env, "expected (handle,address,type,value)").ThrowAsJavaScriptException(); return env.Null(); }
316
+ if (info.Length() < 4 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsString()) {
317
+ Napi::TypeError::New(env, "expected (handle:number,address:number,type:string,value:number|bigint)").ThrowAsJavaScriptException(); return env.Null();
318
+ }
296
319
  HANDLE h = reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(info[0].As<Napi::Number>().DoubleValue()));
297
320
  auto addr = static_cast<std::uintptr_t>(info[1].As<Napi::Number>().DoubleValue());
298
321
  std::string type = info[2].As<Napi::String>().Utf8Value();
322
+ // `value` must match the type family: number for int32/uint32/float/double;
323
+ // number|bigint for int64/uint64/pointer (converted via JsToU64 - the TS
324
+ // signature declares `number|bigint`). The old As<BigInt>() on a Number was
325
+ // an unchecked static_cast: Int64Value() failed, the out-param stayed
326
+ // uninitialized and WriteProcessMemory wrote stack garbage. No C++
327
+ // exception is thrown under NAPI_DISABLE_CPP_EXCEPTIONS, so catch(...)
328
+ // could not save it - validate BEFORE converting.
329
+ const bool is64 = (type == "int64" || type == "uint64" || type == "pointer");
330
+ const bool is32 = (type == "int32" || type == "uint32" || type == "float" || type == "double");
331
+ if (is32 && !info[3].IsNumber()) {
332
+ Napi::TypeError::New(env, "value must be a number for type " + type).ThrowAsJavaScriptException();
333
+ return env.Null();
334
+ }
335
+ std::uint64_t u64 = 0;
336
+ if (is64 && !JsToU64(info[3], u64)) {
337
+ Napi::TypeError::New(env, "value must be number|bigint for type " + type).ThrowAsJavaScriptException();
338
+ return env.Null();
339
+ }
299
340
  bool ok = false;
300
341
  try {
301
342
  if (type == "int32") ok = WriteValue<std::int32_t>(h, addr, info[3].As<Napi::Number>().Int32Value());
302
343
  else if (type == "uint32") ok = WriteValue<std::uint32_t>(h, addr, info[3].As<Napi::Number>().Uint32Value());
303
344
  else if (type == "float") ok = WriteValue<float>(h, addr, info[3].As<Napi::Number>().FloatValue());
304
345
  else if (type == "double") ok = WriteValue<double>(h, addr, info[3].As<Napi::Number>().DoubleValue());
305
- else if (type == "int64") { bool ls; ok = WriteValue<std::int64_t>(h, addr, info[3].As<Napi::BigInt>().Int64Value(&ls)); }
306
- else if (type == "uint64") { bool ls; ok = WriteValue<std::uint64_t>(h, addr, info[3].As<Napi::BigInt>().Uint64Value(&ls)); }
307
- else if (type == "pointer") { bool ls; ok = WriteValue<std::uintptr_t>(h, addr, static_cast<std::uintptr_t>(info[3].As<Napi::BigInt>().Uint64Value(&ls))); }
346
+ else if (type == "int64") ok = WriteValue<std::int64_t>(h, addr, static_cast<std::int64_t>(u64));
347
+ else if (type == "uint64") ok = WriteValue<std::uint64_t>(h, addr, u64);
348
+ else if (type == "pointer") ok = WriteValue<std::uintptr_t>(h, addr, static_cast<std::uintptr_t>(u64));
308
349
  else { Napi::TypeError::New(env, "unknown type " + type).ThrowAsJavaScriptException(); return env.Null(); }
309
350
  return Napi::Boolean::New(env, ok);
310
351
  } catch (...) {
@@ -322,7 +363,9 @@ Napi::Value WriteValueSync(const Napi::CallbackInfo& info) {
322
363
  // gaps/unreadable spans. Only throws if the handle is invalid for VirtualQueryEx.
323
364
  Napi::Value ReadSafeSync(const Napi::CallbackInfo& info) {
324
365
  Napi::Env env = info.Env();
325
- if (info.Length() < 3) { Napi::TypeError::New(env, "expected (handle,address,size)").ThrowAsJavaScriptException(); return env.Null(); }
366
+ if (info.Length() < 3 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsNumber()) {
367
+ Napi::TypeError::New(env, "expected (handle:number,address:number,size:number)").ThrowAsJavaScriptException(); return env.Null();
368
+ }
326
369
  HANDLE h = reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(info[0].As<Napi::Number>().DoubleValue()));
327
370
  auto addr = static_cast<std::uintptr_t>(info[1].As<Napi::Number>().DoubleValue());
328
371
  double sizeD = info[2].As<Napi::Number>().DoubleValue();
@@ -374,7 +417,9 @@ Napi::Value ReadSafeSync(const Napi::CallbackInfo& info) {
374
417
  // flushInstructions(handle, address, size) -> boolean (FlushInstructionCache)
375
418
  Napi::Value FlushInstructionsSync(const Napi::CallbackInfo& info) {
376
419
  Napi::Env env = info.Env();
377
- if (info.Length() < 3) { Napi::TypeError::New(env, "expected (handle,address,size)").ThrowAsJavaScriptException(); return env.Null(); }
420
+ if (info.Length() < 3 || !info[0].IsNumber() || !info[1].IsNumber() || !info[2].IsNumber()) {
421
+ Napi::TypeError::New(env, "expected (handle:number,address:number,size:number)").ThrowAsJavaScriptException(); return env.Null();
422
+ }
378
423
  HANDLE h = reinterpret_cast<HANDLE>(static_cast<std::uintptr_t>(info[0].As<Napi::Number>().DoubleValue()));
379
424
  auto addr = static_cast<std::uintptr_t>(info[1].As<Napi::Number>().DoubleValue());
380
425
  auto size = static_cast<SIZE_T>(info[2].As<Napi::Number>().DoubleValue());
@@ -744,12 +789,6 @@ Napi::Value GetThreadContextSync(const Napi::CallbackInfo& info) {
744
789
  return o;
745
790
  }
746
791
 
747
- static bool JsToU64(const Napi::Value& v, std::uint64_t& out) {
748
- if (v.IsBigInt()) { bool ls; out = v.As<Napi::BigInt>().Uint64Value(&ls); return true; }
749
- if (v.IsNumber()) { out = static_cast<std::uint64_t>(v.As<Napi::Number>().DoubleValue()); return true; }
750
- return false;
751
- }
752
-
753
792
  // setThreadContext(tid, { rip?, rsp?, rbp?, dr0?..dr3?, dr6?, dr7?, eflags? }) -> boolean
754
793
  // Reads the current context, patches provided fields, writes it back.
755
794
  Napi::Value SetThreadContextSync(const Napi::CallbackInfo& info) {
package/native/pattern.cc CHANGED
@@ -219,8 +219,8 @@ private:
219
219
  Napi::Value ScanAsync(const Napi::CallbackInfo& info) {
220
220
  Napi::Env env = info.Env();
221
221
  // scan(pid, pattern[, {start,end,onlyReadable}]) -> Promise<bigint[]>
222
- if (info.Length() < 2) {
223
- Napi::TypeError::New(env, "expected (pid, pattern[, opts])").ThrowAsJavaScriptException();
222
+ if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsString()) {
223
+ Napi::TypeError::New(env, "expected (pid:number, pattern:string[, opts])").ThrowAsJavaScriptException();
224
224
  return env.Null();
225
225
  }
226
226
  DWORD pid = info[0].As<Napi::Number>().Uint32Value();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memoryprocess/bindings",
3
- "version": "0.0.3-beta.2",
3
+ "version": "0.1.0-beta.0",
4
4
  "description": "Native N-API bindings for Windows process memory access (read/write, VirtualQueryEx, pattern scan, thread ops). The .node binary layer of the memoryprocess toolkit.",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.4.4",
@@ -16,9 +16,12 @@
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
+ "src",
19
20
  "prebuilds",
20
21
  "binding.gyp",
21
- "native"
22
+ "native",
23
+ "LICENSE",
24
+ "README.md"
22
25
  ],
23
26
  "os": [
24
27
  "win32"
@@ -31,8 +34,10 @@
31
34
  "clean": "node scripts/clean.mjs",
32
35
  "check": "bun check",
33
36
  "typecheck": "tsc --noEmit",
34
- "prebuild:all": "prebuildify --napi -t 22.17.0 -t 24.4.0 -t 25.0.0 -t 26.0.0",
35
- "prepublishOnly": "npm run build && npm run prebuild:all"
37
+ "prebuild:all": "prebuildify --napi -t 22.17.0 -t 24.4.0 -t 25.0.0 -t 26.0.0"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
36
41
  },
37
42
  "repository": {
38
43
  "type": "git",
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @memoryprocess/bindings -- the native N-API layer.
3
+ * Exposes the raw `.node` module plus its TypeScript interface.
4
+ * Most consumers want `@memoryprocess/core` (the Process class) instead.
5
+ */
6
+ export * from './native.js';
package/src/native.ts ADDED
@@ -0,0 +1,209 @@
1
+ import { createRequire } from 'node:module';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const require = createRequire(import.meta.url);
6
+ const __dir = dirname(fileURLToPath(import.meta.url));
7
+
8
+ interface Native {
9
+ openProcess(
10
+ nameOrPid: string | number,
11
+ access?: number,
12
+ ): { handle: number; pid: number };
13
+ closeHandle(handle: number): boolean;
14
+ listProcesses(): { pid: number; name: string }[];
15
+ getModules(
16
+ handle: number,
17
+ pid: number,
18
+ ): { base: number; size: number; path: string }[];
19
+ getRegions(
20
+ handle: number,
21
+ ): { baseAddress: number; size: number; protection: number; type: number }[];
22
+ readValue(handle: number, address: number, type: string): number | bigint;
23
+ writeValue(
24
+ handle: number,
25
+ address: number,
26
+ type: string,
27
+ value: number | bigint,
28
+ ): boolean;
29
+ readBuffer(handle: number, address: number, size: number): Buffer;
30
+ writeBuffer(handle: number, address: number, data: Buffer): boolean;
31
+ scan(
32
+ pid: number,
33
+ pattern: string,
34
+ opts?: { start?: number; end?: number; onlyReadable?: boolean },
35
+ ): Promise<bigint[]>;
36
+ scanValue(
37
+ pid: number,
38
+ needle: Uint8Array,
39
+ opts?: {
40
+ start?: number;
41
+ end?: number;
42
+ onlyWritable?: boolean;
43
+ alignment?: number;
44
+ },
45
+ ): Promise<bigint[]>;
46
+ allocMemory(
47
+ handle: number,
48
+ size: number,
49
+ protection: number,
50
+ nearAddress?: number,
51
+ ): bigint;
52
+ freeMemory(handle: number, address: number): boolean;
53
+ protectMemory(
54
+ handle: number,
55
+ address: number,
56
+ size: number,
57
+ protection: number,
58
+ ): number;
59
+ isWow64(handle: number): boolean;
60
+ listThreads(pid: number): number[];
61
+ suspendThread(tid: number): boolean;
62
+ resumeThread(tid: number): boolean;
63
+ readSafe(handle: number, address: number, size: number): Buffer;
64
+ flushInstructions(handle: number, address: number, size: number): boolean;
65
+ injectDll(pid: number, dllPath: string, timeoutMs?: number): boolean;
66
+ ejectDll(pid: number, moduleBase: number): boolean;
67
+ queryAddress(
68
+ handle: number,
69
+ address: number,
70
+ ): {
71
+ baseAddress: number;
72
+ allocationBase: number;
73
+ size: number;
74
+ protection: number;
75
+ state: number;
76
+ type: number;
77
+ } | null;
78
+ getThreadContext(tid: number): {
79
+ rip: bigint;
80
+ rsp: bigint;
81
+ rbp: bigint;
82
+ dr0: bigint;
83
+ dr1: bigint;
84
+ dr2: bigint;
85
+ dr3: bigint;
86
+ dr6: bigint;
87
+ dr7: bigint;
88
+ eflags: bigint;
89
+ } | null;
90
+ setThreadContext(
91
+ tid: number,
92
+ ctx: Partial<
93
+ Record<
94
+ | 'rip'
95
+ | 'rsp'
96
+ | 'rbp'
97
+ | 'dr0'
98
+ | 'dr1'
99
+ | 'dr2'
100
+ | 'dr3'
101
+ | 'dr6'
102
+ | 'dr7'
103
+ | 'eflags',
104
+ number | bigint
105
+ >
106
+ >,
107
+ ): boolean;
108
+ callRemote(
109
+ pid: number,
110
+ fnAddress: number | bigint,
111
+ param: number | bigint,
112
+ timeoutMs?: number,
113
+ ): number | null;
114
+ disasmInstructions(
115
+ handle: number,
116
+ address: number,
117
+ count: number,
118
+ ): { offset: number; length: number; relative: boolean }[];
119
+ /**
120
+ * Walk instructions at `address` until `minBytes` are covered, then
121
+ * RELOCATE position-relative operands (rel32 branch/call immediates,
122
+ * RIP-relative disp32) so the stolen bytes are correct when executed from
123
+ * `stubAddr` (must be within rel32 range of `address`). External rel8/16
124
+ * branches (incl. LOOP/JECXZ) can't be widened -- refused `HOOK_RELOCATE`,
125
+ * like MinHook; rel8/16 internal (target inside the stolen window) and
126
+ * all non-relative instructions copy verbatim. Without `stubAddr` any
127
+ * position-relative instruction is refused (inspection mode).
128
+ * Throws `HOOK_STEAL`/`HOOK_RELOCATE` -- returns the relocated bytes.
129
+ */
130
+ hookSteal(
131
+ handle: number,
132
+ address: number,
133
+ minBytesOrOpts?: number | { minBytes?: number; stubAddr?: number | bigint },
134
+ stubAddr?: number | bigint,
135
+ ): { length: number; bytes: Buffer; original: Buffer };
136
+ /**
137
+ * Per-region VirtualProtectEx->write->restore across the whole write span --
138
+ * records each region's real old protection before writing, restores each
139
+ * individually. `restored=false` means part of the span is left weakened.
140
+ */
141
+ writeProtectedMemory(
142
+ handle: number,
143
+ address: number,
144
+ buffer: Buffer,
145
+ protection?: number,
146
+ ): { ok: boolean; restored: boolean; protected: boolean };
147
+ /** Current thread id (TID) -- used to refuse self-suspend. */
148
+ currentThreadId(): number;
149
+ suspendProcess(pid: number): boolean;
150
+ resumeProcess(pid: number): boolean;
151
+ enableDebugPrivilege(): boolean;
152
+ waitForExit(pid: number, timeoutMs?: number): Promise<number | null>;
153
+ findWhatWrites(
154
+ pid: number,
155
+ address: number | bigint,
156
+ size: number,
157
+ timeoutMs: number,
158
+ maxHits: number,
159
+ ): Promise<
160
+ {
161
+ rip: bigint;
162
+ tid: number;
163
+ threadContext: {
164
+ rip: bigint;
165
+ rsp: bigint;
166
+ rbp: bigint;
167
+ dr0: bigint;
168
+ dr1: bigint;
169
+ dr2: bigint;
170
+ dr3: bigint;
171
+ dr6: bigint;
172
+ dr7: bigint;
173
+ eflags: bigint;
174
+ };
175
+ }[]
176
+ >;
177
+ }
178
+
179
+ // Resolution order: dist/memoryprocess.node (built/packaged) -> prebuilds via
180
+ // node-gyp-build -> build/Release/memoryprocess.node (dev). Works whether this
181
+ // file is loaded from src/ (bun test) or bundled into dist/index.js.
182
+ const candidates = [
183
+ join(__dir, 'memoryprocess.node'), // dist/ or src/ (copied for tests)
184
+ join(__dir, '..', 'dist', 'memoryprocess.node'), // src/ -> dist/
185
+ join(__dir, '..', 'build', 'Release', 'memoryprocess.node'), // src/ -> build/
186
+ ];
187
+
188
+ export const native: Native = (() => {
189
+ // Keep the last real load error: a .node that EXISTS but fails to load
190
+ // (missing VC++ CRT, arch or N-API mismatch, wrong Node ABI) must not be
191
+ // reported as "not found" -- surface the underlying loader message.
192
+ let lastErr: unknown;
193
+ for (const path of candidates) {
194
+ try {
195
+ return require(path) as Native;
196
+ } catch (e) {
197
+ lastErr = e;
198
+ }
199
+ }
200
+ try {
201
+ return require('node-gyp-build')(join(__dir, '..')) as Native;
202
+ } catch (e) {
203
+ lastErr = lastErr ?? e;
204
+ }
205
+ const detail = lastErr instanceof Error ? `: ${lastErr.message}` : '';
206
+ throw new Error(
207
+ `memoryprocess native addon failed to load (run \`bun run build\`)${detail}`,
208
+ );
209
+ })();