jsilnet 0.0.0-stage → 0.2.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 +98 -3
- package/REFERENCE.md +191 -0
- package/dist/browser/index.js +345 -0
- package/dist/browser/worker.js +42480 -0
- package/example/README.md +22 -0
- package/example/Sample.cs +10 -0
- package/example/Sample.csproj +7 -0
- package/example/edit-constant.js +48 -0
- package/example/inspect.js +37 -0
- package/package.json +61 -6
- package/src/assembly/instruction-editing.js +165 -0
- package/src/assembly/method-removal.js +789 -0
- package/src/assembly/pointer-table-editing.js +506 -0
- package/src/assembly/portable-pdb-editing.js +441 -0
- package/src/binary/errors.js +17 -0
- package/src/binary/reader.js +206 -0
- package/src/browser/index.d.ts +123 -0
- package/src/browser/index.js +373 -0
- package/src/browser/worker-runtime.js +287 -0
- package/src/browser/worker.js +47 -0
- package/src/il/body.js +165 -0
- package/src/il/instructions.js +217 -0
- package/src/il/opcodes.js +102 -0
- package/src/il/rewrite.js +331 -0
- package/src/index.d.ts +267 -0
- package/src/index.js +13307 -0
- package/src/metadata/classic-pdb.js +2530 -0
- package/src/metadata/custom-attributes.js +387 -0
- package/src/metadata/portable-pdb.js +3347 -0
- package/src/metadata/resources.js +37 -0
- package/src/metadata/root-writer.js +54 -0
- package/src/metadata/signatures.js +339 -0
- package/src/metadata/streams.js +285 -0
- package/src/metadata/strong-name.js +230 -0
- package/src/metadata/tables.js +390 -0
- package/src/metadata/writer.js +191 -0
- package/src/pe/headers.js +491 -0
- package/src/platform/bytes.js +21 -0
- package/src/platform/compression.js +24 -0
- package/src/platform/crypto.js +17 -0
- package/src/resolution/module.js +64 -0
- package/src/types/assembly/instruction-editing.d.ts +33 -0
- package/src/types/assembly/method-removal.d.ts +3 -0
- package/src/types/assembly/pointer-table-editing.d.ts +3 -0
- package/src/types/assembly/portable-pdb-editing.d.ts +23 -0
- package/src/types/binary/errors.d.ts +7 -0
- package/src/types/binary/reader.d.ts +30 -0
- package/src/types/il/body.d.ts +51 -0
- package/src/types/il/instructions.d.ts +33 -0
- package/src/types/il/opcodes.d.ts +2 -0
- package/src/types/il/rewrite.d.ts +38 -0
- package/src/types/index.d.ts +864 -0
- package/src/types/metadata/classic-pdb.d.ts +688 -0
- package/src/types/metadata/custom-attributes.d.ts +28 -0
- package/src/types/metadata/portable-pdb.d.ts +296 -0
- package/src/types/metadata/resources.d.ts +1 -0
- package/src/types/metadata/root-writer.d.ts +1 -0
- package/src/types/metadata/signatures.d.ts +5 -0
- package/src/types/metadata/streams.d.ts +26 -0
- package/src/types/metadata/strong-name.d.ts +11 -0
- package/src/types/metadata/tables.d.ts +25 -0
- package/src/types/metadata/writer.d.ts +16 -0
- package/src/types/pe/headers.d.ts +160 -0
- package/src/types/platform/bytes.d.ts +3 -0
- package/src/types/platform/compression.d.ts +2 -0
- package/src/types/platform/crypto.d.ts +4 -0
- package/src/types/resolution/module.d.ts +12 -0
- package/src/types/writer/patch.d.ts +4 -0
- package/src/types/writer/pe.d.ts +9 -0
- package/src/types/writer/structural.d.ts +4 -0
- package/src/writer/patch.js +42 -0
- package/src/writer/pe.js +196 -0
- package/src/writer/structural.js +241 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Assembly as NodeAssembly } from "../index.js";
|
|
2
|
+
|
|
3
|
+
type Browserify<T> = T extends NodeAssembly
|
|
4
|
+
? BrowserAssembly
|
|
5
|
+
: T extends (...args: infer Args) => infer Result
|
|
6
|
+
? (...args: { [K in keyof Args]: Browserify<Args[K]> }) =>
|
|
7
|
+
Browserify<Result> | Promise<Browserify<Result>>
|
|
8
|
+
: T extends readonly unknown[]
|
|
9
|
+
? { [K in keyof T]: Browserify<T[K]> }
|
|
10
|
+
: T extends object
|
|
11
|
+
? { [K in keyof T]: Browserify<T[K]> }
|
|
12
|
+
: T;
|
|
13
|
+
|
|
14
|
+
type AsyncMethods<T> = {
|
|
15
|
+
[K in keyof T as T[K] extends (...args: infer _Args) => unknown ? K : never]:
|
|
16
|
+
T[K] extends (...args: infer Args) => infer Result
|
|
17
|
+
? (...args: { [I in keyof Args]: Browserify<Args[I]> }) => Promise<Browserify<Result>>
|
|
18
|
+
: never;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
type ResolverMethodName =
|
|
22
|
+
| "parseCustomAttribute"
|
|
23
|
+
| "parseDeclSecurity"
|
|
24
|
+
| "replaceCustomAttribute"
|
|
25
|
+
| "addCustomAttribute"
|
|
26
|
+
| "insertCustomAttribute"
|
|
27
|
+
| "addDeclSecurity"
|
|
28
|
+
| "insertDeclSecurity"
|
|
29
|
+
| "replaceDeclSecurity";
|
|
30
|
+
|
|
31
|
+
type MethodResult<K extends keyof NodeAssembly> =
|
|
32
|
+
NodeAssembly[K] extends (...args: any[]) => infer Result
|
|
33
|
+
? Result extends NodeAssembly ? BrowserAssembly : Result
|
|
34
|
+
: unknown;
|
|
35
|
+
|
|
36
|
+
export interface BrowserTypeResolverContext {
|
|
37
|
+
assembly: BrowserAssembly;
|
|
38
|
+
reference: { table: number; rid: number } | null;
|
|
39
|
+
assemblyReference: unknown;
|
|
40
|
+
assemblyIdentity: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface BrowserResolverOptions {
|
|
44
|
+
resolveEnum?: (name: string) => number | undefined | Promise<number | undefined>;
|
|
45
|
+
resolveType?: (
|
|
46
|
+
name: string,
|
|
47
|
+
context: BrowserTypeResolverContext
|
|
48
|
+
) =>
|
|
49
|
+
| { assembly: BrowserAssembly; rid: number }
|
|
50
|
+
| null
|
|
51
|
+
| undefined
|
|
52
|
+
| Promise<{ assembly: BrowserAssembly; rid: number } | null | undefined>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface BrowserAssemblyState extends Readonly<Record<string, unknown>> {
|
|
56
|
+
bytes: Uint8Array;
|
|
57
|
+
modified: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type BrowserAssembly = Omit<AsyncMethods<NodeAssembly>, ResolverMethodName> & {
|
|
61
|
+
parseCustomAttribute(
|
|
62
|
+
rid: number,
|
|
63
|
+
options?: BrowserResolverOptions
|
|
64
|
+
): Promise<MethodResult<"parseCustomAttribute">>;
|
|
65
|
+
parseDeclSecurity(
|
|
66
|
+
rid: number,
|
|
67
|
+
options?: BrowserResolverOptions
|
|
68
|
+
): Promise<MethodResult<"parseDeclSecurity">>;
|
|
69
|
+
replaceCustomAttribute(
|
|
70
|
+
rid: number,
|
|
71
|
+
value: unknown,
|
|
72
|
+
options?: BrowserResolverOptions
|
|
73
|
+
): Promise<MethodResult<"replaceCustomAttribute">>;
|
|
74
|
+
addCustomAttribute(value: {
|
|
75
|
+
parent: unknown;
|
|
76
|
+
constructor: unknown;
|
|
77
|
+
value: unknown;
|
|
78
|
+
} & BrowserResolverOptions): Promise<MethodResult<"addCustomAttribute">>;
|
|
79
|
+
insertCustomAttribute(value: {
|
|
80
|
+
parent: unknown;
|
|
81
|
+
constructor: unknown;
|
|
82
|
+
value: unknown;
|
|
83
|
+
} & BrowserResolverOptions): Promise<MethodResult<"insertCustomAttribute">>;
|
|
84
|
+
addDeclSecurity(value: {
|
|
85
|
+
parent: unknown;
|
|
86
|
+
action: number;
|
|
87
|
+
permissionSet: unknown;
|
|
88
|
+
} & BrowserResolverOptions): Promise<MethodResult<"addDeclSecurity">>;
|
|
89
|
+
insertDeclSecurity(value: {
|
|
90
|
+
parent: unknown;
|
|
91
|
+
action: number;
|
|
92
|
+
permissionSet: unknown;
|
|
93
|
+
} & BrowserResolverOptions): Promise<MethodResult<"insertDeclSecurity">>;
|
|
94
|
+
replaceDeclSecurity(
|
|
95
|
+
rid: number,
|
|
96
|
+
value?: {
|
|
97
|
+
action?: number;
|
|
98
|
+
permissionSet?: unknown;
|
|
99
|
+
} & BrowserResolverOptions
|
|
100
|
+
): Promise<MethodResult<"replaceDeclSecurity">>;
|
|
101
|
+
getState(): Promise<BrowserAssemblyState>;
|
|
102
|
+
callUtility(name: string, ...args: unknown[]): Promise<unknown>;
|
|
103
|
+
dispose(): void;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export type Assembly = BrowserAssembly;
|
|
107
|
+
|
|
108
|
+
export declare const BrowserAssembly: {
|
|
109
|
+
from(
|
|
110
|
+
input: Uint8Array | ArrayBuffer | Blob,
|
|
111
|
+
options?: { workerFactory?: () => Worker; transferInput?: boolean }
|
|
112
|
+
): Promise<BrowserAssembly>;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export declare const Assembly: typeof BrowserAssembly;
|
|
116
|
+
|
|
117
|
+
export declare function parseAssembly(
|
|
118
|
+
input: Uint8Array | ArrayBuffer | Blob,
|
|
119
|
+
options?: { workerFactory?: () => Worker; transferInput?: boolean }
|
|
120
|
+
): Promise<BrowserAssembly>;
|
|
121
|
+
|
|
122
|
+
export declare function callUtility(name: string, ...args: unknown[]): Promise<unknown>;
|
|
123
|
+
export { FormatError, UnsupportedFormatError } from "../index.js";
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { FormatError, UnsupportedFormatError } from "../binary/errors.js";
|
|
2
|
+
|
|
3
|
+
let nextRequestId = 0;
|
|
4
|
+
|
|
5
|
+
function createWorker(workerFactory) {
|
|
6
|
+
if (workerFactory) return workerFactory();
|
|
7
|
+
if (typeof Worker === "undefined") {
|
|
8
|
+
throw new Error("jsILNET browser APIs require Web Worker support");
|
|
9
|
+
}
|
|
10
|
+
return new Worker(new URL("./worker.js", import.meta.url), { type: "module" });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeInput(input, transferInput) {
|
|
14
|
+
if (typeof Blob !== "undefined" && input instanceof Blob) {
|
|
15
|
+
return { input, transfer: [] };
|
|
16
|
+
}
|
|
17
|
+
if (input instanceof ArrayBuffer) {
|
|
18
|
+
const buffer = transferInput ? input : input.slice(0);
|
|
19
|
+
return { input: buffer, transfer: [buffer] };
|
|
20
|
+
}
|
|
21
|
+
if (input instanceof Uint8Array) {
|
|
22
|
+
if (transferInput && input.buffer instanceof ArrayBuffer &&
|
|
23
|
+
input.byteOffset === 0 && input.byteLength === input.buffer.byteLength) {
|
|
24
|
+
return { input: input.buffer, transfer: [input.buffer] };
|
|
25
|
+
}
|
|
26
|
+
const bytes = Uint8Array.from(input);
|
|
27
|
+
return { input: bytes.buffer, transfer: [bytes.buffer] };
|
|
28
|
+
}
|
|
29
|
+
throw new TypeError("Assembly input must be a Uint8Array, ArrayBuffer, or Blob");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function encodeCallbacks(value, callbacks, seen = new WeakMap()) {
|
|
33
|
+
if (typeof value === "function") {
|
|
34
|
+
const id = callbacks.size + 1;
|
|
35
|
+
callbacks.set(id, value);
|
|
36
|
+
return { __jsilnetCallback: id };
|
|
37
|
+
}
|
|
38
|
+
if (value === null || typeof value !== "object") return value;
|
|
39
|
+
if (seen.has(value)) return seen.get(value);
|
|
40
|
+
if (typeof Blob !== "undefined" && value instanceof Blob) return value;
|
|
41
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value;
|
|
42
|
+
const output = value instanceof Map
|
|
43
|
+
? new Map()
|
|
44
|
+
: value instanceof Set
|
|
45
|
+
? new Set()
|
|
46
|
+
: Array.isArray(value)
|
|
47
|
+
? []
|
|
48
|
+
: {};
|
|
49
|
+
seen.set(value, output);
|
|
50
|
+
if (value instanceof Map) {
|
|
51
|
+
for (const [key, item] of value) {
|
|
52
|
+
output.set(encodeCallbacks(key, callbacks, seen), encodeCallbacks(item, callbacks, seen));
|
|
53
|
+
}
|
|
54
|
+
} else if (value instanceof Set) {
|
|
55
|
+
for (const item of value) output.add(encodeCallbacks(item, callbacks, seen));
|
|
56
|
+
} else if (Array.isArray(value)) {
|
|
57
|
+
for (const item of value) output.push(encodeCallbacks(item, callbacks, seen));
|
|
58
|
+
} else {
|
|
59
|
+
for (const key of Object.keys(value)) {
|
|
60
|
+
output[key] = encodeCallbacks(value[key], callbacks, seen);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return output;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function decodeAssemblyReferences(value, assembly, seen = new WeakMap()) {
|
|
67
|
+
if (value === null || typeof value !== "object") return value;
|
|
68
|
+
if (value.__jsilnetAssembly === true) return assembly;
|
|
69
|
+
if (seen.has(value)) return seen.get(value);
|
|
70
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value;
|
|
71
|
+
const output = value instanceof Map
|
|
72
|
+
? new Map()
|
|
73
|
+
: value instanceof Set
|
|
74
|
+
? new Set()
|
|
75
|
+
: Array.isArray(value)
|
|
76
|
+
? []
|
|
77
|
+
: {};
|
|
78
|
+
seen.set(value, output);
|
|
79
|
+
if (value instanceof Map) {
|
|
80
|
+
for (const [key, item] of value) {
|
|
81
|
+
output.set(
|
|
82
|
+
decodeAssemblyReferences(key, assembly, seen),
|
|
83
|
+
decodeAssemblyReferences(item, assembly, seen)
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
} else if (value instanceof Set) {
|
|
87
|
+
for (const item of value) output.add(decodeAssemblyReferences(item, assembly, seen));
|
|
88
|
+
} else if (Array.isArray(value)) {
|
|
89
|
+
for (const item of value) output.push(decodeAssemblyReferences(item, assembly, seen));
|
|
90
|
+
} else {
|
|
91
|
+
for (const key of Object.keys(value)) {
|
|
92
|
+
output[key] = decodeAssemblyReferences(value[key], assembly, seen);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return output;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function encodeResolverResult(value, currentAssembly, seen = new WeakMap()) {
|
|
99
|
+
if (value instanceof BrowserAssembly) {
|
|
100
|
+
return value === currentAssembly
|
|
101
|
+
? { __jsilnetAssemblyReference: true }
|
|
102
|
+
: { __jsilnetSerializedAssembly: await value.serialize() };
|
|
103
|
+
}
|
|
104
|
+
if (value === null || typeof value !== "object") {
|
|
105
|
+
if (typeof value === "function" || typeof value === "symbol") {
|
|
106
|
+
throw new TypeError("Resolver results must be structured-cloneable values");
|
|
107
|
+
}
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
if (seen.has(value)) return seen.get(value);
|
|
111
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value;
|
|
112
|
+
const output = value instanceof Map
|
|
113
|
+
? new Map()
|
|
114
|
+
: value instanceof Set
|
|
115
|
+
? new Set()
|
|
116
|
+
: Array.isArray(value)
|
|
117
|
+
? []
|
|
118
|
+
: {};
|
|
119
|
+
seen.set(value, output);
|
|
120
|
+
if (value instanceof Map) {
|
|
121
|
+
for (const [key, item] of value) {
|
|
122
|
+
output.set(
|
|
123
|
+
await encodeResolverResult(key, currentAssembly, seen),
|
|
124
|
+
await encodeResolverResult(item, currentAssembly, seen)
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
} else if (value instanceof Set) {
|
|
128
|
+
for (const item of value) {
|
|
129
|
+
output.add(await encodeResolverResult(item, currentAssembly, seen));
|
|
130
|
+
}
|
|
131
|
+
} else if (Array.isArray(value)) {
|
|
132
|
+
for (const item of value) output.push(await encodeResolverResult(item, currentAssembly, seen));
|
|
133
|
+
} else {
|
|
134
|
+
for (const key of Object.keys(value)) {
|
|
135
|
+
output[key] = await encodeResolverResult(value[key], currentAssembly, seen);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return output;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function makeError(data) {
|
|
142
|
+
const error = data?.name === "FormatError"
|
|
143
|
+
? new FormatError(data.message)
|
|
144
|
+
: data?.name === "UnsupportedFormatError"
|
|
145
|
+
? new UnsupportedFormatError(data.message)
|
|
146
|
+
: new Error(data?.message ?? "Browser worker request failed");
|
|
147
|
+
if (data?.name && data.name !== "FormatError" && data.name !== "UnsupportedFormatError") {
|
|
148
|
+
error.name = data.name;
|
|
149
|
+
}
|
|
150
|
+
if (data && "offset" in data) error.offset = data.offset;
|
|
151
|
+
if (data && "context" in data) error.context = data.context;
|
|
152
|
+
return error;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function addRvaMapper(pe) {
|
|
156
|
+
if (!pe || !Array.isArray(pe.sections) || !Number.isInteger(pe.sectionOffset)) return pe;
|
|
157
|
+
pe.rvaToOffset = (rva, size = 1) => {
|
|
158
|
+
if (!Number.isInteger(rva) || !Number.isInteger(size) || rva < 0 || size < 0) {
|
|
159
|
+
throw new FormatError("Invalid RVA range (RVA mapping)");
|
|
160
|
+
}
|
|
161
|
+
if (rva === 0 && size === 0) return 0;
|
|
162
|
+
for (const section of pe.sections) {
|
|
163
|
+
const span = Math.max(section.virtualSize, section.rawSize);
|
|
164
|
+
if (rva < section.virtualAddress || rva - section.virtualAddress >= span) continue;
|
|
165
|
+
const delta = rva - section.virtualAddress;
|
|
166
|
+
if (delta + size > section.rawSize || section.rawPointer + delta + size > pe.bytes.length) {
|
|
167
|
+
throw new FormatError(
|
|
168
|
+
"RVA range is not backed by file data",
|
|
169
|
+
{ offset: section.rawPointer + delta, context: `section ${section.name}` }
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return section.rawPointer + delta;
|
|
173
|
+
}
|
|
174
|
+
if (rva < pe.sectionOffset && rva + size <= pe.bytes.length) return rva;
|
|
175
|
+
throw new FormatError(`RVA 0x${rva.toString(16)} is not mapped by a PE section (RVA mapping)`);
|
|
176
|
+
};
|
|
177
|
+
return pe;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
class WorkerClient {
|
|
181
|
+
#worker;
|
|
182
|
+
#pending = new Map();
|
|
183
|
+
#onMessage;
|
|
184
|
+
#onError;
|
|
185
|
+
#onMessageError;
|
|
186
|
+
#closed = false;
|
|
187
|
+
|
|
188
|
+
constructor(worker) {
|
|
189
|
+
this.#worker = worker;
|
|
190
|
+
this.#onMessage = (event) => {
|
|
191
|
+
const response = event.data;
|
|
192
|
+
const pending = this.#pending.get(response?.id);
|
|
193
|
+
if (!pending) return;
|
|
194
|
+
if (response.type === "resolver") {
|
|
195
|
+
void this.#resolveCallback(response, pending);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this.#pending.delete(response.id);
|
|
199
|
+
if (response.ok) pending.resolve(response.value);
|
|
200
|
+
else pending.reject(makeError(response.error));
|
|
201
|
+
};
|
|
202
|
+
this.#onError = (event) => {
|
|
203
|
+
const error = event.error ?? new Error(event.message || "Browser worker failed");
|
|
204
|
+
for (const pending of this.#pending.values()) pending.reject(error);
|
|
205
|
+
this.#pending.clear();
|
|
206
|
+
this.#close();
|
|
207
|
+
};
|
|
208
|
+
this.#onMessageError = () => {
|
|
209
|
+
const error = new Error("Browser worker sent an uncloneable response");
|
|
210
|
+
for (const pending of this.#pending.values()) pending.reject(error);
|
|
211
|
+
this.#pending.clear();
|
|
212
|
+
this.#close();
|
|
213
|
+
};
|
|
214
|
+
worker.addEventListener("message", this.#onMessage);
|
|
215
|
+
worker.addEventListener("error", this.#onError);
|
|
216
|
+
worker.addEventListener("messageerror", this.#onMessageError);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
request(type, payload = {}, transfer = [], resolverOptions = {}) {
|
|
220
|
+
if (this.#closed) {
|
|
221
|
+
return Promise.reject(new Error("Browser worker is closed"));
|
|
222
|
+
}
|
|
223
|
+
const id = ++nextRequestId;
|
|
224
|
+
return new Promise((resolve, reject) => {
|
|
225
|
+
const callbacks = new Map();
|
|
226
|
+
const encodedPayload = encodeCallbacks(payload, callbacks);
|
|
227
|
+
this.#pending.set(id, { resolve, reject, callbacks, resolverOptions });
|
|
228
|
+
try {
|
|
229
|
+
this.#worker.postMessage({ id, type, ...encodedPayload }, transfer);
|
|
230
|
+
} catch (error) {
|
|
231
|
+
this.#pending.delete(id);
|
|
232
|
+
if (error?.name === "DataCloneError") {
|
|
233
|
+
reject(new TypeError("Browser Worker request contains a non-cloneable value"));
|
|
234
|
+
} else {
|
|
235
|
+
reject(error);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async #resolveCallback(response, pending) {
|
|
242
|
+
const callback = pending.callbacks.get(response.callbackId);
|
|
243
|
+
try {
|
|
244
|
+
if (!callback) throw new TypeError("Worker requested an unknown resolver callback");
|
|
245
|
+
const args = pending.resolverOptions.decodeArguments
|
|
246
|
+
? pending.resolverOptions.decodeArguments(response.args)
|
|
247
|
+
: response.args;
|
|
248
|
+
const result = await callback(...args);
|
|
249
|
+
if (this.#closed || !this.#pending.has(response.id)) return;
|
|
250
|
+
const value = pending.resolverOptions.encodeResult
|
|
251
|
+
? await pending.resolverOptions.encodeResult(result)
|
|
252
|
+
: result;
|
|
253
|
+
if (this.#closed || !this.#pending.has(response.id)) return;
|
|
254
|
+
this.#worker.postMessage({
|
|
255
|
+
type: "resolverResult",
|
|
256
|
+
id: response.id,
|
|
257
|
+
resolverRequestId: response.resolverRequestId,
|
|
258
|
+
ok: true,
|
|
259
|
+
value
|
|
260
|
+
});
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (this.#closed || !this.#pending.has(response.id)) return;
|
|
263
|
+
this.#worker.postMessage({
|
|
264
|
+
type: "resolverResult",
|
|
265
|
+
id: response.id,
|
|
266
|
+
resolverRequestId: response.resolverRequestId,
|
|
267
|
+
ok: false,
|
|
268
|
+
error: {
|
|
269
|
+
name: error?.name ?? "Error",
|
|
270
|
+
message: error?.message ?? String(error)
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
dispose() {
|
|
277
|
+
if (this.#closed) return;
|
|
278
|
+
for (const pending of this.#pending.values()) {
|
|
279
|
+
pending.reject(new Error("Browser worker was disposed"));
|
|
280
|
+
}
|
|
281
|
+
this.#pending.clear();
|
|
282
|
+
this.#close();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
#close() {
|
|
286
|
+
if (this.#closed) return;
|
|
287
|
+
this.#closed = true;
|
|
288
|
+
this.#worker.removeEventListener("message", this.#onMessage);
|
|
289
|
+
this.#worker.removeEventListener("error", this.#onError);
|
|
290
|
+
this.#worker.removeEventListener("messageerror", this.#onMessageError);
|
|
291
|
+
this.#worker.terminate();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export class BrowserAssembly {
|
|
296
|
+
#client;
|
|
297
|
+
#proxy;
|
|
298
|
+
|
|
299
|
+
constructor(client) {
|
|
300
|
+
this.#client = client;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
static async from(input, { workerFactory, transferInput = false } = {}) {
|
|
304
|
+
const normalizedInput = normalizeInput(input, transferInput);
|
|
305
|
+
const worker = createWorker(workerFactory);
|
|
306
|
+
const client = new WorkerClient(worker);
|
|
307
|
+
const { input: inputValue, transfer } = normalizedInput;
|
|
308
|
+
try {
|
|
309
|
+
await client.request("parse", { input: inputValue }, transfer);
|
|
310
|
+
const instance = new BrowserAssembly(client);
|
|
311
|
+
instance.#proxy = new Proxy(instance, {
|
|
312
|
+
get(target, property, receiver) {
|
|
313
|
+
if (property === "then") return undefined;
|
|
314
|
+
if (Reflect.has(target, property)) {
|
|
315
|
+
const value = Reflect.get(target, property, target);
|
|
316
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
317
|
+
}
|
|
318
|
+
if (typeof property === "string") {
|
|
319
|
+
return (...args) => target.#call(property, args);
|
|
320
|
+
}
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
return instance.#proxy;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
client.dispose();
|
|
327
|
+
throw error;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async getState() {
|
|
332
|
+
return this.#client.request("state");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async callUtility(name, ...args) {
|
|
336
|
+
const result = await this.#client.request("utility", { name, args });
|
|
337
|
+
return name === "parsePE" ? addRvaMapper(result) : result;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async #call(name, args) {
|
|
341
|
+
const result = await this.#client.request(
|
|
342
|
+
"call",
|
|
343
|
+
{ name, args },
|
|
344
|
+
[],
|
|
345
|
+
{
|
|
346
|
+
decodeArguments: (value) => decodeAssemblyReferences(value, this.#proxy),
|
|
347
|
+
encodeResult: (value) => encodeResolverResult(value, this.#proxy)
|
|
348
|
+
}
|
|
349
|
+
);
|
|
350
|
+
return result?.__jsilnetAssembly === true ? this.#proxy : result;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
dispose() {
|
|
354
|
+
this.#client.dispose();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export async function parseAssembly(input, options) {
|
|
359
|
+
return BrowserAssembly.from(input, options);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export async function callUtility(name, ...args) {
|
|
363
|
+
const worker = createWorker();
|
|
364
|
+
const client = new WorkerClient(worker);
|
|
365
|
+
try {
|
|
366
|
+
const result = await client.request("utility", { name, args });
|
|
367
|
+
return name === "parsePE" ? addRvaMapper(result) : result;
|
|
368
|
+
} finally {
|
|
369
|
+
client.dispose();
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export { BrowserAssembly as Assembly, FormatError, UnsupportedFormatError };
|