@cheatron/native-mock 1.0.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 +21 -0
- package/README.md +21 -0
- package/dist/constants.d.ts +175 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +173 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/kernel32.d.ts +22 -0
- package/dist/kernel32.d.ts.map +1 -0
- package/dist/kernel32.js +170 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +2 -0
- package/dist/os/handles.d.ts +17 -0
- package/dist/os/handles.d.ts.map +1 -0
- package/dist/os/handles.js +21 -0
- package/dist/os/kernel.d.ts +18 -0
- package/dist/os/kernel.d.ts.map +1 -0
- package/dist/os/kernel.js +50 -0
- package/dist/os/memory.d.ts +24 -0
- package/dist/os/memory.d.ts.map +1 -0
- package/dist/os/memory.js +147 -0
- package/dist/os/process.d.ts +17 -0
- package/dist/os/process.d.ts.map +1 -0
- package/dist/os/process.js +35 -0
- package/dist/os/thread.d.ts +40 -0
- package/dist/os/thread.d.ts.map +1 -0
- package/dist/os/thread.js +64 -0
- package/dist/process.d.ts +30 -0
- package/dist/process.d.ts.map +1 -0
- package/dist/process.js +124 -0
- package/dist/thread.d.ts +28 -0
- package/dist/thread.d.ts.map +1 -0
- package/dist/thread.js +113 -0
- package/package.json +59 -0
package/dist/thread.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Kernel32Impl as Kernel32 } from './kernel32';
|
|
2
|
+
import { ThreadAccess, ContextFlags, CONTEXT, CONTEXT_SIZE, } from './constants';
|
|
3
|
+
import koffi from 'koffi';
|
|
4
|
+
import { log } from './logger';
|
|
5
|
+
/**
|
|
6
|
+
* Handle management registry for automatic cleanup
|
|
7
|
+
*/
|
|
8
|
+
const registry = new FinalizationRegistry((handle) => {
|
|
9
|
+
if (handle) {
|
|
10
|
+
log.trace('Thread', 'Closing orphaned handle via GC');
|
|
11
|
+
Kernel32.CloseHandle(handle);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Represents a thread handle
|
|
16
|
+
*/
|
|
17
|
+
export class Thread {
|
|
18
|
+
_handle;
|
|
19
|
+
constructor(handle, autoClose = true) {
|
|
20
|
+
this._handle = handle;
|
|
21
|
+
if (autoClose && handle) {
|
|
22
|
+
registry.register(this, handle, this);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
static open(threadId, access = ThreadAccess.ALL_ACCESS) {
|
|
26
|
+
log.debug('Thread', `Opening thread ${threadId}`, { access });
|
|
27
|
+
const handle = Kernel32.OpenThread(access, 0, threadId);
|
|
28
|
+
if (!handle) {
|
|
29
|
+
log.error('Thread', `Failed to open thread ${threadId}`);
|
|
30
|
+
throw new Error(`Failed to open thread ${threadId}`);
|
|
31
|
+
}
|
|
32
|
+
return new Thread(handle);
|
|
33
|
+
}
|
|
34
|
+
static current() {
|
|
35
|
+
return currentThread;
|
|
36
|
+
}
|
|
37
|
+
static currentId() {
|
|
38
|
+
return Kernel32.GetCurrentThreadId();
|
|
39
|
+
}
|
|
40
|
+
isValid() {
|
|
41
|
+
return this._handle !== null && this._handle !== undefined;
|
|
42
|
+
}
|
|
43
|
+
close() {
|
|
44
|
+
if (this.isValid()) {
|
|
45
|
+
log.debug('Thread', 'Closing thread handle');
|
|
46
|
+
const h = this._handle;
|
|
47
|
+
this._handle = null;
|
|
48
|
+
registry.unregister(this);
|
|
49
|
+
Kernel32.CloseHandle(h);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
suspend() {
|
|
53
|
+
if (!this.isValid())
|
|
54
|
+
throw new Error('Thread handle is closed');
|
|
55
|
+
const count = Kernel32.SuspendThread(this._handle);
|
|
56
|
+
if (count === 0xffffffff) {
|
|
57
|
+
log.error('Thread', 'SuspendThread failed');
|
|
58
|
+
throw new Error('SuspendThread failed');
|
|
59
|
+
}
|
|
60
|
+
return count;
|
|
61
|
+
}
|
|
62
|
+
resume() {
|
|
63
|
+
if (!this.isValid())
|
|
64
|
+
throw new Error('Thread handle is closed');
|
|
65
|
+
const count = Kernel32.ResumeThread(this._handle);
|
|
66
|
+
if (count === 0xffffffff) {
|
|
67
|
+
log.error('Thread', 'ResumeThread failed');
|
|
68
|
+
throw new Error('ResumeThread failed');
|
|
69
|
+
}
|
|
70
|
+
return count;
|
|
71
|
+
}
|
|
72
|
+
getContext(flags = ContextFlags.FULL) {
|
|
73
|
+
if (!this.isValid())
|
|
74
|
+
throw new Error('Thread handle is closed');
|
|
75
|
+
const buf = Buffer.alloc(CONTEXT_SIZE);
|
|
76
|
+
// ContextFlags is at offset 0x30 (after 6 × uint64 P*Home registers)
|
|
77
|
+
buf.writeUInt32LE(flags, 0x30);
|
|
78
|
+
const success = Kernel32.GetThreadContext(this._handle, buf);
|
|
79
|
+
if (!success) {
|
|
80
|
+
log.error('Thread', 'GetThreadContext failed');
|
|
81
|
+
throw new Error('GetThreadContext failed');
|
|
82
|
+
}
|
|
83
|
+
return koffi.decode(buf, CONTEXT);
|
|
84
|
+
}
|
|
85
|
+
setContext(ctx) {
|
|
86
|
+
if (!this.isValid())
|
|
87
|
+
throw new Error('Thread handle is closed');
|
|
88
|
+
const buf = Buffer.alloc(CONTEXT_SIZE);
|
|
89
|
+
koffi.encode(buf, CONTEXT, ctx);
|
|
90
|
+
const success = Kernel32.SetThreadContext(this._handle, buf);
|
|
91
|
+
if (!success) {
|
|
92
|
+
log.error('Thread', 'SetThreadContext failed');
|
|
93
|
+
throw new Error('SetThreadContext failed');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
get handle() {
|
|
97
|
+
return this._handle;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Represents the current thread (singleton)
|
|
102
|
+
*/
|
|
103
|
+
export class CurrentThread extends Thread {
|
|
104
|
+
constructor() {
|
|
105
|
+
// Current thread uses a pseudo-handle that doesn't need closing
|
|
106
|
+
super(Kernel32.GetCurrentThread(), false);
|
|
107
|
+
}
|
|
108
|
+
close() {
|
|
109
|
+
this._handle = null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Export a pre-initialized instance of the current thread
|
|
113
|
+
export const currentThread = new CurrentThread();
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cheatron/native-mock",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pure JavaScript mock implementation of Win32 native modules for testing without native dependencies",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"cheatron",
|
|
15
|
+
"win32",
|
|
16
|
+
"native",
|
|
17
|
+
"mock",
|
|
18
|
+
"kernel32",
|
|
19
|
+
"simulation",
|
|
20
|
+
"testing"
|
|
21
|
+
],
|
|
22
|
+
"author": "Cheatron",
|
|
23
|
+
"homepage": "https://github.com/Cheatron/cheatron-native-mock#readme",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/Cheatron/cheatron-native-mock.git"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/Cheatron/cheatron-native-mock/issues"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@cheatron/log": "^1.0.1",
|
|
33
|
+
"@cheatron/native-mock-koffi": "^1.0.0",
|
|
34
|
+
"win32-def": "^26.1.2"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@eslint/js": "^10.0.1",
|
|
38
|
+
"@types/bun": "latest",
|
|
39
|
+
"add": "^2.0.6",
|
|
40
|
+
"eslint": "^10.0.0",
|
|
41
|
+
"eslint-config-prettier": "^10.1.8",
|
|
42
|
+
"globals": "^17.3.0",
|
|
43
|
+
"prettier": "^3.8.1",
|
|
44
|
+
"typescript": "^5",
|
|
45
|
+
"typescript-eslint": "^8.56.0"
|
|
46
|
+
},
|
|
47
|
+
"overrides": {
|
|
48
|
+
"koffi": "npm:@cheatron/native-mock-koffi@^1.0.0"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc",
|
|
52
|
+
"prepublishOnly": "bun run build",
|
|
53
|
+
"test": "bun test",
|
|
54
|
+
"lint": "eslint \"src/**/*.{js,ts,jsx,tsx}\" --cache",
|
|
55
|
+
"lint:fix": "eslint \"src/**/*.{js,ts,jsx,tsx}\" --fix",
|
|
56
|
+
"format": "prettier --write \"{src,tests}/**/*.{js,ts,jsx,tsx,css,md,json}\"",
|
|
57
|
+
"format:check": "prettier --check \"{src,tests}/**/*.{js,ts,jsx,tsx,css,md,json}\""
|
|
58
|
+
}
|
|
59
|
+
}
|