jsilnet 0.1.0 → 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.
Files changed (50) hide show
  1. package/README.md +25 -6
  2. package/REFERENCE.md +4 -0
  3. package/dist/browser/index.js +345 -0
  4. package/dist/browser/worker.js +42480 -0
  5. package/package.json +23 -4
  6. package/src/assembly/instruction-editing.js +165 -0
  7. package/src/assembly/method-removal.js +789 -0
  8. package/src/assembly/pointer-table-editing.js +506 -0
  9. package/src/assembly/portable-pdb-editing.js +441 -0
  10. package/src/browser/index.d.ts +123 -0
  11. package/src/browser/index.js +373 -0
  12. package/src/browser/worker-runtime.js +287 -0
  13. package/src/browser/worker.js +47 -0
  14. package/src/index.d.ts +267 -0
  15. package/src/index.js +107 -1574
  16. package/src/metadata/portable-pdb.js +2 -2
  17. package/src/metadata/strong-name.js +84 -83
  18. package/src/pe/headers.js +1 -1
  19. package/src/platform/bytes.js +21 -0
  20. package/src/platform/compression.js +24 -0
  21. package/src/platform/crypto.js +17 -0
  22. package/src/types/assembly/instruction-editing.d.ts +33 -0
  23. package/src/types/assembly/method-removal.d.ts +3 -0
  24. package/src/types/assembly/pointer-table-editing.d.ts +3 -0
  25. package/src/types/assembly/portable-pdb-editing.d.ts +23 -0
  26. package/src/types/binary/errors.d.ts +7 -0
  27. package/src/types/binary/reader.d.ts +30 -0
  28. package/src/types/il/body.d.ts +51 -0
  29. package/src/types/il/instructions.d.ts +33 -0
  30. package/src/types/il/opcodes.d.ts +2 -0
  31. package/src/types/il/rewrite.d.ts +38 -0
  32. package/src/types/index.d.ts +864 -0
  33. package/src/types/metadata/classic-pdb.d.ts +688 -0
  34. package/src/types/metadata/custom-attributes.d.ts +28 -0
  35. package/src/types/metadata/portable-pdb.d.ts +296 -0
  36. package/src/types/metadata/resources.d.ts +1 -0
  37. package/src/types/metadata/root-writer.d.ts +1 -0
  38. package/src/types/metadata/signatures.d.ts +5 -0
  39. package/src/types/metadata/streams.d.ts +26 -0
  40. package/src/types/metadata/strong-name.d.ts +11 -0
  41. package/src/types/metadata/tables.d.ts +25 -0
  42. package/src/types/metadata/writer.d.ts +16 -0
  43. package/src/types/pe/headers.d.ts +160 -0
  44. package/src/types/platform/bytes.d.ts +3 -0
  45. package/src/types/platform/compression.d.ts +2 -0
  46. package/src/types/platform/crypto.d.ts +4 -0
  47. package/src/types/resolution/module.d.ts +12 -0
  48. package/src/types/writer/patch.d.ts +4 -0
  49. package/src/types/writer/pe.d.ts +9 -0
  50. package/src/types/writer/structural.d.ts +4 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jsilnet",
3
- "version": "0.1.0",
4
- "description": "Dependency-free Node.js ECMA-335 / CLI assembly reader and writer",
3
+ "version": "0.2.0",
4
+ "description": "Node.js and browser ECMA-335 / CLI assembly reader and writer",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -26,17 +26,36 @@
26
26
  "engines": {
27
27
  "node": ">=20"
28
28
  },
29
+ "types": "./src/index.d.ts",
29
30
  "files": [
30
31
  "src",
32
+ "dist/browser",
31
33
  "example",
32
34
  "REFERENCE.md"
33
35
  ],
34
36
  "exports": {
35
- ".": "./src/index.js"
37
+ ".": "./src/index.js",
38
+ "./browser": {
39
+ "types": "./src/browser/index.d.ts",
40
+ "default": "./dist/browser/index.js"
41
+ }
36
42
  },
37
43
  "scripts": {
38
44
  "test": "node --test",
39
45
  "check": "node --check src/index.js",
40
- "prepublishOnly": "npm test && npm run check"
46
+ "build:browser": "node scripts/build-browser.js",
47
+ "check:browser": "npm run build:browser && node scripts/check-browser-bundle.js",
48
+ "build:types": "tsc --allowJs --checkJs false --declaration --emitDeclarationOnly --module NodeNext --moduleResolution NodeNext --target ES2022 --outDir src/types src/index.js",
49
+ "precheck:types": "npm run build:types",
50
+ "check:types": "tsc --noEmit --strict --skipLibCheck --module NodeNext --moduleResolution NodeNext type-tests/core-api.ts type-tests/browser-api.ts && node scripts/check-type-coverage.js",
51
+ "prepublishOnly": "npm test && npm run check && npm run check:types && npm run check:browser"
52
+ },
53
+ "devDependencies": {
54
+ "esbuild": "^0.28.2",
55
+ "typescript": "^5.8.3"
56
+ },
57
+ "dependencies": {
58
+ "fflate": "^0.8.3",
59
+ "node-forge": "^1.4.3"
41
60
  }
