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/src/writer/pe.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { FormatError, UnsupportedFormatError } from "../binary/errors.js";
|
|
2
|
+
|
|
3
|
+
function align(value, alignment) {
|
|
4
|
+
if (!Number.isInteger(alignment) || alignment < 1 || (alignment & (alignment - 1)) !== 0) {
|
|
5
|
+
throw new UnsupportedFormatError(`Unsupported PE alignment ${alignment}`);
|
|
6
|
+
}
|
|
7
|
+
return Math.ceil(value / alignment) * alignment;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function writeU16(view, offset, value, label) {
|
|
11
|
+
if (!Number.isInteger(value) || value < 0 || value > 0xffff) throw new RangeError(`${label} exceeds 16-bit range`);
|
|
12
|
+
view.setUint16(offset, value, true);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function writeU32(view, offset, value, label) {
|
|
16
|
+
if (!Number.isInteger(value) || value < 0 || value > 0xffffffff) throw new RangeError(`${label} exceeds 32-bit range`);
|
|
17
|
+
view.setUint32(offset, value, true);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getNextSectionVirtualAddress(pe) {
|
|
21
|
+
let maxVirtualEnd = pe.sizeOfImage;
|
|
22
|
+
for (const section of pe.sections) {
|
|
23
|
+
maxVirtualEnd = Math.max(maxVirtualEnd, section.virtualAddress + Math.max(section.virtualSize, section.rawSize));
|
|
24
|
+
}
|
|
25
|
+
return align(maxVirtualEnd, pe.sectionAlignment);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function expandHeaderForSection(input, pe) {
|
|
29
|
+
const firstRawPointer = Math.min(
|
|
30
|
+
...pe.sections.filter((section) => section.rawSize > 0).map((section) => section.rawPointer),
|
|
31
|
+
pe.sizeOfHeaders
|
|
32
|
+
);
|
|
33
|
+
const sectionHeaderOffset = pe.sectionOffset + pe.sectionCount * 40;
|
|
34
|
+
const newHeaderEnd = sectionHeaderOffset + 40;
|
|
35
|
+
if (sectionHeaderOffset > firstRawPointer) {
|
|
36
|
+
throw new UnsupportedFormatError("PE section headers overlap section data");
|
|
37
|
+
}
|
|
38
|
+
const required = newHeaderEnd - Math.min(pe.sizeOfHeaders, firstRawPointer);
|
|
39
|
+
const delta = required > 0 ? align(required, pe.fileAlignment) : 0;
|
|
40
|
+
const insertionOffset = firstRawPointer;
|
|
41
|
+
const output = new Uint8Array(input.length + delta);
|
|
42
|
+
output.set(input.subarray(0, insertionOffset));
|
|
43
|
+
output.set(input.subarray(insertionOffset), insertionOffset + delta);
|
|
44
|
+
const view = new DataView(output.buffer);
|
|
45
|
+
|
|
46
|
+
if (delta > 0) {
|
|
47
|
+
for (let index = 0; index < pe.sections.length; index++) {
|
|
48
|
+
const header = pe.sectionOffset + index * 40;
|
|
49
|
+
for (const fieldOffset of [20, 24, 28]) {
|
|
50
|
+
const pointer = view.getUint32(header + fieldOffset, true);
|
|
51
|
+
if (pointer >= insertionOffset && pointer !== 0) view.setUint32(header + fieldOffset, pointer + delta, true);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (pe.symbolTablePointer >= insertionOffset && pe.symbolTablePointer !== 0) {
|
|
55
|
+
view.setUint32(pe.peOffset + 12, pe.symbolTablePointer + delta, true);
|
|
56
|
+
}
|
|
57
|
+
view.setUint32(pe.optionalHeaderOffset + 60, pe.sizeOfHeaders + delta, true);
|
|
58
|
+
const debugDirectory = pe.directories[6];
|
|
59
|
+
if (debugDirectory?.rva && debugDirectory.size > 0) {
|
|
60
|
+
if (debugDirectory.size % 28 !== 0) {
|
|
61
|
+
throw new UnsupportedFormatError("Debug directory size is not a multiple of IMAGE_DEBUG_DIRECTORY");
|
|
62
|
+
}
|
|
63
|
+
const originalOffset = pe.rvaToOffset(debugDirectory.rva, debugDirectory.size);
|
|
64
|
+
if (originalOffset < insertionOffset && originalOffset + debugDirectory.size > insertionOffset) {
|
|
65
|
+
throw new UnsupportedFormatError("Debug directory crosses the PE header expansion boundary");
|
|
66
|
+
}
|
|
67
|
+
const debugOffset = originalOffset >= insertionOffset ? originalOffset + delta : originalOffset;
|
|
68
|
+
for (let offset = debugOffset; offset < debugOffset + debugDirectory.size; offset += 28) {
|
|
69
|
+
const pointer = view.getUint32(offset + 24, true);
|
|
70
|
+
if (pointer >= insertionOffset && pointer !== 0) view.setUint32(offset + 24, pointer + delta, true);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { bytes: output, delta, insertionOffset, sectionHeaderOffset, firstRawPointer: firstRawPointer + delta };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function appendPeSection(
|
|
78
|
+
input,
|
|
79
|
+
pe,
|
|
80
|
+
cli,
|
|
81
|
+
payload,
|
|
82
|
+
metadataOffset,
|
|
83
|
+
metadataSize,
|
|
84
|
+
resourcesDirectory = null,
|
|
85
|
+
preserveStrongName = false,
|
|
86
|
+
debugDirectoryPayload = null
|
|
87
|
+
) {
|
|
88
|
+
if (payload.length === 0) throw new RangeError("Cannot add an empty PE section");
|
|
89
|
+
if (pe.sectionCount >= 0xffff) throw new UnsupportedFormatError("PE section-count limit reached");
|
|
90
|
+
const fileAlignment = pe.fileAlignment;
|
|
91
|
+
const sectionAlignment = pe.sectionAlignment;
|
|
92
|
+
const expanded = expandHeaderForSection(input, pe);
|
|
93
|
+
const rawPointer = align(expanded.bytes.length, fileAlignment);
|
|
94
|
+
const rawSize = align(payload.length, fileAlignment);
|
|
95
|
+
const sectionHeaderOffset = expanded.sectionHeaderOffset;
|
|
96
|
+
if (sectionHeaderOffset + 40 > pe.sizeOfHeaders + expanded.delta ||
|
|
97
|
+
sectionHeaderOffset + 40 > expanded.firstRawPointer) {
|
|
98
|
+
throw new UnsupportedFormatError("PE headers have no room for an additional section header");
|
|
99
|
+
}
|
|
100
|
+
const virtualAddress = getNextSectionVirtualAddress(pe);
|
|
101
|
+
const imageSize = align(virtualAddress + payload.length, sectionAlignment);
|
|
102
|
+
if (rawPointer + rawSize > 0xffffffff || imageSize > 0xffffffff) {
|
|
103
|
+
throw new RangeError("Rewritten PE exceeds 32-bit image limits");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const output = new Uint8Array(rawPointer + rawSize);
|
|
107
|
+
output.set(expanded.bytes);
|
|
108
|
+
output.set(payload, rawPointer);
|
|
109
|
+
const view = new DataView(output.buffer);
|
|
110
|
+
view.setUint16(pe.peOffset + 6, pe.sectionCount + 1, true);
|
|
111
|
+
writeU32(view, pe.optionalHeaderOffset + 4, pe.sizeOfCode + rawSize, "SizeOfCode");
|
|
112
|
+
writeU32(view, pe.optionalHeaderOffset + 56, imageSize, "SizeOfImage");
|
|
113
|
+
view.setUint32(pe.checksumOffset, 0, true);
|
|
114
|
+
|
|
115
|
+
output.set(new TextEncoder().encode(".jsilnet"), sectionHeaderOffset);
|
|
116
|
+
writeU32(view, sectionHeaderOffset + 8, payload.length, "section virtual size");
|
|
117
|
+
writeU32(view, sectionHeaderOffset + 12, virtualAddress, "section virtual address");
|
|
118
|
+
writeU32(view, sectionHeaderOffset + 16, rawSize, "section raw size");
|
|
119
|
+
writeU32(view, sectionHeaderOffset + 20, rawPointer, "section raw pointer");
|
|
120
|
+
writeU32(view, sectionHeaderOffset + 24, 0, "section relocation pointer");
|
|
121
|
+
writeU32(view, sectionHeaderOffset + 28, 0, "section line-number pointer");
|
|
122
|
+
writeU16(view, sectionHeaderOffset + 32, 0, "section relocation count");
|
|
123
|
+
writeU16(view, sectionHeaderOffset + 34, 0, "section line-number count");
|
|
124
|
+
writeU32(view, sectionHeaderOffset + 36, 0x60000020, "section characteristics");
|
|
125
|
+
|
|
126
|
+
const shiftedCliOffset = cli.offset >= expanded.insertionOffset ? cli.offset + expanded.delta : cli.offset;
|
|
127
|
+
if (metadataOffset !== null) {
|
|
128
|
+
const newMetadataRva = virtualAddress + metadataOffset;
|
|
129
|
+
writeU32(view, shiftedCliOffset + 8, newMetadataRva, "CLI metadata RVA");
|
|
130
|
+
writeU32(view, shiftedCliOffset + 12, metadataSize, "CLI metadata size");
|
|
131
|
+
}
|
|
132
|
+
if (resourcesDirectory) {
|
|
133
|
+
writeU32(view, shiftedCliOffset + 24, resourcesDirectory.rva, "CLI resources RVA");
|
|
134
|
+
writeU32(view, shiftedCliOffset + 28, resourcesDirectory.size, "CLI resources size");
|
|
135
|
+
}
|
|
136
|
+
if (!preserveStrongName && (cli.flags & 0x08) !== 0) {
|
|
137
|
+
const signature = cli.strongNameSignature;
|
|
138
|
+
if (signature.rva !== 0 && signature.size !== 0) {
|
|
139
|
+
const oldOffset = pe.rvaToOffset(signature.rva, signature.size);
|
|
140
|
+
const signatureOffset = oldOffset >= expanded.insertionOffset ? oldOffset + expanded.delta : oldOffset;
|
|
141
|
+
output.fill(0, signatureOffset, signatureOffset + signature.size);
|
|
142
|
+
}
|
|
143
|
+
writeU32(view, shiftedCliOffset + 16, cli.flags & ~0x08, "CLI flags");
|
|
144
|
+
writeU32(view, shiftedCliOffset + 32, 0, "strong-name RVA");
|
|
145
|
+
writeU32(view, shiftedCliOffset + 36, 0, "strong-name size");
|
|
146
|
+
}
|
|
147
|
+
if (debugDirectoryPayload) {
|
|
148
|
+
const directoryBase = pe.optionalHeaderOffset +
|
|
149
|
+
(pe.optionalHeaderMagic === 0x10b ? 96 : 112);
|
|
150
|
+
const directoryField = directoryBase + 6 * 8;
|
|
151
|
+
if (directoryField + 8 > pe.optionalHeaderOffset + pe.optionalHeaderSize) {
|
|
152
|
+
throw new UnsupportedFormatError("PE optional header has no debug data-directory slot");
|
|
153
|
+
}
|
|
154
|
+
writeU32(
|
|
155
|
+
view,
|
|
156
|
+
directoryField,
|
|
157
|
+
virtualAddress + debugDirectoryPayload.tableOffset,
|
|
158
|
+
"debug directory RVA"
|
|
159
|
+
);
|
|
160
|
+
writeU32(view, directoryField + 4, debugDirectoryPayload.size, "debug directory size");
|
|
161
|
+
for (const entry of debugDirectoryPayload.preservedEntries) {
|
|
162
|
+
const pointer = entry.pointer >= expanded.insertionOffset && entry.pointer !== 0
|
|
163
|
+
? entry.pointer + expanded.delta
|
|
164
|
+
: entry.pointer;
|
|
165
|
+
writeU32(view, rawPointer + entry.pointerOffset, pointer, "debug record raw pointer");
|
|
166
|
+
}
|
|
167
|
+
for (const entry of debugDirectoryPayload.relocatedEntries) {
|
|
168
|
+
writeU32(
|
|
169
|
+
view,
|
|
170
|
+
rawPointer + entry.entryOffset + 16,
|
|
171
|
+
entry.size,
|
|
172
|
+
"debug record data size"
|
|
173
|
+
);
|
|
174
|
+
writeU32(
|
|
175
|
+
view,
|
|
176
|
+
rawPointer + entry.entryOffset + 20,
|
|
177
|
+
virtualAddress + entry.dataOffset,
|
|
178
|
+
"debug record data RVA"
|
|
179
|
+
);
|
|
180
|
+
writeU32(
|
|
181
|
+
view,
|
|
182
|
+
rawPointer + entry.entryOffset + 24,
|
|
183
|
+
rawPointer + entry.dataOffset,
|
|
184
|
+
"debug record raw pointer"
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
bytes: output,
|
|
190
|
+
virtualAddress,
|
|
191
|
+
rawPointer,
|
|
192
|
+
rawSize,
|
|
193
|
+
headerExpansion: expanded.delta,
|
|
194
|
+
cliOffset: shiftedCliOffset
|
|
195
|
+
};
|
|
196
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { FormatError } from "../binary/errors.js";
|
|
2
|
+
import { serializeMetadataRoot } from "../metadata/root-writer.js";
|
|
3
|
+
import { serializeTablesStream } from "../metadata/writer.js";
|
|
4
|
+
import { encodeMethodBody } from "../il/rewrite.js";
|
|
5
|
+
import { appendPeSection, getNextSectionVirtualAddress } from "./pe.js";
|
|
6
|
+
import { applyPatches } from "./patch.js";
|
|
7
|
+
|
|
8
|
+
function align(value, alignment) {
|
|
9
|
+
return Math.ceil(value / alignment) * alignment;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function writeDebugDirectory(payload, tableOffset, assembly, replacements) {
|
|
13
|
+
const entries = assembly.pe.debugDirectoryEntries;
|
|
14
|
+
const tableSize = entries.length * 28;
|
|
15
|
+
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
16
|
+
const preservedEntries = [];
|
|
17
|
+
const relocatedEntries = [];
|
|
18
|
+
let dataOffset = align(tableOffset + tableSize, 4);
|
|
19
|
+
for (let index = 0; index < entries.length; index++) {
|
|
20
|
+
const entry = entries[index];
|
|
21
|
+
const entryOffset = tableOffset + index * 28;
|
|
22
|
+
const replacementValue = replacements.get(index);
|
|
23
|
+
const replacement = replacementValue?.bytes ?? replacementValue;
|
|
24
|
+
view.setUint32(entryOffset, entry.characteristics, true);
|
|
25
|
+
view.setUint32(
|
|
26
|
+
entryOffset + 4,
|
|
27
|
+
replacementValue?.timeDateStamp ?? entry.timeDateStamp,
|
|
28
|
+
true
|
|
29
|
+
);
|
|
30
|
+
view.setUint16(entryOffset + 8, entry.majorVersion, true);
|
|
31
|
+
view.setUint16(entryOffset + 10, entry.minorVersion, true);
|
|
32
|
+
view.setUint32(entryOffset + 12, entry.type, true);
|
|
33
|
+
if (replacement) {
|
|
34
|
+
view.setUint32(entryOffset + 16, replacement.length, true);
|
|
35
|
+
relocatedEntries.push({
|
|
36
|
+
entryOffset,
|
|
37
|
+
dataOffset,
|
|
38
|
+
size: replacement.length
|
|
39
|
+
});
|
|
40
|
+
payload.set(replacement, dataOffset);
|
|
41
|
+
dataOffset = align(dataOffset + replacement.length, 4);
|
|
42
|
+
} else {
|
|
43
|
+
view.setUint32(entryOffset + 16, entry.sizeOfData, true);
|
|
44
|
+
view.setUint32(entryOffset + 20, entry.addressOfRawData, true);
|
|
45
|
+
view.setUint32(entryOffset + 24, entry.pointerToRawData, true);
|
|
46
|
+
if (entry.pointerToRawData !== 0) {
|
|
47
|
+
preservedEntries.push({
|
|
48
|
+
pointerOffset: entryOffset + 24,
|
|
49
|
+
pointer: entry.pointerToRawData
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
size: tableSize,
|
|
56
|
+
preservedEntries,
|
|
57
|
+
relocatedEntries,
|
|
58
|
+
endOffset: dataOffset
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cloneTables(tables) {
|
|
63
|
+
const rows = new Map();
|
|
64
|
+
for (const [id, tableRows] of tables.rows) {
|
|
65
|
+
rows.set(id, tableRows.map((row) => {
|
|
66
|
+
const copy = { ...row };
|
|
67
|
+
for (const [key, value] of Object.entries(copy)) {
|
|
68
|
+
if (value && typeof value === "object") {
|
|
69
|
+
copy[key] = value.target !== undefined
|
|
70
|
+
? { ...value, target: value.target ? { ...value.target } : null }
|
|
71
|
+
: { ...value };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return copy;
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
return { ...tables, rows, counts: [...tables.counts] };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getHeapStreams(assembly) {
|
|
81
|
+
if (assembly._heapStreams) {
|
|
82
|
+
return new Map([...assembly._heapStreams].map(([name, bytes]) => [name, bytes.slice()]));
|
|
83
|
+
}
|
|
84
|
+
return new Map([...assembly.metadataRoot.streams].map(([name, stream]) => [name, stream.bytes.slice()]));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildMetadata(assembly, tables, heapStreams, tableStreamName) {
|
|
88
|
+
const streams = new Map(heapStreams);
|
|
89
|
+
streams.set(tableStreamName, serializeTablesStream(tables, heapStreams));
|
|
90
|
+
return serializeMetadataRoot(assembly.metadataRoot, streams);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildResourcePayload(assembly, tables) {
|
|
94
|
+
if (assembly._resourceEdits.size === 0) return null;
|
|
95
|
+
const rows = tables.rows.get(40) ?? [];
|
|
96
|
+
const entries = [];
|
|
97
|
+
let size = 0;
|
|
98
|
+
for (const resource of assembly.resources) {
|
|
99
|
+
if (resource.implementation !== null) continue;
|
|
100
|
+
const data = assembly._resourceEdits.get(resource.rid) ?? resource.data;
|
|
101
|
+
if (!(data instanceof Uint8Array)) {
|
|
102
|
+
throw new FormatError(`Embedded ManifestResource row ${resource.rid} has no payload`);
|
|
103
|
+
}
|
|
104
|
+
const offset = align(size, 8);
|
|
105
|
+
if (offset > 0xffffffff) throw new RangeError("Manifest resource offset exceeds 32-bit range");
|
|
106
|
+
const row = rows[resource.rid - 1];
|
|
107
|
+
if (!row || row.rid !== resource.rid) {
|
|
108
|
+
throw new FormatError(`ManifestResource row ${resource.rid} is missing from the metadata tables`);
|
|
109
|
+
}
|
|
110
|
+
row.Offset = offset;
|
|
111
|
+
if (data.length > 0xffffffff) throw new RangeError("Manifest resource exceeds the CLI size limit");
|
|
112
|
+
entries.push({ offset, data });
|
|
113
|
+
size = offset + 4 + data.length;
|
|
114
|
+
if (size > 0xffffffff) throw new RangeError("CLI resources directory exceeds 32-bit range");
|
|
115
|
+
}
|
|
116
|
+
if (entries.length === 0) throw new FormatError("No embedded resources are available for replacement");
|
|
117
|
+
const output = new Uint8Array(size);
|
|
118
|
+
const view = new DataView(output.buffer);
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
view.setUint32(entry.offset, entry.data.length, true);
|
|
121
|
+
output.set(entry.data, entry.offset + 4);
|
|
122
|
+
}
|
|
123
|
+
return output;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function serializeStructural(
|
|
127
|
+
assembly,
|
|
128
|
+
{ preserveStrongName = false, strongNameSignatureSize = 0 } = {}
|
|
129
|
+
) {
|
|
130
|
+
const source = applyPatches(assembly.bytes, assembly._patches, assembly.cli, assembly.pe, {
|
|
131
|
+
preserveStrongName
|
|
132
|
+
});
|
|
133
|
+
const tables = cloneTables(assembly.tables);
|
|
134
|
+
const heapStreams = getHeapStreams(assembly);
|
|
135
|
+
const tableStreamName = assembly.metadataRoot.streams.has("#~") ? "#~" : "#-";
|
|
136
|
+
let metadata = buildMetadata(assembly, tables, heapStreams, tableStreamName);
|
|
137
|
+
const virtualAddress = getNextSectionVirtualAddress(assembly.pe);
|
|
138
|
+
let bodyOffset = align(metadata.length, 4);
|
|
139
|
+
const encodedBodies = [];
|
|
140
|
+
const bodies = new Map(assembly._addedMethodBodies ?? []);
|
|
141
|
+
for (const [methodRid, state] of assembly._structuralBodies) bodies.set(methodRid, state);
|
|
142
|
+
for (const [methodRid, state] of [...bodies].sort(([left], [right]) => left - right)) {
|
|
143
|
+
const methodRow = tables.rows.get(6)?.[methodRid - 1];
|
|
144
|
+
if (!methodRow) throw new FormatError(`MethodDef row ${methodRid} is missing from the metadata tables`);
|
|
145
|
+
const methodRva = virtualAddress + bodyOffset;
|
|
146
|
+
if (methodRva > 0xffffffff) throw new RangeError("Rewritten method RVA exceeds 32-bit range");
|
|
147
|
+
const bytes = state instanceof Uint8Array
|
|
148
|
+
? state
|
|
149
|
+
: encodeMethodBody(state, methodRva);
|
|
150
|
+
methodRow.RVA = methodRva;
|
|
151
|
+
encodedBodies.push({ offset: bodyOffset, bytes });
|
|
152
|
+
bodyOffset = align(bodyOffset + bytes.length, 4);
|
|
153
|
+
}
|
|
154
|
+
const resourcePayload = buildResourcePayload(assembly, tables);
|
|
155
|
+
const resourceOffset = resourcePayload ? align(bodyOffset, 8) : 0;
|
|
156
|
+
const resourceEnd = resourcePayload ? resourceOffset + resourcePayload.length : bodyOffset;
|
|
157
|
+
const signatureOffset = strongNameSignatureSize > 0 ? align(resourceEnd, 4) : 0;
|
|
158
|
+
const signatureEnd = strongNameSignatureSize > 0
|
|
159
|
+
? signatureOffset + strongNameSignatureSize
|
|
160
|
+
: resourceEnd;
|
|
161
|
+
const hasDebugEdits = (assembly._embeddedPortablePdbEdits?.size ?? 0) > 0;
|
|
162
|
+
const debugTableOffset = hasDebugEdits ? align(signatureEnd, 4) : 0;
|
|
163
|
+
const debugTableSize = hasDebugEdits
|
|
164
|
+
? assembly.pe.debugDirectoryEntries.length * 28
|
|
165
|
+
: 0;
|
|
166
|
+
const debugDataStart = hasDebugEdits
|
|
167
|
+
? align(debugTableOffset + debugTableSize, 4)
|
|
168
|
+
: 0;
|
|
169
|
+
const debugRecordEdits = new Map(assembly._embeddedPortablePdbEdits ?? []);
|
|
170
|
+
for (const [index, edit] of assembly._debugRecordEdits ?? []) {
|
|
171
|
+
debugRecordEdits.set(index, edit);
|
|
172
|
+
}
|
|
173
|
+
let finalPayloadSize = hasDebugEdits ? debugDataStart : signatureEnd;
|
|
174
|
+
if (hasDebugEdits) {
|
|
175
|
+
for (const [index, replacementValue] of [...debugRecordEdits].sort(
|
|
176
|
+
([left], [right]) => left - right
|
|
177
|
+
)) {
|
|
178
|
+
if (!assembly.pe.debugDirectoryEntries[index]) {
|
|
179
|
+
throw new FormatError(`Debug-directory entry ${index} is missing`);
|
|
180
|
+
}
|
|
181
|
+
const replacement = replacementValue?.bytes ?? replacementValue;
|
|
182
|
+
finalPayloadSize = align(finalPayloadSize, 4) + replacement.length;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const rewrittenMetadata = buildMetadata(assembly, tables, heapStreams, tableStreamName);
|
|
186
|
+
if (rewrittenMetadata.length !== metadata.length) {
|
|
187
|
+
throw new FormatError("Metadata size changed while assigning method RVAs");
|
|
188
|
+
}
|
|
189
|
+
metadata = rewrittenMetadata;
|
|
190
|
+
|
|
191
|
+
const payload = new Uint8Array(finalPayloadSize);
|
|
192
|
+
payload.set(metadata);
|
|
193
|
+
for (const body of encodedBodies) payload.set(body.bytes, body.offset);
|
|
194
|
+
if (resourcePayload) payload.set(resourcePayload, resourceOffset);
|
|
195
|
+
let debugDirectoryPayload = null;
|
|
196
|
+
if (hasDebugEdits) {
|
|
197
|
+
debugDirectoryPayload = writeDebugDirectory(
|
|
198
|
+
payload,
|
|
199
|
+
debugTableOffset,
|
|
200
|
+
assembly,
|
|
201
|
+
debugRecordEdits
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
const resourcesDirectory = resourcePayload
|
|
205
|
+
? { rva: virtualAddress + resourceOffset, size: resourcePayload.length }
|
|
206
|
+
: assembly._clearResourcesDirectory
|
|
207
|
+
? { rva: 0, size: 0 }
|
|
208
|
+
: null;
|
|
209
|
+
const appended = appendPeSection(
|
|
210
|
+
source,
|
|
211
|
+
assembly.pe,
|
|
212
|
+
assembly.cli,
|
|
213
|
+
payload,
|
|
214
|
+
0,
|
|
215
|
+
metadata.length,
|
|
216
|
+
resourcesDirectory,
|
|
217
|
+
preserveStrongName,
|
|
218
|
+
hasDebugEdits
|
|
219
|
+
? {
|
|
220
|
+
tableOffset: debugTableOffset,
|
|
221
|
+
size: debugDirectoryPayload.size,
|
|
222
|
+
preservedEntries: debugDirectoryPayload.preservedEntries,
|
|
223
|
+
relocatedEntries: debugDirectoryPayload.relocatedEntries
|
|
224
|
+
}
|
|
225
|
+
: null
|
|
226
|
+
);
|
|
227
|
+
if (strongNameSignatureSize > 0) {
|
|
228
|
+
const signatureRva = virtualAddress + signatureOffset;
|
|
229
|
+
if (signatureRva > 0xffffffff) throw new RangeError("Strong-name signature RVA exceeds 32-bit range");
|
|
230
|
+
const view = new DataView(
|
|
231
|
+
appended.bytes.buffer,
|
|
232
|
+
appended.bytes.byteOffset,
|
|
233
|
+
appended.bytes.byteLength
|
|
234
|
+
);
|
|
235
|
+
const directoryOffset = appended.cliOffset +
|
|
236
|
+
assembly.cli.strongNameDirectoryOffset - assembly.cli.offset;
|
|
237
|
+
view.setUint32(directoryOffset, signatureRva, true);
|
|
238
|
+
view.setUint32(directoryOffset + 4, strongNameSignatureSize, true);
|
|
239
|
+
}
|
|
240
|
+
return appended.bytes;
|
|
241
|
+
}
|