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,287 @@
|
|
|
1
|
+
import * as core from "../index.js";
|
|
2
|
+
import { Assembly, parseAssembly } from "../index.js";
|
|
3
|
+
|
|
4
|
+
const assemblyMethods = new Set(
|
|
5
|
+
Object.getOwnPropertyNames(Assembly.prototype).filter((name) => name !== "constructor")
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
function cloneValue(value, assembly, seen = new WeakMap(), functionMode = "omit") {
|
|
9
|
+
if (assembly !== null && value === assembly) return { __jsilnetAssembly: true };
|
|
10
|
+
if (value === null || typeof value !== "object") {
|
|
11
|
+
if (typeof value !== "function") return value;
|
|
12
|
+
if (functionMode === "omit") return undefined;
|
|
13
|
+
if (value.name === "rvaToOffset") return { __jsilnetFunction: "pe.rvaToOffset" };
|
|
14
|
+
throw new TypeError("Browser utility returned an unsupported function-valued property");
|
|
15
|
+
}
|
|
16
|
+
if (seen.has(value)) return seen.get(value);
|
|
17
|
+
if (value instanceof ArrayBuffer) return value.slice(0);
|
|
18
|
+
if (ArrayBuffer.isView(value)) {
|
|
19
|
+
if (value instanceof DataView) {
|
|
20
|
+
return new DataView(value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength));
|
|
21
|
+
}
|
|
22
|
+
if (value instanceof Uint8Array) return Uint8Array.from(value);
|
|
23
|
+
return new value.constructor(value);
|
|
24
|
+
}
|
|
25
|
+
if (value instanceof Map) {
|
|
26
|
+
const output = new Map();
|
|
27
|
+
seen.set(value, output);
|
|
28
|
+
for (const [key, item] of value) {
|
|
29
|
+
output.set(
|
|
30
|
+
cloneValue(key, assembly, seen, functionMode),
|
|
31
|
+
cloneValue(item, assembly, seen, functionMode)
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
36
|
+
if (value instanceof Set) {
|
|
37
|
+
const output = new Set();
|
|
38
|
+
seen.set(value, output);
|
|
39
|
+
for (const item of value) output.add(cloneValue(item, assembly, seen, functionMode));
|
|
40
|
+
return output;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
const output = [];
|
|
44
|
+
seen.set(value, output);
|
|
45
|
+
for (const item of value) output.push(cloneValue(item, assembly, seen, functionMode));
|
|
46
|
+
return output;
|
|
47
|
+
}
|
|
48
|
+
const output = {};
|
|
49
|
+
seen.set(value, output);
|
|
50
|
+
for (const key of Object.keys(value)) {
|
|
51
|
+
const item = cloneValue(value[key], assembly, seen, functionMode);
|
|
52
|
+
if (item !== undefined) output[key] = item;
|
|
53
|
+
}
|
|
54
|
+
return output;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class ResolverRequest extends Error {
|
|
58
|
+
constructor(callbackId, args) {
|
|
59
|
+
super("Browser resolver callback required");
|
|
60
|
+
this.callbackId = callbackId;
|
|
61
|
+
this.args = args;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function stableValue(value, assembly, seen = new WeakSet()) {
|
|
66
|
+
if (assembly !== null && value === assembly) return { assembly: true };
|
|
67
|
+
if (value === null || typeof value !== "object") {
|
|
68
|
+
if (typeof value === "bigint") return `${value}n`;
|
|
69
|
+
if (typeof value === "undefined") return "__undefined";
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
if (seen.has(value)) return "__cycle";
|
|
73
|
+
seen.add(value);
|
|
74
|
+
if (value instanceof ArrayBuffer) return [...new Uint8Array(value)];
|
|
75
|
+
if (ArrayBuffer.isView(value)) {
|
|
76
|
+
return {
|
|
77
|
+
type: value.constructor.name,
|
|
78
|
+
values: [...new Uint8Array(value.buffer, value.byteOffset, value.byteLength)]
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (value instanceof Map) {
|
|
82
|
+
return [...value].map(([key, item]) => [
|
|
83
|
+
stableValue(key, assembly, seen),
|
|
84
|
+
stableValue(item, assembly, seen)
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
87
|
+
if (value instanceof Set) return [...value].map((item) => stableValue(item, assembly, seen));
|
|
88
|
+
if (Array.isArray(value)) return value.map((item) => stableValue(item, assembly, seen));
|
|
89
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [
|
|
90
|
+
key,
|
|
91
|
+
stableValue(value[key], assembly, seen)
|
|
92
|
+
]));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function decodeResolverResult(value, assembly, seen = new WeakMap()) {
|
|
96
|
+
if (value === null || typeof value !== "object") return value;
|
|
97
|
+
if (value.__jsilnetAssemblyReference === true) return assembly;
|
|
98
|
+
if (value.__jsilnetSerializedAssembly instanceof Uint8Array) {
|
|
99
|
+
return parseAssembly(value.__jsilnetSerializedAssembly);
|
|
100
|
+
}
|
|
101
|
+
if (seen.has(value)) return seen.get(value);
|
|
102
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value;
|
|
103
|
+
const output = value instanceof Map
|
|
104
|
+
? new Map()
|
|
105
|
+
: value instanceof Set
|
|
106
|
+
? new Set()
|
|
107
|
+
: Array.isArray(value)
|
|
108
|
+
? []
|
|
109
|
+
: {};
|
|
110
|
+
seen.set(value, output);
|
|
111
|
+
if (value instanceof Map) {
|
|
112
|
+
for (const [key, item] of value) {
|
|
113
|
+
output.set(
|
|
114
|
+
decodeResolverResult(key, assembly, seen),
|
|
115
|
+
decodeResolverResult(item, assembly, seen)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
} else if (value instanceof Set) {
|
|
119
|
+
for (const item of value) output.add(decodeResolverResult(item, assembly, seen));
|
|
120
|
+
} else if (Array.isArray(value)) {
|
|
121
|
+
for (const item of value) output.push(decodeResolverResult(item, assembly, seen));
|
|
122
|
+
} else {
|
|
123
|
+
for (const key of Object.keys(value)) {
|
|
124
|
+
output[key] = decodeResolverResult(value[key], assembly, seen);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return output;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function restoreCallbacks(value, cache, assembly, seen = new WeakMap()) {
|
|
131
|
+
if (value === null || typeof value !== "object") return value;
|
|
132
|
+
if (Number.isInteger(value.__jsilnetCallback)) {
|
|
133
|
+
const callbackId = value.__jsilnetCallback;
|
|
134
|
+
return (...args) => {
|
|
135
|
+
const key = `${callbackId}:${JSON.stringify(stableValue(args, assembly))}`;
|
|
136
|
+
if (cache.has(key)) return cache.get(key);
|
|
137
|
+
throw new ResolverRequest(callbackId, args);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (seen.has(value)) return seen.get(value);
|
|
141
|
+
if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) return value;
|
|
142
|
+
const output = value instanceof Map
|
|
143
|
+
? new Map()
|
|
144
|
+
: value instanceof Set
|
|
145
|
+
? new Set()
|
|
146
|
+
: Array.isArray(value)
|
|
147
|
+
? []
|
|
148
|
+
: {};
|
|
149
|
+
seen.set(value, output);
|
|
150
|
+
if (value instanceof Map) {
|
|
151
|
+
for (const [key, item] of value) {
|
|
152
|
+
output.set(
|
|
153
|
+
restoreCallbacks(key, cache, assembly, seen),
|
|
154
|
+
restoreCallbacks(item, cache, assembly, seen)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
} else if (value instanceof Set) {
|
|
158
|
+
for (const item of value) output.add(restoreCallbacks(item, cache, assembly, seen));
|
|
159
|
+
} else if (Array.isArray(value)) {
|
|
160
|
+
for (const item of value) output.push(restoreCallbacks(item, cache, assembly, seen));
|
|
161
|
+
} else {
|
|
162
|
+
for (const key of Object.keys(value)) {
|
|
163
|
+
output[key] = restoreCallbacks(value[key], cache, assembly, seen);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return output;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function getState(assembly) {
|
|
170
|
+
const state = {};
|
|
171
|
+
for (const key of Object.keys(assembly)) {
|
|
172
|
+
if (!key.startsWith("_")) state[key] = cloneValue(assembly[key], assembly);
|
|
173
|
+
}
|
|
174
|
+
state.bytes = cloneValue(assembly.bytes, assembly);
|
|
175
|
+
state.modified = assembly.modified;
|
|
176
|
+
return state;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function serializeError(error) {
|
|
180
|
+
return {
|
|
181
|
+
name: error?.name ?? "Error",
|
|
182
|
+
message: error?.message ?? String(error),
|
|
183
|
+
offset: error?.offset,
|
|
184
|
+
context: error?.context
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function createWorkerController({ requestResolver } = {}) {
|
|
189
|
+
let assembly = null;
|
|
190
|
+
let nextResolverRequestId = 0;
|
|
191
|
+
async function invokeWithResolvers(message, operation, input, activeAssembly, resolveCallback) {
|
|
192
|
+
const resolverCache = new Map();
|
|
193
|
+
while (true) {
|
|
194
|
+
try {
|
|
195
|
+
return operation(...restoreCallbacks(input, resolverCache, activeAssembly));
|
|
196
|
+
} catch (error) {
|
|
197
|
+
if (!(error instanceof ResolverRequest)) throw error;
|
|
198
|
+
if (typeof resolveCallback !== "function") {
|
|
199
|
+
throw new Error("Browser resolver callback channel is unavailable");
|
|
200
|
+
}
|
|
201
|
+
const resolverRequestId = ++nextResolverRequestId;
|
|
202
|
+
const value = await new Promise((resolve, reject) => {
|
|
203
|
+
resolveCallback(
|
|
204
|
+
message.id,
|
|
205
|
+
resolverRequestId,
|
|
206
|
+
error.callbackId,
|
|
207
|
+
cloneValue(error.args, activeAssembly),
|
|
208
|
+
(response) => {
|
|
209
|
+
if (response.ok) resolve(response.value);
|
|
210
|
+
else {
|
|
211
|
+
const failure = new Error(
|
|
212
|
+
response.error?.message ?? "Resolver callback failed"
|
|
213
|
+
);
|
|
214
|
+
failure.name = response.error?.name ?? "Error";
|
|
215
|
+
reject(failure);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
const key = `${error.callbackId}:${JSON.stringify(
|
|
221
|
+
stableValue(error.args, activeAssembly)
|
|
222
|
+
)}`;
|
|
223
|
+
resolverCache.set(key, decodeResolverResult(value, activeAssembly));
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return async function handle(message, resolveCallback = requestResolver) {
|
|
228
|
+
try {
|
|
229
|
+
if (message?.type === "parse") {
|
|
230
|
+
const input = typeof Blob !== "undefined" && message.input instanceof Blob
|
|
231
|
+
? await message.input.arrayBuffer()
|
|
232
|
+
: message.input;
|
|
233
|
+
assembly = parseAssembly(input);
|
|
234
|
+
return { id: message.id, ok: true, value: null };
|
|
235
|
+
}
|
|
236
|
+
if (message?.type === "dispose") {
|
|
237
|
+
assembly = null;
|
|
238
|
+
return { id: message.id, ok: true, value: null };
|
|
239
|
+
}
|
|
240
|
+
if (message?.type === "state") {
|
|
241
|
+
if (!assembly) throw new Error("No assembly has been loaded");
|
|
242
|
+
return { id: message.id, ok: true, value: getState(assembly) };
|
|
243
|
+
}
|
|
244
|
+
if (message?.type === "call") {
|
|
245
|
+
if (!assembly) throw new Error("No assembly has been loaded");
|
|
246
|
+
if (!assemblyMethods.has(message.name)) {
|
|
247
|
+
throw new TypeError(`Assembly method ${String(message.name)} is not available`);
|
|
248
|
+
}
|
|
249
|
+
const result = await invokeWithResolvers(
|
|
250
|
+
message,
|
|
251
|
+
(...args) => assembly[message.name](...args),
|
|
252
|
+
message.args,
|
|
253
|
+
assembly,
|
|
254
|
+
resolveCallback
|
|
255
|
+
);
|
|
256
|
+
return {
|
|
257
|
+
id: message.id,
|
|
258
|
+
ok: true,
|
|
259
|
+
value: result === assembly
|
|
260
|
+
? { __jsilnetAssembly: true }
|
|
261
|
+
: cloneValue(result, assembly)
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (message?.type === "utility") {
|
|
265
|
+
const operation = core[message.name];
|
|
266
|
+
if (typeof operation !== "function" || message.name === "parseAssembly") {
|
|
267
|
+
throw new TypeError(`Library utility ${String(message.name)} is not available`);
|
|
268
|
+
}
|
|
269
|
+
const result = await invokeWithResolvers(
|
|
270
|
+
message,
|
|
271
|
+
operation,
|
|
272
|
+
message.args,
|
|
273
|
+
assembly,
|
|
274
|
+
resolveCallback
|
|
275
|
+
);
|
|
276
|
+
return {
|
|
277
|
+
id: message.id,
|
|
278
|
+
ok: true,
|
|
279
|
+
value: cloneValue(result, assembly, new WeakMap(), "encode")
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
throw new TypeError("Unknown browser worker request");
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return { id: message?.id, ok: false, error: serializeError(error) };
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createWorkerController } from "./worker-runtime.js";
|
|
2
|
+
|
|
3
|
+
const handle = createWorkerController();
|
|
4
|
+
const pendingResolvers = new Map();
|
|
5
|
+
|
|
6
|
+
function collectTransferables(value, transferables = new Set(), seen = new WeakSet()) {
|
|
7
|
+
if (value === null || typeof value !== "object" || seen.has(value)) return transferables;
|
|
8
|
+
seen.add(value);
|
|
9
|
+
if (value instanceof ArrayBuffer) {
|
|
10
|
+
transferables.add(value);
|
|
11
|
+
} else if (ArrayBuffer.isView(value)) {
|
|
12
|
+
if (value.buffer instanceof ArrayBuffer) transferables.add(value.buffer);
|
|
13
|
+
} else if (value instanceof Map) {
|
|
14
|
+
for (const [key, item] of value) {
|
|
15
|
+
collectTransferables(key, transferables, seen);
|
|
16
|
+
collectTransferables(item, transferables, seen);
|
|
17
|
+
}
|
|
18
|
+
} else if (value instanceof Set || Array.isArray(value)) {
|
|
19
|
+
for (const item of value) collectTransferables(item, transferables, seen);
|
|
20
|
+
} else {
|
|
21
|
+
for (const item of Object.values(value)) collectTransferables(item, transferables, seen);
|
|
22
|
+
}
|
|
23
|
+
return transferables;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function requestResolver(id, resolverRequestId, callbackId, args, resolve) {
|
|
27
|
+
const key = `${id}:${resolverRequestId}`;
|
|
28
|
+
pendingResolvers.set(key, resolve);
|
|
29
|
+
const request = { type: "resolver", id, resolverRequestId, callbackId, args };
|
|
30
|
+
self.postMessage(request, [...collectTransferables(request)]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
self.addEventListener("message", async (event) => {
|
|
34
|
+
const message = event.data;
|
|
35
|
+
if (message?.type === "resolverResult") {
|
|
36
|
+
const key = `${message.id}:${message.resolverRequestId}`;
|
|
37
|
+
const resolve = pendingResolvers.get(key);
|
|
38
|
+
if (!resolve) return;
|
|
39
|
+
pendingResolvers.delete(key);
|
|
40
|
+
resolve(message);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const response = await handle(message, (id, resolverRequestId, callbackId, args, resolve) => {
|
|
44
|
+
requestResolver(id, resolverRequestId, callbackId, args, resolve);
|
|
45
|
+
});
|
|
46
|
+
self.postMessage(response, [...collectTransferables(response)]);
|
|
47
|
+
});
|
package/src/il/body.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { BinaryReader } from "../binary/reader.js";
|
|
2
|
+
import { FormatError, UnsupportedFormatError } from "../binary/errors.js";
|
|
3
|
+
import { decodeInstructions } from "./instructions.js";
|
|
4
|
+
|
|
5
|
+
function parseExceptionSection(bytes, fileOffset, sectionRva) {
|
|
6
|
+
const reader = new BinaryReader(bytes, {
|
|
7
|
+
start: fileOffset,
|
|
8
|
+
baseOffset: fileOffset,
|
|
9
|
+
context: "method data section"
|
|
10
|
+
});
|
|
11
|
+
const kind = reader.u1();
|
|
12
|
+
const fat = (kind & 0x40) !== 0;
|
|
13
|
+
const moreSections = (kind & 0x80) !== 0;
|
|
14
|
+
if ((kind & 0x3f) !== 0x01) {
|
|
15
|
+
throw new UnsupportedFormatError(`Unsupported method data section kind 0x${kind.toString(16)}`, {
|
|
16
|
+
offset: fileOffset,
|
|
17
|
+
context: "method body"
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
let dataSize;
|
|
21
|
+
if (fat) {
|
|
22
|
+
dataSize = reader.u1() | (reader.u1() << 8) | (reader.u1() << 16);
|
|
23
|
+
} else {
|
|
24
|
+
dataSize = reader.u1();
|
|
25
|
+
reader.u2();
|
|
26
|
+
}
|
|
27
|
+
const clauseSize = fat ? 24 : 12;
|
|
28
|
+
if (dataSize < 4 || (dataSize - 4) % clauseSize !== 0 || dataSize > bytes.length - fileOffset) {
|
|
29
|
+
throw new FormatError("Invalid exception-handler section size", { offset: fileOffset, context: "method body" });
|
|
30
|
+
}
|
|
31
|
+
const clauses = [];
|
|
32
|
+
const clauseReader = new BinaryReader(bytes, {
|
|
33
|
+
start: fileOffset + 4,
|
|
34
|
+
length: dataSize - 4,
|
|
35
|
+
baseOffset: fileOffset + 4,
|
|
36
|
+
context: "exception clauses"
|
|
37
|
+
});
|
|
38
|
+
while (clauseReader.remaining > 0) {
|
|
39
|
+
if (fat) {
|
|
40
|
+
const flags = clauseReader.u4();
|
|
41
|
+
const tryOffset = clauseReader.u4();
|
|
42
|
+
const tryLength = clauseReader.u4();
|
|
43
|
+
const handlerOffset = clauseReader.u4();
|
|
44
|
+
const handlerLength = clauseReader.u4();
|
|
45
|
+
const classTokenOrFilterOffset = clauseReader.u4();
|
|
46
|
+
clauses.push({ flags, tryOffset, tryLength, handlerOffset, handlerLength, classTokenOrFilterOffset });
|
|
47
|
+
} else {
|
|
48
|
+
const flags = clauseReader.u2();
|
|
49
|
+
const tryOffset = clauseReader.u2();
|
|
50
|
+
const tryLength = clauseReader.u1();
|
|
51
|
+
const handlerOffset = clauseReader.u2();
|
|
52
|
+
const handlerLength = clauseReader.u1();
|
|
53
|
+
const classTokenOrFilterOffset = clauseReader.u4();
|
|
54
|
+
clauses.push({ flags, tryOffset, tryLength, handlerOffset, handlerLength, classTokenOrFilterOffset });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
offset: fileOffset,
|
|
59
|
+
rva: sectionRva,
|
|
60
|
+
size: dataSize,
|
|
61
|
+
fat,
|
|
62
|
+
moreSections,
|
|
63
|
+
clauses
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function parseMethodBody(input, fileOffset, methodRva = fileOffset) {
|
|
68
|
+
const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
69
|
+
if (fileOffset < 0 || fileOffset >= bytes.length) {
|
|
70
|
+
throw new FormatError("Method body offset is outside the file", { offset: fileOffset, context: "method body" });
|
|
71
|
+
}
|
|
72
|
+
const first = bytes[fileOffset];
|
|
73
|
+
let headerSize;
|
|
74
|
+
let codeSize;
|
|
75
|
+
let maxStack;
|
|
76
|
+
let localVarSigToken;
|
|
77
|
+
let flags;
|
|
78
|
+
let format;
|
|
79
|
+
if ((first & 0x03) === 0x02) {
|
|
80
|
+
format = "tiny";
|
|
81
|
+
headerSize = 1;
|
|
82
|
+
codeSize = first >>> 2;
|
|
83
|
+
maxStack = 8;
|
|
84
|
+
localVarSigToken = 0;
|
|
85
|
+
flags = 0x02;
|
|
86
|
+
} else if ((first & 0x03) === 0x03) {
|
|
87
|
+
format = "fat";
|
|
88
|
+
const header = new BinaryReader(bytes, { start: fileOffset, baseOffset: fileOffset, context: "fat method header" });
|
|
89
|
+
const flagsAndSize = header.u2();
|
|
90
|
+
const headerDwords = flagsAndSize >>> 12;
|
|
91
|
+
headerSize = headerDwords * 4;
|
|
92
|
+
if (headerSize < 12 || fileOffset + headerSize > bytes.length) {
|
|
93
|
+
throw new FormatError(`Invalid fat method header size ${headerSize}`, { offset: fileOffset, context: "method body" });
|
|
94
|
+
}
|
|
95
|
+
flags = flagsAndSize & 0x0fff;
|
|
96
|
+
maxStack = header.u2();
|
|
97
|
+
codeSize = header.u4();
|
|
98
|
+
localVarSigToken = header.u4();
|
|
99
|
+
} else {
|
|
100
|
+
throw new FormatError(`Invalid method header format 0x${(first & 0x03).toString(16)}`, {
|
|
101
|
+
offset: fileOffset,
|
|
102
|
+
context: "method body"
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const codeOffset = fileOffset + headerSize;
|
|
107
|
+
if (!Number.isSafeInteger(codeSize) || codeOffset + codeSize > bytes.length) {
|
|
108
|
+
throw new FormatError("Method code exceeds the file", { offset: codeOffset, context: "method body" });
|
|
109
|
+
}
|
|
110
|
+
const instructions = decodeInstructions(bytes.subarray(codeOffset, codeOffset + codeSize), {
|
|
111
|
+
baseOffset: codeOffset
|
|
112
|
+
});
|
|
113
|
+
let sections = [];
|
|
114
|
+
let endOffset = codeOffset + codeSize;
|
|
115
|
+
const hasSections = format === "fat" && (flags & 0x08) !== 0;
|
|
116
|
+
if (hasSections) {
|
|
117
|
+
let sectionRva = (methodRva + headerSize + codeSize + 3) & ~3;
|
|
118
|
+
let sectionOffset = fileOffset + (sectionRva - methodRva);
|
|
119
|
+
let more = true;
|
|
120
|
+
while (more) {
|
|
121
|
+
if (sectionOffset >= bytes.length) {
|
|
122
|
+
throw new FormatError("Method data section exceeds the file", { offset: sectionOffset, context: "method body" });
|
|
123
|
+
}
|
|
124
|
+
const section = parseExceptionSection(bytes, sectionOffset, sectionRva);
|
|
125
|
+
sections.push(section);
|
|
126
|
+
endOffset = sectionOffset + section.size;
|
|
127
|
+
more = section.moreSections;
|
|
128
|
+
if (more) {
|
|
129
|
+
sectionRva = (sectionRva + section.size + 3) & ~3;
|
|
130
|
+
sectionOffset = fileOffset + (sectionRva - methodRva);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const section of sections) {
|
|
135
|
+
for (const clause of section.clauses) {
|
|
136
|
+
if (clause.tryOffset + clause.tryLength > codeSize ||
|
|
137
|
+
clause.handlerOffset + clause.handlerLength > codeSize) {
|
|
138
|
+
throw new FormatError("Exception-handler region exceeds the method code", {
|
|
139
|
+
offset: section.offset,
|
|
140
|
+
context: "method body"
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if ((clause.flags & 1) !== 0 && clause.classTokenOrFilterOffset >= codeSize) {
|
|
144
|
+
throw new FormatError("Exception filter offset exceeds the method code", {
|
|
145
|
+
offset: section.offset,
|
|
146
|
+
context: "method body"
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
format,
|
|
154
|
+
flags,
|
|
155
|
+
maxStack,
|
|
156
|
+
codeSize,
|
|
157
|
+
localVarSigToken,
|
|
158
|
+
headerOffset: fileOffset,
|
|
159
|
+
headerSize,
|
|
160
|
+
codeOffset,
|
|
161
|
+
endOffset,
|
|
162
|
+
instructions,
|
|
163
|
+
sections
|
|
164
|
+
};
|
|
165
|
+
}
|