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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TH3C0D3R
|
|
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
CHANGED
|
@@ -1,3 +1,98 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# jsILNET
|
|
2
|
+
|
|
3
|
+
`jsILNET` is a JavaScript library for inspecting and rewriting .NET assemblies. It reads PE/CLI formats directly and does not require a .NET runtime. The package provides a synchronous Node.js API and a Promise-based browser API backed by a Web Worker.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 20 or later
|
|
8
|
+
- ECMAScript modules
|
|
9
|
+
- Web Workers for the browser entry point (`jsilnet/browser`)
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install jsilnet
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
Read an assembly, inspect its metadata, and write an unchanged round trip:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
23
|
+
import { parseAssembly } from "jsilnet";
|
|
24
|
+
|
|
25
|
+
const input = new Uint8Array(await readFile("input.dll"));
|
|
26
|
+
const assembly = parseAssembly(input);
|
|
27
|
+
|
|
28
|
+
console.log(`Types: ${assembly.types.length}`);
|
|
29
|
+
console.log(`Methods: ${assembly.methods.length}`);
|
|
30
|
+
|
|
31
|
+
await writeFile("roundtrip.dll", assembly.toBuffer());
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Example: edit an existing IL operand
|
|
35
|
+
|
|
36
|
+
This example updates an existing `ldc.i4.s` operand in a method named `Answer` and writes the rewritten assembly:
|
|
37
|
+
|
|
38
|
+
```js
|
|
39
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
40
|
+
import { parseAssembly } from "jsilnet";
|
|
41
|
+
|
|
42
|
+
const assembly = parseAssembly(new Uint8Array(await readFile("input.dll")));
|
|
43
|
+
const method = assembly.methods.find((item) => item.name === "Answer");
|
|
44
|
+
if (!method) throw new Error("Method Answer was not found");
|
|
45
|
+
|
|
46
|
+
const instruction = assembly
|
|
47
|
+
.getInstructions(method.rid)
|
|
48
|
+
.find((item) => item.opcode.name === "ldc.i4.s");
|
|
49
|
+
if (!instruction) throw new Error("No ldc.i4.s instruction was found");
|
|
50
|
+
|
|
51
|
+
assembly.setInstructionOperand(method.rid, instruction.offset, 43);
|
|
52
|
+
await writeFile("output.dll", assembly.toBuffer());
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Edits are validated against the supported input profile; unsupported or unsafe rewrites fail explicitly. See the [Current capabilities](REFERENCE.md#current-capabilities) and [API surface](REFERENCE.md#api-surface) references for supported features, APIs, and limitations.
|
|
56
|
+
|
|
57
|
+
## Browser usage
|
|
58
|
+
|
|
59
|
+
In a browser application using a package-aware bundler with module Worker support, import the separate browser entry point:
|
|
60
|
+
|
|
61
|
+
```js
|
|
62
|
+
import { parseAssembly } from "jsilnet/browser";
|
|
63
|
+
|
|
64
|
+
async function roundTripFile(file) {
|
|
65
|
+
const assembly = await parseAssembly(file); // File and Blob inputs are supported.
|
|
66
|
+
try {
|
|
67
|
+
const state = await assembly.getState();
|
|
68
|
+
console.log(`Assembly modified: ${state.modified}`);
|
|
69
|
+
const bytes = await assembly.serialize();
|
|
70
|
+
return new Blob([bytes], { type: "application/octet-stream" });
|
|
71
|
+
} finally {
|
|
72
|
+
assembly.dispose();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The browser API is Promise-based and runs parsing, editing, and serialization in a Web Worker; the default Node.js entry point remains synchronous. Assembly methods must be awaited, `getState()` returns a snapshot, and `serialize()` returns a `Uint8Array`. Resolver callbacks such as `resolveEnum` and `resolveType` may be synchronous or asynchronous. Calling `dispose()` terminates the Worker and releases its Assembly state.
|
|
78
|
+
|
|
79
|
+
`parseAssembly` also accepts `Uint8Array` and `ArrayBuffer`. For full-buffer byte inputs, `{ transferInput: true }` transfers the input to the Worker and detaches it from the caller; subarray views are copied.
|
|
80
|
+
|
|
81
|
+
## Live Demo
|
|
82
|
+
The browser-only demo in `live-demo/` inspects and patches .NET assemblies without uploading them. Build it with `npm run build --prefix live-demo`, then serve `live-demo/dist/` from any static web host. See [`live-demo/README.md`](live-demo/README.md) for local preview instructions.
|
|
83
|
+
|
|
84
|
+
## Documentation
|
|
85
|
+
|
|
86
|
+
- [Runnable example](example/README.md)
|
|
87
|
+
- [Current capabilities](REFERENCE.md#current-capabilities)
|
|
88
|
+
- [API surface](REFERENCE.md#api-surface)
|
|
89
|
+
- [Architecture and implementation status](ARCHITECTURE.md)
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
npm install
|
|
95
|
+
npm test
|
|
96
|
+
npm run check
|
|
97
|
+
npm run check:browser
|
|
98
|
+
```
|
package/REFERENCE.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# jsILNET technical reference
|
|
2
|
+
|
|
3
|
+
This document records detailed feature coverage, exported APIs, behavioral limits, and test guidance. For installation and usage examples, see the [README](README.md).
|
|
4
|
+
|
|
5
|
+
## Current capabilities
|
|
6
|
+
|
|
7
|
+
- Parse PE32/PE32+ headers, sections, RVA mappings, and the CLI header.
|
|
8
|
+
- Validate PE section/file alignment relationships and reject out-of-file, overlapping, or overflowing section ranges.
|
|
9
|
+
- Reject PE sections that overlap the aligned headers or exceed `SizeOfImage`; metadata-root flags and stream-name alignment padding must be zero. `#Blob` and `#US` indexes must address entry boundaries, while `#Strings` indexes may use valid suffix sharing.
|
|
10
|
+
- Validate ordering for ECMA-defined sorted metadata tables and reject duplicate keys in metadata tables with unique keys.
|
|
11
|
+
- Parse and bounds-check debug-directory entries, adjusting their raw pointers if section-header growth shifts file data.
|
|
12
|
+
- Replace RSDS/NB10 CodeView PDB paths in place when the encoded path length is unchanged.
|
|
13
|
+
- Replacing an embedded Portable PDB source also recalculates the Document hash for recognized SHA-1/SHA-2 algorithms; unsupported or nil algorithms are rejected.
|
|
14
|
+
- `replaceEmbeddedPortablePdbLocalScope(pdb, localScopeRid, options)` reserializes a Portable PDB after updating a LocalScope start offset and/or length, or assigning an existing ImportScope row (or nil). `Assembly.replaceEmbeddedPortablePdbLocalScope(debugEntryIndex, localScopeRid, options)` additionally validates both range endpoints against instruction boundaries in the owning method and rejects edits after an IL-offset remap for that method has been staged.
|
|
15
|
+
- `replaceEmbeddedPortablePdbLocalVariable(pdb, localVariableRid, options)` updates a local's name, attributes, and/or slot index; attributes are limited to the defined pinned bit, slot indexes must fit 16 bits, and the updated row is checked against its owning LocalScope for duplicate names or indexes. The Assembly method stages the reserialized PDB and refreshes its identity/checksum.
|
|
16
|
+
- `replaceEmbeddedPortablePdbLocalConstant(pdb, localConstantRid, { name })` renames a LocalConstant through the Portable PDB string heap and validates uniqueness within its owning LocalScope; the Assembly method refreshes the PDB identity and supported checksum.
|
|
17
|
+
- `replaceEmbeddedPortablePdbCustomDebugInformation(pdb, customDebugInformationRid, value)` replaces any existing CDI blob and decodes/validates recognized kinds before serialization. Opaque kinds remain opaque, so the API does not claim their payload semantics; the Assembly method refreshes PDB identity and checksum.
|
|
18
|
+
- `replaceEmbeddedPortablePdbMethodDocument(pdb, methodRid, documentRid)` changes a method's default Document and re-encodes its sequence points with that document while preserving offsets and source spans; a nil Document retains explicit point documents. The Assembly method refreshes the embedded PDB identity and checksum.
|
|
19
|
+
- `replaceEmbeddedPortablePdbImportScope(pdb, importScopeRid, { parentRid })` changes an ImportScope parent while preserving its raw Imports blob; references are range-checked and parent cycles are rejected. The Assembly method refreshes the embedded PDB identity and checksum.
|
|
20
|
+
- Parse embedded Portable PDB containers with bounded decompression, decode Portable PDB tables, document names, sequence points, EmbeddedSource, SourceLink, AsyncMethodSteppingInformation, and supported EnC records, and expose heaps plus `#Pdb` identity and referenced type-system row counts. Referenced row counts, entry points, and embedded PDB identities are checked against the containing assembly and CodeView record; append-only type-system row additions automatically update the `#Pdb` counts when exactly one embedded PDB is present. `serializeEmbeddedPortablePdb` reserializes a parsed container while retaining its streams; `addEmbeddedPortablePdbDocument` appends a Document row, while `insertEmbeddedPortablePdbDocument` inserts at a 1-based `beforeRid` and remaps method-debug Document references, sequence-point blobs, Document-scoped CDI Parents, and TypeDefinitionDocument lists. Insertion is refused when opaque CDI may contain unremappable Document references. `removeEmbeddedPortablePdbDocument` removes an unreferenced one, and `replaceEmbeddedPortablePdbDocumentMetadata` updates its hash, hash-algorithm GUID, or language GUID (pass `null` to clear a value); `addEmbeddedPortablePdbTypeDefinitionDocuments` and `replaceEmbeddedPortablePdbTypeDefinitionDocuments` add or replace a TypeDef's TypeDefinitionDocument CDI; removing a Document remaps those CDI document lists and drops records whose lists become empty. `replaceEmbeddedPortablePdbDocumentName`, `replaceEmbeddedPortablePdbSequencePoints`, `replaceEmbeddedPortablePdbSource`, `removeEmbeddedPortablePdbSource`, `replaceEmbeddedPortablePdbSourceLink`, `removeEmbeddedPortablePdbSourceLink`, and `removeEmbeddedPortablePdbCustomDebugInformation` emit reserialized containers with updated debug metadata. SourceLink updates require an existing module-scoped record and validate the documents mapping object. `Assembly.replaceEmbeddedPortablePdb` can replace an embedded record and relocate its data plus a rebuilt debug-directory table into a new PE section; it recalculates the Portable PDB content ID and supported PDB checksum, then synchronizes the associated CodeView identity. Corresponding document-name, TypeDefinitionDocument, sequence-point, EmbeddedSource, and SourceLink methods compose edits to the same PDB. Only one embedded PDB record can be edited per Assembly; multiple PDB checksum records and unsupported checksum algorithms are rejected rather than left stale. `appendPortablePdbDocumentName` adds path components to a Blob heap; `encodePortablePdbDocumentName` and `encodePortablePdbSequencePoints` encode standalone blobs. Structural IL edits automatically rebase sequence points, LocalScope boundaries, state-machine hoisted-local scopes, and async catch/yield/resume offsets when referenced instruction boundaries survive; opaque method-scoped custom-debug records remain a refusal boundary. Parse classic PDB MSF 7.0 stream directories, information-stream identity, named streams, feature signatures, DBI headers, module descriptors, source-file lists, bounded module symbol records, and C13 line/column records with file-name resolution; object-name, public, data, thread-data, constant, procedure, managed-procedure, thunk, block/with-scope/scope-end, frame-procedure, compiler-info, local, BP-relative, register, register-relative, UDT, namespace, and label symbols expose decoded values and locations. `replaceClassicPdbModuleNames` can rename DBI module/object names while rebuilding the MSF streams; classic symbol names can be replaced and their module streams can grow across MSF block allocations when modules have no scoped or unknown symbols; `replaceClassicPdbLineInfo` updates C13 code offsets, line fields, and optional columns in place without resizing module streams; `/names` paths can be replaced without shifting string offsets. Full classic PDB rebuilding remains unsupported.
|
|
21
|
+
- `Assembly.insertEmbeddedPortablePdbDocument(debugEntryIndex, options)` exposes arbitrary Document insertion for an embedded PDB and refreshes the PDB identity and checksum through the Assembly editing path.
|
|
22
|
+
- `replaceClassicPdbStringTableName` supports both same-length and variable-length UTF-8 names; length changes rebuild MSF streams and remap C13 checksum references to shifted `/names` offsets.
|
|
23
|
+
- `replaceClassicPdbFileChecksum` supports the no-checksum, MD5, SHA-1, and SHA-256 C13 kinds; the kind is inferred from digest length unless supplied. Changing digest length rebuilds the affected module/MSF streams and remaps following line-block checksum offsets.
|
|
24
|
+
- `insertClassicPdbFileChecksum` appends a C13 file-checksum record, and `removeClassicPdbFileChecksum` removes an unreferenced one while remapping later line-block checksum offsets. Both rebuild affected module/MSF streams and reparse output; checksum records still referenced by line blocks cannot be removed.
|
|
25
|
+
- `removeClassicPdbSymbol` removes standalone public, data, or constant symbols and rebuilds the module stream as needed; modules containing scoped or unknown symbols are refused to avoid invalidating symbol offsets.
|
|
26
|
+
- `insertClassicPdbPublicSymbol` appends an `S_PUB32` symbol before C11/C13 data, creating a symbol signature when needed and updating DBI/MSF sizes.
|
|
27
|
+
- `replaceClassicPdbPublicSymbol` updates the flags, address, and/or segment of an `S_PUB32` record in place while preserving its name and record size.
|
|
28
|
+
- `insertClassicPdbDataSymbol` appends a validated module-local `S_LDATA32` record before C11/C13 data with its type index, address, segment, and name.
|
|
29
|
+
- `replaceClassicPdbDataSymbol` updates the type index, address, and/or segment of a module-local `S_LDATA32` record in place while preserving its name and record size.
|
|
30
|
+
- `insertClassicPdbThreadDataSymbol` appends a validated module-local `S_LTHREAD32` record; standalone thread-data symbols can also be removed with `removeClassicPdbSymbol`.
|
|
31
|
+
- `replaceClassicPdbThreadDataSymbol` updates the type index, address, and/or segment of a module-local `S_LTHREAD32` record without resizing it; the name and module stream layout are preserved.
|
|
32
|
+
- `insertClassicPdbConstantSymbol` appends an `S_CONSTANT` record with a 32-bit type index and finite numeric or exact BigInt value; integer encodings are limited to 64 bits.
|
|
33
|
+
- `replaceClassicPdbConstantSymbol` updates an `S_CONSTANT` type index/value and can resize its record. Resizing is refused when scoped/unknown records make following symbol offsets unsafe.
|
|
34
|
+
- `replaceClassicPdbConstantSymbol` updates an `S_CONSTANT` type index/value and can resize its record. Resizing is refused when scoped/unknown records make following symbol offsets unsafe.
|
|
35
|
+
- `insertClassicPdbLineBlock`, `replaceClassicPdbLineBlock`, and `removeClassicPdbLineBlock` edit C13 file blocks; insertion validates the referenced checksum entry, and replacement supports line-count changes with or without columns. These edits rebuild affected module/MSF streams, update DBI lengths, preserve surrounding module data, and reparse output; reserved/shared streams and unrecognized per-block payloads are refused.
|
|
36
|
+
- Read the metadata root, `#~`/`#-` table streams, and the `#Strings`, `#US`, `#GUID`, and `#Blob` heaps.
|
|
37
|
+
- Decode ECMA-335 metadata tables and expose core module, type, field, method, member-reference, and assembly-reference data.
|
|
38
|
+
- Parse and encode method, field, local, property, method-instantiation, and type signatures, including array shapes, generic instances, modifiers, and compressed signed integers.
|
|
39
|
+
- Read embedded manifest-resource payloads with bounds checks and expose linked-resource implementation references.
|
|
40
|
+
- Add embedded or linked manifest-resource rows and replace embedded payloads while updating offsets and relocating the CLI resources directory.
|
|
41
|
+
- Replace File-linked resource payloads while updating the referenced File hash; the external file must still be written by the caller.
|
|
42
|
+
- Parse, add, and replace custom-attribute blobs for supported primitive, string, `System.Type`, array, boxed, and enum argument types. `resolveEnum(name)` can supply an external enum's underlying element type. `resolveType(name, context)` can instead return `{ assembly, rid }` for a TypeDef in a separately parsed assembly, enabling enum discovery plus constructor, inheritance, and named-member validation against external metadata. Its context contains the source `assembly`, TypeRef `reference` (or `null` for name-based lookups), `assemblyReference`, and any assembly identity in the requested name. Unresolved external types retain the conservative, incomplete-validation behavior.
|
|
43
|
+
- Decode and encode binary declarative-security PermissionSets with typed named arguments; `resolveType` also enables validation of externally defined security-attribute types. Expose or emit legacy UTF-8 XML PermissionSets as text without expanding entities.
|
|
44
|
+
- Decode CIL instructions, including `0xFE`-prefixed opcodes, branch and switch operands, tiny/fat method headers, and exception-handler sections.
|
|
45
|
+
- Patch fixed-width instruction operands using existing heap entries and validated metadata tokens, or replace an opcode when its encoded width and operand layout remain unchanged.
|
|
46
|
+
- Insert/remove CIL instructions, resolve branch and switch targets, promote out-of-range short branches, and update supported exception-handler regions.
|
|
47
|
+
- Re-encode tiny/fat method bodies, including max-stack changes, and relocate rewritten bodies and rebuilt metadata into an appended PE section.
|
|
48
|
+
- Append `TypeRef`, `MemberRef`, and `AssemblyRef` rows, insert `AssemblyRef` rows before existing rows, and write required string/blob heap entries while recalculating metadata index widths and rebuilding metadata streams.
|
|
49
|
+
- Append fields to a TypeDef whose FieldList reaches the end of the field list (including when later types have empty ranges), or insert into any valid owner range; replace field names/flags without changing their tokens. `#-` field insertion is supported when FieldPtr is one-to-one with Field.
|
|
50
|
+
- Append validated concrete IL method bodies or bodyless abstract virtual methods to a TypeDef whose MethodList reaches the end of the method list (including when later types have empty ranges), including one-to-one `#-` MethodPtr layouts; generic methods are supported when their matching GenericParam rows are added.
|
|
51
|
+
- Append parameter rows to a MethodDef whose ParamList reaches the end of the list, with signature-sequence checks and support for one-to-one ParamPtr metadata.
|
|
52
|
+
- Reorder complete bijective pointer tables across owners with validation: FieldPtr rechecks field generic contexts and explicit-layout constraints; ParamPtr checks parameter sequences; MethodPtr is limited to bodyless abstract non-generic methods without MethodSemantics/MethodImpl references; PropertyPtr/EventPtr require no MethodSemantics associations and valid generic contexts.
|
|
53
|
+
- Append properties and events to a TypeDef whose ownership range reaches the end of the corresponding list (including when later owners have empty ranges), and remove unreferenced rows; `#-` PropertyPtr/EventPtr layouts are supported when each pointer table is a one-to-one mapping to its target rows.
|
|
54
|
+
- Append `TypeSpec` rows after validating type references and locally known generic-instance arities.
|
|
55
|
+
- Append `MethodSpec` rows with validated generic instantiation signatures.
|
|
56
|
+
- Append `GenericParam` rows with validated owner ordering, variance, and special-constraint flags; update existing parameter names and flags without changing their row identities.
|
|
57
|
+
- Insert sorted `MemberRef` rows while remapping their supported metadata and CIL references; the API returns old-to-new tokens.
|
|
58
|
+
- Insert sorted `TypeRef` rows while remapping known metadata references, signature blobs, and CIL operands.
|
|
59
|
+
- Insert sorted `MethodSpec` rows while remapping custom-attribute references and CIL operands.
|
|
60
|
+
- Append UTF-16LE user strings to `#US` and use their tokens in `ldstr` edits.
|
|
61
|
+
- Preserve unchanged input byte-for-byte; reparse rewritten output and maintain existing metadata token RIDs when appending supported rows.
|
|
62
|
+
|
|
63
|
+
## API surface
|
|
64
|
+
|
|
65
|
+
The package exports `parseAssembly` and `Assembly`, plus lower-level PE, metadata, heap, signature, binary-reader, coded-index, CIL-instruction, and method-body parsing utilities.
|
|
66
|
+
|
|
67
|
+
The `jsilnet/browser` package subpath exports a Promise-based `parseAssembly` and `Assembly` proxy. It accepts a `Blob` for worker-side file reading, or a `Uint8Array`/`ArrayBuffer`; for a full-buffer `Uint8Array` or `ArrayBuffer`, `transferInput: true` transfers ownership and detaches the input from the caller. Assembly operations execute in a dedicated module Worker; `getState()` retrieves a structured-cloneable snapshot, `serialize()` returns a `Uint8Array`, and `dispose()` terminates the Worker. `callUtility(name, ...args)` runs an exported standalone utility in a Worker. Browser support is not yet fully at parity: callback-valued `resolveEnum`/`resolveType` options cannot be structured-cloned, and standalone utility results omit function-valued properties other than the restored `parsePE().rvaToOffset` mapping.
|
|
68
|
+
|
|
69
|
+
The package declarations cover all package exports and public `Assembly` methods. Run `npm run check:types` to regenerate the declarations, compile a consumer type check, and verify API coverage.
|
|
70
|
+
|
|
71
|
+
An `Assembly` exposes parsed PE/CLI metadata, `module`, `types`, `typeRefs`, `typeSpecs`, `methods`, `memberRefs`, `moduleRefs`, `methodSpecs`, `genericParams`, `genericParamConstraints`, `customAttributes`, `assemblyRefs`, `files`, and `resources`. Type records include their fields, methods, events, properties, and generic parameters; method records include their parameter rows. Method bodies are loaded with `getMethodBody` or `getInstructions`. Editing methods include `setInstructionOperand`, `setInstructionOpcode`, `insertInstruction`, `removeInstruction`, `setMethodMaxStack`, `replaceMethodSignature`, `replaceCodeViewPath`, `replaceModule`, `replaceField`, `replaceFieldSignature`, `addField`, `insertField`, `removeField`, `addFieldRva`, `replaceFieldRva`, `removeFieldRva`, `addMethod`, `insertMethod`, `addConstant`, `replaceConstant`, `addParam`, `insertParam`, `replaceParam`, `removeParam`, `addProperty`, `insertProperty`, `removeProperty`, `addEvent`, `insertEvent`, `removeEvent`, `addMethodSemantics`, `replaceMethodSemantics`, `removeMethodSemantics`, `replaceAssemblyIdentity`, `signStrongName`, `replaceStrongNamePublicKey`, `verifyStrongNameSignature`, `parseCustomAttribute`, `replaceCustomAttribute`, `addCustomAttribute`, `removeCustomAttribute`, `addManifestResource`, `replaceManifestResource`, `replaceLinkedResource`, `removeManifestResource`, `addTypeRef`, `insertTypeRefSorted`, `removeTypeRef`, `addModuleRef`, `replaceModuleRef`, `removeModuleRef`, `addTypeSpec`, `replaceTypeSpecSignature`, `addStandAloneSig`, `insertStandAloneSig`, `replaceStandAloneSig`, `removeStandAloneSig`, `addMemberRef`, `insertMemberRefSorted`, `addMethodSpec`, `replaceMethodSpecInstantiation`, `insertMethodSpecSorted`, `removeMethodSpec`, `addGenericParam`, `replaceGenericParam`, `removeGenericParam`, `addGenericParamConstraint`, `insertGenericParamConstraint`, `replaceGenericParamConstraint`, `removeGenericParamConstraint`, `addAssemblyRef`, `insertAssemblyRef`, `removeAssemblyRef`, `addAssemblyProcessor`, `replaceAssemblyProcessor`, `removeAssemblyProcessor`, `addAssemblyOS`, `replaceAssemblyOS`, `removeAssemblyOS`, `addAssemblyRefProcessor`, `replaceAssemblyRefProcessor`, `removeAssemblyRefProcessor`, `addAssemblyRefOS`, `replaceAssemblyRefOS`, `removeAssemblyRefOS`, `addFile`, `insertFile`, `removeFile`, and `addUserString`; sorted insertion and removal methods return a token map when existing token RIDs change. `addConstant` and `replaceConstant` accept the ECMA element type and its raw encoded value bytes; adding requires an existing Field, Param, or Property parent and rejects duplicate parents or an append that violates declared table ordering. `replaceModule({ name, generation })` updates the module name or unsigned 16-bit generation while preserving its MVID. `signStrongName(privateKey)` accepts an RSA PEM or Node.js private `KeyObject` and signs during serialization; the key must match the assembly's existing full public key, and a correctly sized signature slot is reused or allocated in a new PE section. `replaceStrongNamePublicKey(privateKey, { hashAlgorithm })` explicitly changes the full Assembly public key/hash ID to match the supplied private key, then requests signing; the Assembly name, version, and culture remain unchanged. `replaceAssemblyIdentity({ name, culture, version })` changes those three manifest identity fields. `verifyStrongNameSignature()` verifies signatures using the embedded RSA public key. Signing supports the standard 16-slot PE optional header when 15 or 16 data directories are declared, and SHA-1, SHA-256, SHA-384, or SHA-512 assembly hash IDs; other directory-count profiles are refused. Custom-attribute and PermissionSet parse/edit methods accept `resolveEnum(name)` and `resolveType(name, context)` options (or properties on their input objects); a type resolver returns `{ assembly, rid }` for a TypeDef from another parsed assembly and can enable external enum, constructor, inheritance, and named-member validation. `addCustomAttribute` appends only when the declared parent ordering remains valid. `replaceLinkedResource(rid, data)` supports resources whose `Implementation` points directly to a File row, updates that File row's hash using the containing Assembly hash algorithm, and leaves the new external payload available as `resources[rid - 1].data`; it does not write the external file. `insertGenericParamConstraint({ ownerRid, constraint })` inserts into an already Owner-ordered GenericParamConstraint table and returns `{ rid, tokenMap }`. `Resource, File, TypeRef, ModuleRef, AssemblyRef, StandAloneSig, MethodSpec, GenericParamConstraint, GenericParam, Property, Event, and CustomAttribute removal rejects rows with live references and remaps surviving references. `serialize` returns a `Uint8Array`, and `toBuffer` returns a Node.js `Buffer`.
|
|
72
|
+
Sorted row insertions are available through `insertConstant`, `insertFieldMarshal`, `insertClassLayout`, `insertFieldLayout`, `insertFieldRva`, `insertImplMap`, `insertMethodSemantics`, `insertMethodImpl`, `insertNestedClass`, `insertGenericParam`, `insertDeclSecurity`, `insertInterfaceImplementation`, `insertCustomAttribute`, and the TypeRef/MemberRef/MethodSpec insertion APIs. Token-row removals for Constant, FieldMarshal, FieldRVA, ClassLayout, FieldLayout, DeclSecurity, MethodSemantics, MethodImpl, ImplMap, and NestedClass reject live references, remap surviving token references, and return a token map; edits that shift tokens are refused with debug data, opaque metadata streams, or incompatible method-body edits.
|
|
73
|
+
`insertFile({ beforeRid, name, flags, hashValue })` inserts at a 1-based File row position (use `files.length + 1` to append), remaps File references, and returns `{ token, tokenMap }`.
|
|
74
|
+
`insertExportedType({ beforeRid, name, namespace, flags, typeDefId, implementation })` inserts at a 1-based ExportedType row position, remaps ExportedType references, and returns `{ token, tokenMap }`.
|
|
75
|
+
`insertManifestResource({ beforeRid, name, flags, implementation, data })` inserts at a 1-based resource row position, remaps ManifestResource references, preserves embedded payload edits, and returns `{ token, tokenMap }`.
|
|
76
|
+
`insertAssemblyRef({ beforeRid, name, culture, version, flags, publicKeyOrToken, hashValue })` inserts at a 1-based AssemblyRef row position, remaps scopes and linked File/ExportedType/resource references plus AssemblyRef platform rows, and returns `{ token, tokenMap }`.
|
|
77
|
+
`insertAssemblyProcessor({ beforeRid, processor })`, `insertAssemblyOS({ beforeRid, platformId, majorVersion, minorVersion })`, `insertAssemblyRefProcessor({ beforeRid, processor, assemblyRefRid })`, and `insertAssemblyRefOS({ beforeRid, platformId, majorVersion, minorVersion, assemblyRefRid })` insert platform rows at 1-based positions and return `{ rid, tokenMap }`. Removing these rows remaps surviving ENC tokens and returns a token map; RID-changing edits refuse debug data, opaque streams, or incompatible method-body edits.
|
|
78
|
+
|
|
79
|
+
Invalid or unsupported structures and edits raise `FormatError` or `UnsupportedFormatError`; they are not silently skipped.
|
|
80
|
+
|
|
81
|
+
`addInterfaceImplementation` appends an InterfaceImpl row to any existing TypeDef when table ordering permits, with duplicate, sort-order, and TypeSpec-context checks. `replaceInterfaceImplementation` changes the interface type without changing its row ID and rejects duplicates or invalid TypeSpec contexts. `removeInterfaceImplementation` rejects live CustomAttribute references, compacts later row references, and returns a row-RID map.
|
|
82
|
+
`insertInterfaceImplementation` inserts by Class into an already Class-ordered InterfaceImpl table and returns `{ rid, tokenMap }`, remapping affected CustomAttribute, CIL, and ENC references.
|
|
83
|
+
`insertCustomAttribute` inserts by Parent into an already Parent-ordered table and returns `{ token, rid, tokenMap }`, remapping later CustomAttribute tokens in CIL and ENC references.
|
|
84
|
+
Custom-attribute constructors must be standard instance, nongeneric methods returning `void`; locally defined constructor types are rejected when their known base chain does not derive from `System.Attribute`. Named arguments on local constructor types are checked against known inherited public writable fields and properties, including metadata signatures and closed local generic base instantiations.
|
|
85
|
+
`insertDeclSecurity` inserts by Parent into an already Parent-ordered table and returns `{ rid, tokenMap }`, remapping shifted DeclSecurity references in metadata, custom attributes, CIL, and ENC records.
|
|
86
|
+
`insertNestedClass` inserts by NestedClass into an already ordered table and returns `{ rid, tokenMap }`, remapping shifted NestedClass tokens in ENC records.
|
|
87
|
+
`insertMethodImpl` inserts by Class into an already Class-ordered table and returns `{ rid, tokenMap }`, remapping shifted MethodImpl tokens in ENC records.
|
|
88
|
+
`insertConstant` inserts by Parent into an already Parent-ordered table and returns `{ token, rid, tokenMap }`, remapping shifted Constant tokens in metadata, CIL operands, and ENC records.
|
|
89
|
+
`insertFieldMarshal` inserts by Parent into an already Parent-ordered table and returns `{ rid, tokenMap }`, remapping shifted FieldMarshal tokens in ENC records.
|
|
90
|
+
`insertMethodSemantics` inserts by Association into an already Association-ordered table and returns `{ rid, tokenMap }`, remapping shifted MethodSemantics tokens in ENC records.
|
|
91
|
+
`insertClassLayout` inserts by Parent into an already Parent-ordered table and returns `{ rid, tokenMap }`, remapping shifted ClassLayout tokens in ENC records.
|
|
92
|
+
`insertFieldLayout` inserts by Field into an already Field-ordered table and returns `{ rid, tokenMap }`, remapping shifted FieldLayout tokens in ENC records.
|
|
93
|
+
`insertImplMap` inserts by MemberForwarded into an already ordered table and returns `{ rid, tokenMap }`, remapping shifted ImplMap tokens in ENC records.
|
|
94
|
+
`insertFieldRva` inserts by Field into an already Field-ordered table and returns `{ rid, tokenMap }`, remapping shifted FieldRVA tokens in ENC records.
|
|
95
|
+
`insertGenericParam` inserts by Owner/Number into an already ordered table and returns `{ token, rid, tokenMap }`, remapping shifted GenericParam references and ENC tokens.
|
|
96
|
+
|
|
97
|
+
`replaceTypeRef` updates a TypeRef name, namespace, or resolution scope without changing its token; `removeTypeRef` rejects live references and remaps surviving metadata, signature, and CIL references.
|
|
98
|
+
|
|
99
|
+
`removeMemberRef` rejects live metadata/CIL references and remaps surviving MemberRef tokens.
|
|
100
|
+
|
|
101
|
+
`replaceMemberRef` updates a MemberRef's name, parent, or signature without changing its token; it validates generic context (including arity inferred from a direct TypeRef parent's backtick suffix) and sorted-row order, preserves MethodSpec arity, and refuses edits to rows used as custom-attribute constructors.
|
|
102
|
+
|
|
103
|
+
An external TypeRef name without an arity suffix has unknown generic arity; generic variables in its MemberRef signatures are preserved rather than incorrectly rejected as if the type were nongeneric.
|
|
104
|
+
|
|
105
|
+
`removeTypeSpec` rejects live references and remaps surviving TypeDefOrRef metadata indexes, signature references, and CIL tokens.
|
|
106
|
+
|
|
107
|
+
`replaceAssemblyRef` updates version, flags, identity strings, public-key/token bytes, or hash bytes without changing the AssemblyRef token. `replaceFile` updates a File row's name, flags, or hash without changing its token or breaking linked ManifestResource references. `addAssemblyProcessor`/`replaceAssemblyProcessor`/`removeAssemblyProcessor`, the corresponding `AssemblyOS` methods, and `AssemblyRefProcessor`/`AssemblyRefOS` methods provide validated platform-table CRUD. Processor/platform fields are checked as unsigned 32-bit values, AssemblyRef links must resolve, and removal is refused when unknown metadata streams could contain references.
|
|
108
|
+
|
|
109
|
+
`replaceManifestResourceMetadata` updates a resource's name and flags, or changes the implementation target of a linked resource; it preserves embedded payloads and refuses conversions between embedded and linked resources.
|
|
110
|
+
|
|
111
|
+
`addExportedType` appends an ExportedType row whose implementation is an existing File, AssemblyRef, or ExportedType row and returns its metadata token. `insertExportedType` inserts before a 1-based row position and remaps supported references. `replaceExportedType` updates its name, namespace, flags, TypeDefId, or implementation without changing its token; cyclic nested-type forwarding is rejected. `removeExportedType` removes an unreferenced row at any RID and remaps surviving references, refusing debug data or unknown metadata streams.
|
|
112
|
+
|
|
113
|
+
`replaceField` updates a Field row's name and flags without changing its token; `addField` appends a validated field to a TypeDef whose field ownership reaches the end of the list and advances later empty TypeDef boundaries. `insertField` inserts into any valid TypeDef range and returns `{ token, tokenMap }`; optimized metadata remaps shifted Field tokens, while `#-` insertion is supported when FieldPtr has a one-to-one mapping. `removeField` rejects live metadata/CIL references, remaps surviving Field tokens, and updates TypeDef list ownership for optimized and supported `#-` metadata.
|
|
114
|
+
|
|
115
|
+
`addFieldRva`, `replaceFieldRva`, and `removeFieldRva` edit FieldRVA rows; exposed Field records include their RVA or `null`. RVA edits validate static fixed-width primitive fields and require the complete value range to be backed by PE file data. Adding/removing rows synchronizes `HasFieldRVA`; changing a Field with FieldRVA cannot clear that flag, make it non-static, or make it variable-sized. `insertFieldRva` and removal remap shifted token references and return token maps.
|
|
116
|
+
|
|
117
|
+
`addParam` appends a validated Param row to a MethodDef whose ParamList reaches the end of the list, advancing later empty MethodDef boundaries. Its sequence must fit the current method signature and preserve existing parameter-row order. `insertParam` inserts by sequence into any valid method range and returns `{ rid, tokenMap }`; optimized metadata remaps shifted Param tokens, while `#-` is supported when ParamPtr has a one-to-one mapping. `replaceParam` updates a Param row's name or flags without changing its row identity. `removeParam` rejects live references and remaps surviving Param row IDs and MethodDef list boundaries.
|
|
118
|
+
|
|
119
|
+
`insertProperty` and `insertEvent` insert at the end of any valid owner range and return `{ rid, tokenMap }`; if the owner has no PropertyMap or EventMap row, insertion creates one in TypeDef order. Optimized metadata remaps shifted tokens, while `#-` insertion requires a bijective PropertyPtr or EventPtr table.
|
|
120
|
+
|
|
121
|
+
`insertStandAloneSig({ beforeRid, signature })` inserts a parsed method or local-variable signature at a 1-based row position and returns `{ token, tokenMap }`; insertion remaps shifted metadata/ENC and CIL tokens plus fat method-header local-signature tokens. With exactly one embedded Portable PDB, it also remaps local-signature references in sequence-point blobs and updates `#Pdb` counts. Other unsafe debug, opaque-stream, or method-body-edit combinations are refused.
|
|
122
|
+
|
|
123
|
+
`removeStandAloneSig` rejects rows referenced by metadata, CIL operands, fat method headers, or embedded Portable PDB sequence points; it remaps surviving StandAloneSig tokens and local-signature references, updating affected sequence-point blobs and `#Pdb` counts. Other debug data and unknown metadata streams make remapping unsafe.
|
|
124
|
+
|
|
125
|
+
`addStandAloneSig({ signature })` appends a parsed method or local-variable signature and returns its stable StandAloneSig token. `replaceStandAloneSig` replaces the blob in place while preserving signature kind; signatures are exposed in `assembly.standAloneSigs`.
|
|
126
|
+
Lazy method-body parsing validates local-variable and `calli` signatures, direct TypeSpec tokens (including catch-clause types), TypeSpec-parented MemberRefs, and MethodSpec instantiations against the caller's type and method generic arities.
|
|
127
|
+
|
|
128
|
+
`addMethod({ owner, name, flags, signature, body })` can append a concrete managed IL method to a TypeDef whose method ownership reaches the end of the list when `body` is a complete, validated tiny/fat method-body byte sequence; later empty TypeDef boundaries advance. `insertMethod` inserts at the end of any valid owner range and returns `{ token, tokenMap }`; optimized MethodDef tokens and the managed CLI entry-point token are remapped, and `#-` insertion uses a bijective MethodPtr table. Body token operands, local signatures, and generic variable contexts are checked against the assembly, including remapping tokens inside a newly inserted body. Both methods can also create a bodyless abstract virtual method on an abstract class or interface. Both forms support generic signatures when the matching GenericParam rows are added, and one-to-one `#-` MethodPtr/ParamPtr layouts. With exactly one embedded Portable PDB, MethodDef insertions are supported when existing MethodDef references can be remapped; recognized state-machine hoisted-local scopes, async stepping method references, and Edit-and-Continue local-slot/lambda-closure CDI Parents are remapped and CustomDebugInformation rows are resorted, while opaque method-scoped CDI remains a refusal boundary. The MethodDebugInformation row is inserted, LocalScope and StateMachineMethod references are remapped, and `#Pdb` counts and entry points are updated. `removeMethod` removes unreferenced methods and their owned Param/GenericParam rows when those ranges are contiguous and have no live references. MethodDef, Param, and GenericParam references are remapped together. With exactly one embedded Portable PDB, method removal is supported when the method has no StateMachineMethod or entry-point references. Its LocalScope rows and owned LocalVariable/LocalConstant ranges are removed, surviving list starts and CDI Parents are remapped, and CDI attached to discarded debug rows is removed; recognized method-scoped CDI and async stepping references are updated/resorted, while opaque method-scoped CDI remains a refusal boundary. Its MethodDebugInformation row, method-token references, and `#Pdb` type-system row counts are updated. The CLI entry-point token is also remapped, while removal of the entry-point method is refused. Other token remapping remains refused when debug data, unknown streams, or added method bodies make it unsafe.
|
|
129
|
+
|
|
130
|
+
`addConstant` and `replaceConstant` operate on raw ECMA element-type/value-byte pairs for existing Field, Param, or Property parents; `addConstant` rejects duplicate parents and declared Parent-order violations. Adding/removing constants synchronizes the parent's `HasDefault` flag, and flag replacement is refused while the row remains. `insertConstant` inserts by Parent into an ordered Constant table and remaps shifted tokens; `removeConstant` rejects live references and remaps surviving Constant tokens, returning a token map.
|
|
131
|
+
|
|
132
|
+
`replaceMethod` updates a MethodDef name, flags, and implementation flags in place without changing its RID.
|
|
133
|
+
|
|
134
|
+
`replaceTypeDef` updates TypeDef names, namespaces, flags, and base-type references in place; it preserves the required `<Module>` row and validates base-type references and known TypeSpec generic contexts.
|
|
135
|
+
|
|
136
|
+
`addTypeDef` appends a TypeDef with a validated optional base type and stable token, initializing optimized or pointer-table ownership boundaries so fields and supported methods can subsequently be appended to it. `insertTypeDef({ beforeRid, name, namespace, flags, extends })` inserts an empty TypeDef before a 1-based row position and returns `{ token, tokenMap }`, remapping known metadata, signature, CIL, and catch-clause references. `removeTypeDef` removes an unreferenced empty non-module type at any RID, remapping later TypeDef references in metadata, signatures, CIL operands, and catch clauses. TypeRef sorted insertion and TypeRef/TypeSpec removal also remap or reject catch-clause tokens when their RIDs change. These operations refuse live references, debug data, unknown streams, sorted-key changes, and unsupported combinations with added method bodies; non-trailing TypeDef removal returns the resulting token map.
|
|
137
|
+
`insertTypeSpec({ beforeRid, signature })` inserts a validated TypeSpec at a 1-based row position and returns `{ token, tokenMap }`, remapping supported metadata, signature, CIL, and EH catch-token references.
|
|
138
|
+
`insertModuleRef({ beforeRid, name })` inserts a ModuleRef at a 1-based row position and returns `{ token, tokenMap }`, updating its token and TypeRef scope references.
|
|
139
|
+
|
|
140
|
+
`addFieldMarshal`, `replaceFieldMarshal`, and `removeFieldMarshal` edit raw native-type descriptor blobs for Field and Param parents; duplicate parents and declared Parent-order violations are rejected on append. Adding/removing descriptors synchronizes `HasFieldMarshal`, and flag replacement is refused while a descriptor remains. `insertFieldMarshal` and removal remap shifted token references and return token maps.
|
|
141
|
+
|
|
142
|
+
ClassLayout and FieldLayout rows can be added, updated, and removed through `addClassLayout`, `replaceClassLayout`, `removeClassLayout`, `addFieldLayout`, `replaceFieldLayout`, and `removeFieldLayout`; parent uniqueness, layout values, and declared row ordering are validated. Sorted insertion APIs and removal remap shifted tokens and return token maps.
|
|
143
|
+
|
|
144
|
+
Nested TypeDef relationships can be added, changed, and removed with `addNestedClass`, `replaceNestedClass`, and `removeNestedClass`; duplicate nested owners, invalid TypeDef references, declared sort order, and relationship cycles are checked. `insertNestedClass` and removal remap shifted tokens and return token maps.
|
|
145
|
+
|
|
146
|
+
DeclSecurity rows can be added or removed, and their action or raw/structured PermissionSet can be replaced, with parent/action uniqueness and declared Parent ordering enforced. `parseDeclSecurity` decodes binary CLI PermissionSet attributes and typed named arguments, or exposes legacy UTF-8 XML payloads as text without parsing or entity expansion. When a security-attribute type resolves to a local or caller-resolved TypeDef, named arguments are checked against known public writable fields and properties. `resolveEnum` and `resolveType` can be supplied when adding, replacing, or parsing a PermissionSet. The exported `encodePermissionSet` serializes parsed binary or XML PermissionSet values. TypeDef and MethodDef `HasSecurity` flags are synchronized with the presence of their security rows, and flag replacement is refused while a row remains. Sorted insertion and removal remap shifted tokens and return token maps.
|
|
147
|
+
|
|
148
|
+
MethodImpl override mappings can be added, replaced, and removed with `addMethodImpl`, `replaceMethodImpl`, and `removeMethodImpl`; references must resolve to method-signature MethodDef/MemberRef rows, and MethodDef bodies must belong to the owning TypeDef. `insertMethodImpl` and removal remap shifted tokens and return token maps.
|
|
149
|
+
|
|
150
|
+
MethodSemantics rows can be added, replaced, and removed with `addMethodSemantics`, `replaceMethodSemantics`, and `removeMethodSemantics`; accessor semantics are restricted by association kind, methods must belong to the associated TypeDef, and getter/setter/add/remove signatures are validated. Sorted insertion and removal remap shifted tokens and return token maps.
|
|
151
|
+
|
|
152
|
+
P/Invoke mappings can be added, updated, and removed with `addImplMap`, `replaceImplMap`, and `removeImplMap`; members must carry the PInvokeImpl flag, scopes must be existing ModuleRef rows, and removing a mapping clears that member flag. `insertImplMap` and removal remap shifted tokens and return token maps.
|
|
153
|
+
|
|
154
|
+
`replacePropertySignature` validates generic variable bounds and requires associated getter/setter signatures to remain compatible; update accessors with `replaceMethodSignature` before changing the property signature.
|
|
155
|
+
|
|
156
|
+
`replaceProperty` updates a property's name and flags without changing its row identity; a property with a Constant row must retain `HasDefault`.
|
|
157
|
+
|
|
158
|
+
`replaceEvent` updates event names, flags, and types in place; a changed event type must remain compatible with associated add/remove accessors.
|
|
159
|
+
|
|
160
|
+
The PE reader accepts power-of-two section/file alignments, enforces the PE low-alignment and standard file-alignment rules, and rejects misaligned header/section ranges, overlapping ranges, and ranges outside file/RVA bounds.
|
|
161
|
+
|
|
162
|
+
## Boundaries and signature behavior
|
|
163
|
+
|
|
164
|
+
Structural rewriting is deliberately limited. Metadata supports table-specific append and insertion operations, including `beforeRid` insertion for TypeDef, ModuleRef, AssemblyRef, StandAloneSig, File, ExportedType, ManifestResource, and assembly platform rows; sorted MemberRef, TypeRef, MethodSpec, and other metadata tables use their declared key order. New MethodDef, Param, Field, Property, and Event rows can target valid owner ranges in optimized metadata or bijective corresponding pointer-table layouts. Property/Event insertion creates a missing owner map when necessary. `#-` pointer tables require a complete one-to-one mapping to target rows; `reorderPointerTable` supports validated cross-owner moves with table-specific generic-context, explicit-layout, parameter-sequence, abstract-method, and association checks. Supported token-row insertions/removals remap metadata/CIL/ENC references and signature or EH tokens where applicable; shared remapping revalidates declared sorted keys and refuses unsafe debug/stream/body combinations or live references to removed rows. Sorted-key violations are rejected instead of resorting other tables, and generalized arbitrary-table insertion/removal or arbitrary table sorting remain unsupported. Edits at ambiguous exception-handler boundaries, removal of branch/EH targets, unsupported method-body layouts, and Authenticode-signed images are rejected.
|
|
165
|
+
|
|
166
|
+
Modified strong-name-signed assemblies have the signature cleared by default; call `signStrongName` before serialization to request re-signing when the private key matches the existing full assembly public key. A missing signature slot is allocated in a new PE section. `replaceStrongNamePublicKey` explicitly updates the Assembly public key and hash algorithm and re-signs; `replaceAssemblyIdentity` can change the name, version, and culture. Signing supports the standard 16-slot PE optional header when 15 or 16 data directories are declared, and SHA-1, SHA-256, SHA-384, or SHA-512 assembly hash IDs; other directory-count profiles are refused. `verifyStrongNameSignature` verifies signatures using the embedded RSA public key. Authenticode-signed PE files can be read unchanged, but edits are refused. Generic metadata/signatures are partially supported: owner arities and TypeSpec contexts are checked across metadata signatures; caller contexts are checked for TypeSpec-parented MemberRefs, MethodSpec and direct TypeSpec CIL operands, local/calli signatures, and catch types when bodies are loaded. TypeSpec and MemberRef replacement revalidate affected contexts atomically, while `parseAssembly` defers CIL caller checks until `getMethodBody` loads each body. Trailing method-owned GenericParam rows can be removed with signature-arity and token remapping. Adding/removing manifest-resource rows, replacing embedded payloads, and remapping surviving references are supported; linked-resource payload replacement updates the File hash and in-memory data, while callers remain responsible for writing the external file. Custom attributes can be parsed/replaced, appended in valid parent order, and removed for supported primitive, string, array, boxed, and enum argument types; `resolveEnum` supplies enum underlyings, while `resolveType` can resolve separately parsed external TypeDefs for enum, constructor, inheritance, and named-member checks. Binary CLI PermissionSet blobs can be decoded and encoded as typed security-attribute named values, with the same external TypeDef resolver available for named-member checks. Legacy UTF-8 XML payloads are exposed or emitted as text without parsing or entity expansion. If external metadata is unresolved, validation remains conservative. PE CodeView records in RSDS and NB10 forms are parsed for their PDB identity and path; paths that fit within the existing record slot can be replaced while preserving record size and PDB identity. Embedded Portable PDB containers expose parsed tables, document names, and decoded sequence points alongside `#Pdb` identity/entry-point/table counts. Classic PDB parsing covers MSF 7.0 streams, info/named streams, DBI module descriptors/source files, bounded module symbols, public/data/thread/constant/procedure/scope/frame/compiler/object/local/register/UDT/namespace records, and C13 line/column records with path resolution. `replaceClassicPdbModuleNames` edits DBI module/object names with DBI/MSF stream rebuilding; `replaceClassicPdbSymbolName` can resize records in modules without scoped or unknown symbols when the existing MSF block allocation is preserved; `/names` path edits preserve UTF-8 byte length. General-purpose Portable PDB rewriting beyond the operations documented above and broader classic PDB rebuilding remain unsupported. Instruction-offset-changing edits automatically rebase Portable PDB sequence points, LocalScope boundaries, state-machine hoisted-local scopes, and async stepping offsets only when exactly one embedded PDB is present and all referenced instruction boundaries survive; opaque method-scoped custom-debug records and other debug-directory layouts are refused. Broad malformed/obfuscated-input hardening remains incomplete.
|
|
167
|
+
`replaceClassicPdbStringTableName` also supports variable-length names by rebuilding MSF streams and remapping C13 checksum references to shifted `/names` offsets.
|
|
168
|
+
|
|
169
|
+
Portable PDB token remapping has a narrow exception to the preceding limitation: non-MethodDef/non-StandAloneSig RID changes are supported when exactly one embedded PDB is present, all custom-debug records are recognized module-scoped SourceLink, DefaultNamespace, CompilationOptions, or CompilationMetadataReferences records, TypeDef-scoped TypeDefinitionDocument records, Document-scoped EmbeddedSource records, or LocalVariable/LocalConstant-scoped DynamicLocalVariables records, and all PE debug-directory entries use recognized formats; their `#Pdb` counts are synchronized. TypeDef insertions/removals remap TypeDefinitionDocument Parents, resort the CDI rows, and discard records owned by a removed TypeDef. Opaque custom-debug records remain a refusal boundary, and MethodDef and StandAloneSig changes use dedicated PDB remappers.
|
|
170
|
+
|
|
171
|
+
Custom-attribute constructor validation resolves local generic TypeSpec parents back to their TypeDef before checking that the type derives from `System.Attribute`. A caller-supplied `resolveType` callback can resolve external TypeRefs to TypeDefs in separately parsed assemblies; when resolved, constructor MemberRefs are matched to declared constructors, base chains are checked, and public writable fields/properties (including inherited members) are validated. External generic inheritance whose signature types cannot be mapped safely remains conservative.
|
|
172
|
+
|
|
173
|
+
Assembly parsing rejects cycles in locally defined TypeDef inheritance chains, including generic TypeSpec base types.
|
|
174
|
+
|
|
175
|
+
`addEmbeddedPortablePdbSource` adds an EmbeddedSource record for a Document that does not already have one and updates its recognized SHA-1/SHA-2 hash. `addEmbeddedPortablePdbSourceLink` appends a module-scoped SourceLink custom-debug record when one does not already exist; the existing replace APIs continue to require an existing record.
|
|
176
|
+
|
|
177
|
+
PE32 and PE32+ CLI assemblies are covered, including PE32+ structural metadata rewriting and output reparsing. The parser requires one Module row, at most one Assembly row, and the special `<Module>` TypeDef first; it also rejects invalid metadata-root stream directories, table-header reserved values, malformed heap entries (including unreferenced entries), and malformed sentinels. Unknown metadata streams are retained byte-for-byte on no-op round trips; operations that remap metadata tokens refuse unknown streams. Supported Field, MethodDef, Param, Property, and Event ownership edits handle `#-` pointer tables only when each table is a bijection to its target table. The implementation otherwise assumes well-formed assemblies from standard compilers; broader malformed/obfuscated-input hardening remains incomplete. Unsupported table/stream layouts and invalid bounds or references encountered during parsing produce explicit errors.
|
|
178
|
+
|
|
179
|
+
The `#-` reader resolves TypeDef field/method/event/property ownership and MethodDef parameter lists through their pointer tables. Supported ownership edits require one-to-one pointer-to-target mappings; non-bijective layouts are explicitly rejected.
|
|
180
|
+
|
|
181
|
+
Generic TypeSpec variables referenced by TypeDef base types, interface implementations, events, generic constraints, method/field/property signatures, and MemberRefs are checked against their known owner context. Recursive TypeSpec-to-TypeSpec references are rejected, including during replacement. Direct-TypeDef MemberRefs use the owner's metadata arity; TypeSpec-parent MemberRef signatures use the parent's instantiated type arity; MethodDef-parented MemberRefs use the declaring type and method arities. Direct-TypeRef MemberRefs infer arity from trailing backtick-number name suffixes, including enclosing TypeRef arities for nested types, and TypeRef renames revalidate their dependent MemberRefs. ModuleRef-parented MemberRefs are validated with no declaring-type variables while retaining generic method parameters from their own signatures. TypeSpec-parented MemberRefs and direct TypeSpec CIL tokens (including catch-clause types) are checked against each caller's generic context, including newly added method bodies; replacing a MemberRef or TypeSpec revalidates affected call sites atomically. MethodSpec insertion/replacement also validates its instantiation at the call site. Direct generic MethodDef/MemberRef tokens in method-token CIL instructions are rejected; generic calls must use MethodSpec tokens. `replaceMethodSignature` revalidates dependent MethodSpec arities and caller contexts before mutation, and adding a TypeDef GenericParam rejects any existing TypeSpec instance whose argument count would no longer match. `parseAssembly` defers caller-context validation for CIL bodies until `getMethodBody` loads them.
|
|
182
|
+
|
|
183
|
+
## Tests
|
|
184
|
+
|
|
185
|
+
`npm test` runs Node.js's built-in test runner across the binary, metadata, signature, IL, assembly, resource, custom-attribute, classic-PDB, and runtime suites. The suites cover compiler-produced input, byte-identical no-op round trips, serialize/reparse and atomic-failure behavior, metadata index-width boundaries, CIL/EH rewriting, generic owner/caller contexts, Portable PDB updates, and classic PDB checksum/line/symbol edits.
|
|
186
|
+
|
|
187
|
+
When a .NET SDK is available, `test/runtime.test.js` also builds managed fixtures and validates rewritten assemblies through CLR loading, reflection, and execution. This acceptance includes structural PE/metadata/IL rewriting, generic methods and embedded Portable PDB edits, custom-attribute values, embedded resources, strong-name signatures, and compiler-produced classic PDB parsing/edit/reparse. These tests are skipped when the SDK is unavailable; the library itself has no .NET runtime dependency.
|
|
188
|
+
|
|
189
|
+
Run `npm run check` for the source syntax check and `git diff --check` for patch whitespace validation. These checks do not replace the runtime tests. The acceptance suite verifies only documented support and refusal boundaries; Tier 3 parity remains incomplete for general-purpose PDB rewriting, unresolved external attribute metadata, and broad malformed/obfuscated-input recovery.
|
|
190
|
+
|
|
191
|
+
Run the suite with `npm test`; run the package syntax check with `npm run check`.
|