42
61
  }
@@ -0,0 +1,165 @@
1
+ import { FormatError } from "../binary/errors.js";
2
+ import { encodeOpcode } from "../il/instructions.js";
3
+ import { listOpcodes } from "../il/opcodes.js";
4
+ import {
5
+ insertInstruction,
6
+ layoutStructuralState,
7
+ removeInstruction,
8
+ updateStructuralOperand
9
+ } from "../il/rewrite.js";
10
+ import { createOperandPatches } from "../writer/patch.js";
11
+ import { metadataTableNames } from "../metadata/tables.js";
12
+
13
+ export function createInstructionEditor(assembly, hooks) {
14
+ return {
15
+ setInstructionOperand(methodRid, instructionOffset, value) {
16
+ hooks.ensureEditable();
17
+ const body = assembly.getMethodBody(methodRid);
18
+ if (!body) throw new FormatError("Method has no CIL body");
19
+ const instruction = body.instructions.find((candidate) => candidate.offset === instructionOffset);
20
+ if (!instruction) throw new RangeError(`No instruction begins at IL_${instructionOffset.toString(16)}`);
21
+ if (!instruction.operand) throw new TypeError("Instruction has no operand");
22
+
23
+ if (instruction.operand.kind === "branch") {
24
+ hooks.validateBranchTarget(body, value);
25
+ } else if (instruction.operand.kind === "switch") {
26
+ if (!Array.isArray(value)) throw new TypeError("Switch operand must be an array of IL target offsets");
27
+ for (const target of value) hooks.validateBranchTarget(body, target);
28
+ } else if (instruction.opcode.name === "ldstr") {
29
+ if (!Number.isInteger(value) || (value >>> 24) !== 0x70) {
30
+ throw new TypeError("ldstr operand must be a 0x70-prefixed user-string token");
31
+ }
32
+ assembly.heaps.getUserString(value);
33
+ } else if (instruction.operand.kind === "token") {
34
+ if (!Number.isInteger(value) || value < 0 || value > 0xffffffff) {
35
+ throw new RangeError("Metadata token must be an unsigned 32-bit integer");
36
+ }
37
+ const table = value >>> 24;
38
+ const rid = value & 0x00ffffff;
39
+ if (!hooks.allowedTokenTables(instruction.opcode.name).includes(table)) {
40
+ throw new FormatError(`Metadata table ${table} is not valid for ${instruction.opcode.name}`);
41
+ }
42
+ if (rid === 0 || rid > (assembly.tables.counts[table] ?? 0)) {
43
+ throw new RangeError(
44
+ `${instruction.opcode.name} references missing ${metadataTableNames[table]} row ${rid}`
45
+ );
46
+ }
47
+ hooks.validateInstructionGenericContext(assembly, methodRid, instruction, value);
48
+ }
49
+
50
+ if (instruction.operand.kind === "branch" && instruction.operand.targetOffset === value) return assembly;
51
+ if (instruction.operand.kind === "switch" &&
52
+ instruction.operand.targetOffsets.length === value.length &&
53
+ instruction.operand.targetOffsets.every((target, index) => target === value[index])) return assembly;
54
+ if (instruction.operand.kind !== "branch" && instruction.operand.kind !== "switch" &&
55
+ instruction.operand.value === value) return assembly;
56
+
57
+ if (assembly._structuralBodies.has(methodRid) || assembly._addedMethodBodies.has(methodRid)) {
58
+ const state = hooks.createStructuralState(methodRid);
59
+ const editableInstruction = state.instructions.find((candidate) => candidate.offset === instructionOffset);
60
+ updateStructuralOperand(state, editableInstruction, value);
61
+ hooks.commitStructuralState(methodRid, state);
62
+ } else {
63
+ const patches = createOperandPatches(instruction, value);
64
+ for (const patch of patches) assembly._patches.push(patch);
65
+ instruction.operand = instruction.operand.kind === "branch"
66
+ ? { kind: "branch", targetOffset: value }
67
+ : instruction.operand.kind === "switch"
68
+ ? { kind: "switch", targetOffsets: [...value] }
69
+ : { ...instruction.operand, value };
70
+ }
71
+ return assembly;
72
+ },
73
+
74
+ setInstructionOpcode(methodRid, instructionOffset, opcodeNameOrCode) {
75
+ hooks.ensureEditable();
76
+ const body = assembly.getMethodBody(methodRid);
77
+ if (!body) throw new FormatError("Method has no CIL body");
78
+ const instruction = body.instructions.find((candidate) => candidate.offset === instructionOffset);
79
+ if (!instruction) throw new RangeError(`No instruction begins at IL_${instructionOffset.toString(16)}`);
80
+ const opcode = listOpcodes().find((candidate) =>
81
+ candidate.name === opcodeNameOrCode || candidate.code === opcodeNameOrCode
82
+ );
83
+ if (!opcode) throw new RangeError(`Unknown CIL opcode ${opcodeNameOrCode}`);
84
+ const encoded = encodeOpcode(opcode);
85
+ if (encoded.length !== instruction.opcodeSize || opcode.operand !== instruction.opcode.operand) {
86
+ throw new FormatError("Opcode edit must preserve the opcode and operand widths");
87
+ }
88
+ if (opcode.code === instruction.opcode.code) return assembly;
89
+ if (instruction.operand?.kind === "token") {
90
+ if (opcode.name !== "ldstr") {
91
+ const table = instruction.operand.value >>> 24;
92
+ const rid = instruction.operand.value & 0x00ffffff;
93
+ if (!hooks.allowedTokenTables(opcode.name).includes(table)) {
94
+ throw new FormatError(`Metadata table ${table} is not valid for ${opcode.name}`);
95
+ }
96
+ if (rid === 0 || rid > (assembly.tables.counts[table] ?? 0)) {
97
+ throw new RangeError(
98
+ `${opcode.name} references missing ${metadataTableNames[table]} row ${rid}`
99
+ );
100
+ }
101
+ hooks.validateInstructionGenericContext(
102
+ assembly, methodRid, { ...instruction, opcode }, instruction.operand.value
103
+ );
104
+ }
105
+ }
106
+ if (assembly._structuralBodies.has(methodRid) || assembly._addedMethodBodies.has(methodRid)) {
107
+ const state = hooks.createStructuralState(methodRid);
108
+ const editableInstruction = state.instructions.find((candidate) => candidate.offset === instructionOffset);
109
+ editableInstruction.opcode = opcode;
110
+ layoutStructuralState(state);
111
+ hooks.commitStructuralState(methodRid, state);
112
+ } else {
113
+ assembly._patches.push({ offset: instruction.opcodeOffset, bytes: encoded });
114
+ instruction.opcode = opcode;
115
+ }
116
+ return assembly;
117
+ },
118
+
119
+ insertInstruction(methodRid, beforeOffset, opcodeNameOrCode, operand) {
120
+ hooks.ensureEditable();
121
+ const opcode = listOpcodes().find((candidate) =>
122
+ candidate.name === opcodeNameOrCode || candidate.code === opcodeNameOrCode
123
+ );
124
+ if (!opcode) throw new RangeError(`Unknown CIL opcode ${opcodeNameOrCode}`);
125
+ const state = hooks.createStructuralState(methodRid);
126
+ const instruction = insertInstruction(state, beforeOffset, opcode, operand);
127
+ if (instruction.operand?.kind === "token" && opcode.name !== "ldstr") {
128
+ const token = instruction.operand.value;
129
+ const table = token >>> 24;
130
+ const rid = token & 0x00ffffff;
131
+ if (!hooks.allowedTokenTables(opcode.name).includes(table)) {
132
+ throw new FormatError(`Metadata table ${table} is not valid for ${opcode.name}`);
133
+ }
134
+ if (rid === 0 || rid > (assembly.tables.counts[table] ?? 0)) {
135
+ throw new RangeError(
136
+ `${opcode.name} references missing ${metadataTableNames[table]} row ${rid}`
137
+ );
138
+ }
139
+ hooks.validateInstructionGenericContext(assembly, methodRid, instruction, token);
140
+ }
141
+ hooks.commitStructuralState(methodRid, state);
142
+ return instruction;
143
+ },
144
+
145
+ removeInstruction(methodRid, instructionOffset) {
146
+ hooks.ensureEditable();
147
+ const state = hooks.createStructuralState(methodRid);
148
+ const instruction = removeInstruction(state, instructionOffset);
149
+ hooks.commitStructuralState(methodRid, state);
150
+ return instruction;
151
+ },
152
+
153
+ setMethodMaxStack(methodRid, maxStack) {
154
+ hooks.ensureEditable();
155
+ if (!Number.isInteger(maxStack) || maxStack < 0 || maxStack > 0xffff) {
156
+ throw new RangeError("maxStack must be an unsigned 16-bit integer");
157
+ }
158
+ const state = hooks.createStructuralState(methodRid);
159
+ state.maxStack = maxStack;
160
+ layoutStructuralState(state);
161
+ hooks.commitStructuralState(methodRid, state);
162
+ return assembly;
163
+ }
164
+ };
165
+ }