jsilnet 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +98 -3
- package/REFERENCE.md +191 -0
- package/dist/browser/index.js +345 -0
- package/dist/browser/worker.js +42480 -0
- package/example/README.md +22 -0
- package/example/Sample.cs +10 -0
- package/example/Sample.csproj +7 -0
- package/example/edit-constant.js +48 -0
- package/example/inspect.js +37 -0
- package/package.json +61 -6
- package/src/assembly/instruction-editing.js +165 -0
- package/src/assembly/method-removal.js +789 -0
- package/src/assembly/pointer-table-editing.js +506 -0
- package/src/assembly/portable-pdb-editing.js +441 -0
- package/src/binary/errors.js +17 -0
- package/src/binary/reader.js +206 -0
- package/src/browser/index.d.ts +123 -0
- package/src/browser/index.js +373 -0
- package/src/browser/worker-runtime.js +287 -0
- package/src/browser/worker.js +47 -0
- package/src/il/body.js +165 -0
- package/src/il/instructions.js +217 -0
- package/src/il/opcodes.js +102 -0
- package/src/il/rewrite.js +331 -0
- package/src/index.d.ts +267 -0
- package/src/index.js +13307 -0
- package/src/metadata/classic-pdb.js +2530 -0
- package/src/metadata/custom-attributes.js +387 -0
- package/src/metadata/portable-pdb.js +3347 -0
- package/src/metadata/resources.js +37 -0
- package/src/metadata/root-writer.js +54 -0
- package/src/metadata/signatures.js +339 -0
- package/src/metadata/streams.js +285 -0
- package/src/metadata/strong-name.js +230 -0
- package/src/metadata/tables.js +390 -0
- package/src/metadata/writer.js +191 -0
- package/src/pe/headers.js +491 -0
- package/src/platform/bytes.js +21 -0
- package/src/platform/compression.js +24 -0
- package/src/platform/crypto.js +17 -0
- package/src/resolution/module.js +64 -0
- package/src/types/assembly/instruction-editing.d.ts +33 -0
- package/src/types/assembly/method-removal.d.ts +3 -0
- package/src/types/assembly/pointer-table-editing.d.ts +3 -0
- package/src/types/assembly/portable-pdb-editing.d.ts +23 -0
- package/src/types/binary/errors.d.ts +7 -0
- package/src/types/binary/reader.d.ts +30 -0
- package/src/types/il/body.d.ts +51 -0
- package/src/types/il/instructions.d.ts +33 -0
- package/src/types/il/opcodes.d.ts +2 -0
- package/src/types/il/rewrite.d.ts +38 -0
- package/src/types/index.d.ts +864 -0
- package/src/types/metadata/classic-pdb.d.ts +688 -0
- package/src/types/metadata/custom-attributes.d.ts +28 -0
- package/src/types/metadata/portable-pdb.d.ts +296 -0
- package/src/types/metadata/resources.d.ts +1 -0
- package/src/types/metadata/root-writer.d.ts +1 -0
- package/src/types/metadata/signatures.d.ts +5 -0
- package/src/types/metadata/streams.d.ts +26 -0
- package/src/types/metadata/strong-name.d.ts +11 -0
- package/src/types/metadata/tables.d.ts +25 -0
- package/src/types/metadata/writer.d.ts +16 -0
- package/src/types/pe/headers.d.ts +160 -0
- package/src/types/platform/bytes.d.ts +3 -0
- package/src/types/platform/compression.d.ts +2 -0
- package/src/types/platform/crypto.d.ts +4 -0
- package/src/types/resolution/module.d.ts +12 -0
- package/src/types/writer/patch.d.ts +4 -0
- package/src/types/writer/pe.d.ts +9 -0
- package/src/types/writer/structural.d.ts +4 -0
- package/src/writer/patch.js +42 -0
- package/src/writer/pe.js +196 -0
- package/src/writer/structural.js +241 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { BinaryReader } from "../binary/reader.js";
|
|
2
|
+
import { FormatError, UnsupportedFormatError } from "../binary/errors.js";
|
|
3
|
+
|
|
4
|
+
const tableNames = [
|
|
5
|
+
"Module", "TypeRef", "TypeDef", "FieldPtr", "Field", "MethodPtr", "MethodDef", "ParamPtr", "Param",
|
|
6
|
+
"InterfaceImpl", "MemberRef", "Constant", "CustomAttribute", "FieldMarshal", "DeclSecurity", "ClassLayout",
|
|
7
|
+
"FieldLayout", "StandAloneSig", "EventMap", "EventPtr", "Event", "PropertyMap", "PropertyPtr", "Property",
|
|
8
|
+
"MethodSemantics", "MethodImpl", "ModuleRef", "TypeSpec", "ImplMap", "FieldRVA", "ENCLog", "ENCMap",
|
|
9
|
+
"Assembly", "AssemblyProcessor", "AssemblyOS", "AssemblyRef", "AssemblyRefProcessor", "AssemblyRefOS",
|
|
10
|
+
"File", "ExportedType", "ManifestResource", "NestedClass", "GenericParam", "MethodSpec", "GenericParamConstraint",
|
|
11
|
+
null, null, null,
|
|
12
|
+
"Document", "MethodDebugInformation", "LocalScope", "LocalVariable", "LocalConstant", "ImportScope",
|
|
13
|
+
"StateMachineMethod", "CustomDebugInformation"
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const codedIndexes = {
|
|
17
|
+
TypeDefOrRef: { bits: 2, tables: [2, 1, 27] },
|
|
18
|
+
HasConstant: { bits: 2, tables: [4, 8, 23] },
|
|
19
|
+
HasCustomAttribute: { bits: 5, tables: [6, 4, 1, 2, 8, 9, 10, 0, 14, 23, 20, 17, 26, 27, 32, 35, 38, 39, 40, 42, 44, 43] },
|
|
20
|
+
HasFieldMarshal: { bits: 1, tables: [4, 8] },
|
|
21
|
+
HasDeclSecurity: { bits: 2, tables: [2, 6, 32] },
|
|
22
|
+
MemberRefParent: { bits: 3, tables: [2, 1, 26, 6, 27] },
|
|
23
|
+
HasSemantics: { bits: 1, tables: [20, 23] },
|
|
24
|
+
MethodDefOrRef: { bits: 1, tables: [6, 10] },
|
|
25
|
+
MemberForwarded: { bits: 1, tables: [4, 6] },
|
|
26
|
+
Implementation: { bits: 2, tables: [38, 35, 39] },
|
|
27
|
+
CustomAttributeType: { bits: 3, tables: [null, null, 6, 10, null] },
|
|
28
|
+
ResolutionScope: { bits: 2, tables: [0, 26, 35, 1] },
|
|
29
|
+
TypeOrMethodDef: { bits: 1, tables: [2, 6] },
|
|
30
|
+
HasCustomDebugInformation: {
|
|
31
|
+
bits: 5,
|
|
32
|
+
tables: [6, 4, 1, 2, 8, 9, 10, 0, 14, 23, 20, 17, 26, 27, 32, 35, 38, 39, 40, 42,
|
|
33
|
+
44, 43, 48, 50, 51, 52, 53]
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const schemas = [
|
|
38
|
+
[["u2", "Generation"], ["str", "Name"], ["guid", "Mvid"], ["guid", "EncId"], ["guid", "EncBaseId"]],
|
|
39
|
+
[["coded", "ResolutionScope"], ["str", "TypeName"], ["str", "TypeNamespace"]],
|
|
40
|
+
[["u4", "Flags"], ["str", "TypeName"], ["str", "TypeNamespace"], ["coded", "TypeDefOrRef", "Extends"], ["table", 4, "FieldList", 3], ["table", 6, "MethodList", 5]],
|
|
41
|
+
[["table", 4, "Field"]],
|
|
42
|
+
[["u2", "Flags"], ["str", "Name"], ["blob", "Signature"]],
|
|
43
|
+
[["table", 6, "Method"]],
|
|
44
|
+
[["u4", "RVA"], ["u2", "ImplFlags"], ["u2", "Flags"], ["str", "Name"], ["blob", "Signature"], ["table", 8, "ParamList", 7]],
|
|
45
|
+
[["table", 8, "Param"]],
|
|
46
|
+
[["u2", "Flags"], ["u2", "Sequence"], ["str", "Name"]],
|
|
47
|
+
[["table", 2, "Class"], ["coded", "TypeDefOrRef", "Interface"]],
|
|
48
|
+
[["coded", "MemberRefParent", "Class"], ["str", "Name"], ["blob", "Signature"]],
|
|
49
|
+
[["u1", "Type"], ["u1", "Padding"], ["coded", "HasConstant", "Parent"], ["blob", "Value"]],
|
|
50
|
+
[["coded", "HasCustomAttribute", "Parent"], ["coded", "CustomAttributeType", "Type"], ["blob", "Value"]],
|
|
51
|
+
[["coded", "HasFieldMarshal", "Parent"], ["blob", "NativeType"]],
|
|
52
|
+
[["u2", "Action"], ["coded", "HasDeclSecurity", "Parent"], ["blob", "PermissionSet"]],
|
|
53
|
+
[["u2", "PackingSize"], ["u4", "ClassSize"], ["table", 2, "Parent"]],
|
|
54
|
+
[["u4", "Offset"], ["table", 4, "Field"]],
|
|
55
|
+
[["blob", "Signature"]],
|
|
56
|
+
[["table", 2, "Parent"], ["table", 20, "EventList", 19]],
|
|
57
|
+
[["table", 20, "Event"]],
|
|
58
|
+
[["u2", "EventFlags"], ["str", "Name"], ["coded", "TypeDefOrRef", "EventType"]],
|
|
59
|
+
[["table", 2, "Parent"], ["table", 23, "PropertyList", 22]],
|
|
60
|
+
[["table", 23, "Property"]],
|
|
61
|
+
[["u2", "Flags"], ["str", "Name"], ["blob", "Type"]],
|
|
62
|
+
[["u2", "Semantics"], ["table", 6, "Method"], ["coded", "HasSemantics", "Association"]],
|
|
63
|
+
[["table", 2, "Class"], ["coded", "MethodDefOrRef", "MethodBody"], ["coded", "MethodDefOrRef", "MethodDeclaration"]],
|
|
64
|
+
[["str", "Name"]],
|
|
65
|
+
[["blob", "Signature"]],
|
|
66
|
+
[["u2", "MappingFlags"], ["coded", "MemberForwarded", "MemberForwarded"], ["str", "ImportName"], ["table", 26, "ImportScope"]],
|
|
67
|
+
[["u4", "RVA"], ["table", 4, "Field"]],
|
|
68
|
+
[["u4", "Token"], ["u4", "FuncCode"]],
|
|
69
|
+
[["u4", "Token"]],
|
|
70
|
+
[["u4", "HashAlgId"], ["u2", "MajorVersion"], ["u2", "MinorVersion"], ["u2", "BuildNumber"], ["u2", "RevisionNumber"], ["u4", "Flags"], ["blob", "PublicKey"], ["str", "Name"], ["str", "Culture"]],
|
|
71
|
+
[["u4", "Processor"]],
|
|
72
|
+
[["u4", "OSPlatformId"], ["u4", "OSMajorVersion"], ["u4", "OSMinorVersion"]],
|
|
73
|
+
[["u2", "MajorVersion"], ["u2", "MinorVersion"], ["u2", "BuildNumber"], ["u2", "RevisionNumber"], ["u4", "Flags"], ["blob", "PublicKeyOrToken"], ["str", "Name"], ["str", "Culture"], ["blob", "HashValue"]],
|
|
74
|
+
[["u4", "Processor"], ["table", 35, "AssemblyRef"]],
|
|
75
|
+
[["u4", "OSPlatformId"], ["u4", "OSMajorVersion"], ["u4", "OSMinorVersion"], ["table", 35, "AssemblyRef"]],
|
|
76
|
+
[["u4", "Flags"], ["str", "Name"], ["blob", "HashValue"]],
|
|
77
|
+
[["u4", "Flags"], ["u4", "TypeDefId"], ["str", "TypeName"], ["str", "TypeNamespace"], ["coded", "Implementation"]],
|
|
78
|
+
[["u4", "Offset"], ["u4", "Flags"], ["str", "Name"], ["coded", "Implementation"]],
|
|
79
|
+
[["table", 2, "NestedClass"], ["table", 2, "EnclosingClass"]],
|
|
80
|
+
[["u2", "Number"], ["u2", "Flags"], ["coded", "TypeOrMethodDef", "Owner"], ["str", "Name"]],
|
|
81
|
+
[["coded", "MethodDefOrRef", "Method"], ["blob", "Instantiation"]],
|
|
82
|
+
[["table", 42, "Owner"], ["coded", "TypeDefOrRef", "Constraint"]],
|
|
83
|
+
null, null, null,
|
|
84
|
+
[["blob", "Name"], ["guid", "HashAlgorithm"], ["blob", "Hash"], ["guid", "Language"]],
|
|
85
|
+
[["table", 48, "Document"], ["blob", "SequencePoints"]],
|
|
86
|
+
[["table", 6, "Method"], ["table", 53, "ImportScope"], ["table", 51, "VariableList", 51],
|
|
87
|
+
["table", 52, "ConstantList", 52], ["u4", "StartOffset"], ["u4", "Length"]],
|
|
88
|
+
[["u2", "Attributes"], ["u2", "Index"], ["str", "Name"]],
|
|
89
|
+
[["str", "Name"], ["blob", "Signature"]],
|
|
90
|
+
[["table", 53, "Parent"], ["blob", "Imports"]],
|
|
91
|
+
[["table", 6, "MoveNextMethod"], ["table", 6, "KickoffMethod"]],
|
|
92
|
+
[["coded", "HasCustomDebugInformation", "Parent"], ["guid", "Kind"], ["blob", "Value"]]
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const tableIdByName = new Map(tableNames.flatMap((name, id) => name === null ? [] : [[name, id]]));
|
|
96
|
+
const pointerTables = new Map([[3, 4], [5, 6], [7, 8], [19, 20], [22, 23]]);
|
|
97
|
+
const listColumns = new Set([
|
|
98
|
+
"FieldList", "MethodList", "ParamList", "EventList", "PropertyList", "VariableList", "ConstantList"
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
function fieldWidth(field, counts, heapSizes, present) {
|
|
102
|
+
const [kind, value, , pointer] = field;
|
|
103
|
+
if (kind === "u1") return 1;
|
|
104
|
+
if (kind === "u2") return 2;
|
|
105
|
+
if (kind === "u4") return 4;
|
|
106
|
+
if (kind === "str") return (heapSizes & 1) !== 0 ? 4 : 2;
|
|
107
|
+
if (kind === "guid") return (heapSizes & 2) !== 0 ? 4 : 2;
|
|
108
|
+
if (kind === "blob") return (heapSizes & 4) !== 0 ? 4 : 2;
|
|
109
|
+
if (kind === "table") {
|
|
110
|
+
const table = pointer !== undefined && present.has(pointer) ? pointer : value;
|
|
111
|
+
return tableIndexWidth(counts[table]);
|
|
112
|
+
}
|
|
113
|
+
const definition = codedIndexes[value];
|
|
114
|
+
if (!definition) throw new FormatError(`Unknown coded-index kind ${value}`, { context: "metadata tables" });
|
|
115
|
+
const maxRows = Math.max(0, ...definition.tables.filter((id) => id !== null).map((id) => counts[id] || 0));
|
|
116
|
+
return maxRows < 2 ** (16 - definition.bits) ? 2 : 4;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function readIndex(reader, width) {
|
|
120
|
+
if (width === 1) return reader.u1();
|
|
121
|
+
return width === 2 ? reader.u2() : reader.u4();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function decodeCodedIndex(value, kind) {
|
|
125
|
+
const definition = codedIndexes[kind];
|
|
126
|
+
if (!definition) throw new FormatError(`Unknown coded-index kind ${kind}`);
|
|
127
|
+
const mask = (1 << definition.bits) - 1;
|
|
128
|
+
const tag = value & mask;
|
|
129
|
+
const table = definition.tables[tag];
|
|
130
|
+
const rid = value >>> definition.bits;
|
|
131
|
+
if (table === null || table === undefined) {
|
|
132
|
+
throw new FormatError(`Invalid ${kind} tag ${tag}`, { context: "metadata tables" });
|
|
133
|
+
}
|
|
134
|
+
if (rid === 0 && value !== 0) {
|
|
135
|
+
throw new FormatError(`Invalid zero row ID in ${kind}`, { context: "metadata tables" });
|
|
136
|
+
}
|
|
137
|
+
return rid === 0 ? null : { table, tableName: tableNames[table], rid };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function encodeCodedIndex(table, rid, kind) {
|
|
141
|
+
const definition = codedIndexes[kind];
|
|
142
|
+
if (!definition) throw new FormatError(`Unknown coded-index kind ${kind}`);
|
|
143
|
+
const maxRid = Math.floor(0xffffffff / (2 ** definition.bits));
|
|
144
|
+
if (!Number.isInteger(rid) || rid < 0 || rid > maxRid) throw new RangeError("Invalid metadata row ID");
|
|
145
|
+
if (rid === 0) return 0;
|
|
146
|
+
const tag = definition.tables.indexOf(table);
|
|
147
|
+
if (tag < 0) throw new FormatError(`Table ${table} is not valid for ${kind}`);
|
|
148
|
+
return rid * (2 ** definition.bits) + tag;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function codedIndexWidth(kind, counts) {
|
|
152
|
+
const definition = codedIndexes[kind];
|
|
153
|
+
if (!definition) throw new FormatError(`Unknown coded-index kind ${kind}`);
|
|
154
|
+
const maxRows = Math.max(0, ...definition.tables.filter((id) => id !== null).map((id) => counts[id] || 0));
|
|
155
|
+
return maxRows < 2 ** (16 - definition.bits) ? 2 : 4;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function tableIndexWidth(rowCount) {
|
|
159
|
+
if (!Number.isInteger(rowCount) || rowCount < 0 || rowCount > 0xffffffff) {
|
|
160
|
+
throw new RangeError("Invalid metadata table row count");
|
|
161
|
+
}
|
|
162
|
+
return rowCount < 0x10000 ? 2 : 4;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function parseTablesStream(stream, externalCounts = null) {
|
|
166
|
+
const reader = new BinaryReader(stream.bytes, {
|
|
167
|
+
baseOffset: stream.offset,
|
|
168
|
+
context: `${stream.name} tables stream`
|
|
169
|
+
});
|
|
170
|
+
const reserved = reader.u4();
|
|
171
|
+
const majorVersion = reader.u1();
|
|
172
|
+
const minorVersion = reader.u1();
|
|
173
|
+
const heapSizes = reader.u1();
|
|
174
|
+
if ((heapSizes & 0xf8) !== 0) {
|
|
175
|
+
throw new FormatError("Metadata heap-size flags contain reserved bits", {
|
|
176
|
+
offset: reader.absoluteOffset - 1,
|
|
177
|
+
context: `${stream.name} tables stream`
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
const reservedByte = reader.u1();
|
|
181
|
+
if (reserved !== 0 || reservedByte !== 1) {
|
|
182
|
+
throw new FormatError("Metadata tables header has invalid reserved fields", {
|
|
183
|
+
offset: reader.absoluteOffset - 8,
|
|
184
|
+
context: `${stream.name} tables stream`
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
const valid = reader.u8();
|
|
188
|
+
const sorted = reader.u8();
|
|
189
|
+
const present = new Set();
|
|
190
|
+
const counts = Array.from({ length: 64 }, (_, id) => externalCounts?.[id] ?? 0);
|
|
191
|
+
for (let id = 0; id < 64; id++) {
|
|
192
|
+
if ((valid & (1n << BigInt(id))) === 0n) continue;
|
|
193
|
+
if (id >= schemas.length || !schemas[id]) {
|
|
194
|
+
throw new UnsupportedFormatError(`Metadata table 0x${id.toString(16).padStart(2, "0")} has no supported schema`, {
|
|
195
|
+
offset: reader.absoluteOffset,
|
|
196
|
+
context: `${stream.name} tables stream`
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
present.add(id);
|
|
200
|
+
counts[id] = reader.u4();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const rows = new Map();
|
|
204
|
+
const tableDataOffset = reader.absoluteOffset;
|
|
205
|
+
for (const id of [...present].sort((a, b) => a - b)) {
|
|
206
|
+
const schema = schemas[id];
|
|
207
|
+
const rowCount = counts[id];
|
|
208
|
+
const rowSize = schema.reduce((size, field) => size + fieldWidth(field, counts, heapSizes, present), 0);
|
|
209
|
+
if (rowSize === 0 || rowCount > Math.floor(reader.remaining / rowSize)) {
|
|
210
|
+
throw new FormatError(`Table ${tableNames[id]} row count exceeds the remaining stream`, {
|
|
211
|
+
offset: reader.absoluteOffset,
|
|
212
|
+
context: `${stream.name} tables stream`
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const tableRows = [];
|
|
216
|
+
const previousListIndexes = new Map();
|
|
217
|
+
for (let rid = 1; rid <= rowCount; rid++) {
|
|
218
|
+
const row = { table: id, tableName: tableNames[id], rid };
|
|
219
|
+
for (const field of schema) {
|
|
220
|
+
const [kind, value, name, pointer] = field;
|
|
221
|
+
const width = fieldWidth(field, counts, heapSizes, present);
|
|
222
|
+
const raw = readIndex(reader, width);
|
|
223
|
+
if (kind === "coded") {
|
|
224
|
+
const target = decodeCodedIndex(raw, value);
|
|
225
|
+
if (target && target.rid > counts[target.table]) {
|
|
226
|
+
throw new FormatError(`${value} references missing ${target.tableName} row ${target.rid}`, {
|
|
227
|
+
offset: reader.absoluteOffset - width,
|
|
228
|
+
context: `${stream.name} tables stream`
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
row[name ?? value] = { raw, target };
|
|
232
|
+
} else if (kind === "table") {
|
|
233
|
+
const targetTable = pointer !== undefined && present.has(pointer) ? pointer : value;
|
|
234
|
+
const isList = listColumns.has(name);
|
|
235
|
+
const maxRid = counts[targetTable] + (isList ? 1 : 0);
|
|
236
|
+
if (isList && raw === 0) {
|
|
237
|
+
throw new FormatError(`${name} must reference a one-based list position`, {
|
|
238
|
+
offset: reader.absoluteOffset - width,
|
|
239
|
+
context: `${stream.name} tables stream`
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (raw > maxRid) {
|
|
243
|
+
throw new FormatError(`${name} references missing ${tableNames[targetTable]} row ${raw}`, {
|
|
244
|
+
offset: reader.absoluteOffset - width,
|
|
245
|
+
context: `${stream.name} tables stream`
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const previous = previousListIndexes.get(name);
|
|
249
|
+
if (isList && previous !== undefined && raw < previous) {
|
|
250
|
+
throw new FormatError(`${name} indexes are not ordered`, {
|
|
251
|
+
offset: reader.absoluteOffset - width,
|
|
252
|
+
context: `${stream.name} tables stream`
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
if (isList) previousListIndexes.set(name, raw);
|
|
256
|
+
row[name] = { raw, table: targetTable, tableName: tableNames[targetTable], rid: raw };
|
|
257
|
+
} else {
|
|
258
|
+
row[name ?? value] = raw;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
tableRows.push(row);
|
|
262
|
+
}
|
|
263
|
+
rows.set(id, tableRows);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
for (const row of rows.get(42) ?? []) {
|
|
267
|
+
if (!row.Owner.target) {
|
|
268
|
+
throw new FormatError(`GenericParam row ${row.rid} has a nil Owner`, {
|
|
269
|
+
context: `${stream.name} tables stream`
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
if ((row.Flags & ~0x1f) !== 0 || (row.Flags & 0x03) === 0x03 ||
|
|
273
|
+
(row.Flags & 0x18) === 0x18) {
|
|
274
|
+
throw new FormatError(`GenericParam row ${row.rid} contains invalid flags`, {
|
|
275
|
+
context: `${stream.name} tables stream`
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
if ((row.Flags & 0x03) !== 0 && row.Owner.target.table === 6) {
|
|
279
|
+
throw new FormatError(`GenericParam row ${row.rid} gives variance to a method parameter`, {
|
|
280
|
+
context: `${stream.name} tables stream`
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
for (const row of rows.get(44) ?? []) {
|
|
285
|
+
if (row.Owner.rid === 0 || !row.Constraint.target) {
|
|
286
|
+
throw new FormatError(`GenericParamConstraint row ${row.rid} has a nil Owner or Constraint`, {
|
|
287
|
+
context: `${stream.name} tables stream`
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const sortedKeys = new Map([
|
|
292
|
+
[1, (row) => [row.ResolutionScope.raw]],
|
|
293
|
+
[9, (row) => [row.Class.rid]],
|
|
294
|
+
[10, (row) => [row.Class.raw]],
|
|
295
|
+
[11, (row) => [row.Parent.raw]],
|
|
296
|
+
[12, (row) => [row.Parent.raw]],
|
|
297
|
+
[13, (row) => [row.Parent.raw]],
|
|
298
|
+
[14, (row) => [row.Parent.raw]],
|
|
299
|
+
[15, (row) => [row.Parent.rid]],
|
|
300
|
+
[16, (row) => [row.Field.rid]],
|
|
301
|
+
[18, (row) => [row.Parent.rid]],
|
|
302
|
+
[21, (row) => [row.Parent.rid]],
|
|
303
|
+
[24, (row) => [row.Association.raw]],
|
|
304
|
+
[25, (row) => [row.Class.rid]],
|
|
305
|
+
[28, (row) => [row.MemberForwarded.raw]],
|
|
306
|
+
[29, (row) => [row.Field.rid]],
|
|
307
|
+
[41, (row) => [row.NestedClass.rid]],
|
|
308
|
+
[42, (row) => [row.Owner.raw, row.Number]],
|
|
309
|
+
[43, (row) => [row.Method.raw]],
|
|
310
|
+
[44, (row) => [row.Owner.rid]]
|
|
311
|
+
]);
|
|
312
|
+
const uniqueKeys = new Map([
|
|
313
|
+
[9, (row) => [row.Class.rid, row.Interface.raw]],
|
|
314
|
+
[11, (row) => [row.Parent.raw]],
|
|
315
|
+
[13, (row) => [row.Parent.raw]],
|
|
316
|
+
[14, (row) => [row.Parent.raw, row.Action]],
|
|
317
|
+
[15, (row) => [row.Parent.rid]],
|
|
318
|
+
[16, (row) => [row.Field.rid]],
|
|
319
|
+
[18, (row) => [row.Parent.rid]],
|
|
320
|
+
[21, (row) => [row.Parent.rid]],
|
|
321
|
+
[28, (row) => [row.MemberForwarded.raw]],
|
|
322
|
+
[29, (row) => [row.Field.rid]],
|
|
323
|
+
[41, (row) => [row.NestedClass.rid]],
|
|
324
|
+
[42, (row) => [row.Owner.raw, row.Number]],
|
|
325
|
+
[44, (row) => [row.Owner.rid, row.Constraint.raw]]
|
|
326
|
+
]);
|
|
327
|
+
for (const [id, keyOf] of uniqueKeys) {
|
|
328
|
+
const seen = new Set();
|
|
329
|
+
for (const row of rows.get(id) ?? []) {
|
|
330
|
+
const key = JSON.stringify(keyOf(row));
|
|
331
|
+
if (seen.has(key)) {
|
|
332
|
+
throw new FormatError(`${tableNames[id]} rows contain a duplicate key`, {
|
|
333
|
+
context: `${stream.name} tables stream`
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
seen.add(key);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
for (const [id, keyOf] of sortedKeys) {
|
|
340
|
+
if ((sorted & (1n << BigInt(id))) === 0n) continue;
|
|
341
|
+
let previous = null;
|
|
342
|
+
for (const row of rows.get(id) ?? []) {
|
|
343
|
+
const current = keyOf(row);
|
|
344
|
+
let outOfOrder = false;
|
|
345
|
+
if (previous) {
|
|
346
|
+
for (let index = 0; index < current.length; index++) {
|
|
347
|
+
if (current[index] === previous[index]) continue;
|
|
348
|
+
outOfOrder = current[index] < previous[index];
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (outOfOrder) {
|
|
353
|
+
throw new FormatError(`${tableNames[id]} rows are not ordered by their sorted key`, {
|
|
354
|
+
context: `${stream.name} tables stream`
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
previous = current;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
majorVersion,
|
|
363
|
+
minorVersion,
|
|
364
|
+
reserved,
|
|
365
|
+
reservedByte,
|
|
366
|
+
heapSizes,
|
|
367
|
+
valid,
|
|
368
|
+
sorted,
|
|
369
|
+
counts,
|
|
370
|
+
rows,
|
|
371
|
+
tableDataOffset,
|
|
372
|
+
row(idOrName, rid) {
|
|
373
|
+
const id = typeof idOrName === "string" ? tableIdByName.get(idOrName) : idOrName;
|
|
374
|
+
if (!Number.isInteger(id) || !Number.isInteger(rid) || rid < 1) return null;
|
|
375
|
+
return rows.get(id)?.[rid - 1] ?? null;
|
|
376
|
+
},
|
|
377
|
+
get(idOrName) {
|
|
378
|
+
const id = typeof idOrName === "string" ? tableIdByName.get(idOrName) : idOrName;
|
|
379
|
+
return Number.isInteger(id) ? rows.get(id) ?? [] : [];
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export const metadataTableNames = tableNames;
|
|
385
|
+
export const metadataTableSchemas = schemas;
|
|
386
|
+
|
|
387
|
+
export function metadataTableId(nameOrId) {
|
|
388
|
+
if (Number.isInteger(nameOrId)) return nameOrId;
|
|
389
|
+
return tableIdByName.get(nameOrId) ?? -1;
|
|
390
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { FormatError } from "../binary/errors.js";
|
|
2
|
+
import {
|
|
3
|
+
codedIndexWidth,
|
|
4
|
+
encodeCodedIndex,
|
|
5
|
+
metadataTableNames,
|
|
6
|
+
metadataTableSchemas,
|
|
7
|
+
tableIndexWidth
|
|
8
|
+
} from "./tables.js";
|
|
9
|
+
|
|
10
|
+
function assertUnsigned(value, max, label) {
|
|
11
|
+
if (!Number.isInteger(value) || value < 0 || value > max) {
|
|
12
|
+
throw new FormatError(`${label} value ${value} does not fit its encoded width`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function appendU1(output, value) {
|
|
17
|
+
assertUnsigned(value, 0xff, "u1");
|
|
18
|
+
output.push(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function appendU2(output, value) {
|
|
22
|
+
assertUnsigned(value, 0xffff, "u2");
|
|
23
|
+
output.push(value & 0xff, value >>> 8);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function appendU4(output, value) {
|
|
27
|
+
assertUnsigned(value, 0xffffffff, "u4");
|
|
28
|
+
output.push(value & 0xff, (value >>> 8) & 0xff, (value >>> 16) & 0xff, (value >>> 24) & 0xff);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function appendU8(output, value) {
|
|
32
|
+
const number = BigInt(value);
|
|
33
|
+
if (number < 0n || number > 0xffffffffffffffffn) throw new RangeError("u8 value is out of range");
|
|
34
|
+
for (let index = 0; index < 8; index++) output.push(Number((number >> BigInt(index * 8)) & 0xffn));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function columnWidth(field, counts, heapSizes, present) {
|
|
38
|
+
const [kind, value, , pointer] = field;
|
|
39
|
+
if (kind === "u1") return 1;
|
|
40
|
+
if (kind === "u2") return 2;
|
|
41
|
+
if (kind === "u4") return 4;
|
|
42
|
+
if (kind === "str") return (heapSizes & 1) !== 0 ? 4 : 2;
|
|
43
|
+
if (kind === "guid") return (heapSizes & 2) !== 0 ? 4 : 2;
|
|
44
|
+
if (kind === "blob") return (heapSizes & 4) !== 0 ? 4 : 2;
|
|
45
|
+
if (kind === "table") {
|
|
46
|
+
const table = pointer !== undefined && present.has(pointer) ? pointer : value;
|
|
47
|
+
return tableIndexWidth(counts[table]);
|
|
48
|
+
}
|
|
49
|
+
return codedIndexWidth(value, counts);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function columnValue(row, field) {
|
|
53
|
+
const [kind, value, name] = field;
|
|
54
|
+
const column = row[name ?? value];
|
|
55
|
+
if (kind === "table") return column?.rid ?? column ?? 0;
|
|
56
|
+
if (kind === "coded") {
|
|
57
|
+
const target = column?.target;
|
|
58
|
+
return target ? encodeCodedIndex(target.table, target.rid, value) : 0;
|
|
59
|
+
}
|
|
60
|
+
return column ?? 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writeRow(output, row, schema, counts, heapSizes, present) {
|
|
64
|
+
for (const field of schema) {
|
|
65
|
+
const [kind, value, name] = field;
|
|
66
|
+
const encoded = columnValue(row, field);
|
|
67
|
+
const width = columnWidth(field, counts, heapSizes, present);
|
|
68
|
+
if (kind === "u1") appendU1(output, encoded);
|
|
69
|
+
else if (kind === "u2") appendU2(output, encoded);
|
|
70
|
+
else if (width === 2) appendU2(output, encoded);
|
|
71
|
+
else appendU4(output, encoded);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function serializeTablesStream(tables, heapStreams) {
|
|
76
|
+
const rows = new Map([...tables.rows].map(([id, tableRows]) => [id, [...tableRows]]));
|
|
77
|
+
const valid = tables.valid;
|
|
78
|
+
const present = new Set();
|
|
79
|
+
const counts = tables.counts ? [...tables.counts] : Array(64).fill(0);
|
|
80
|
+
for (let id = 0; id < 64; id++) {
|
|
81
|
+
if ((valid & (1n << BigInt(id))) !== 0n || rows.has(id)) {
|
|
82
|
+
if (!metadataTableSchemas[id]) throw new FormatError(`Cannot serialize unsupported metadata table ${id}`);
|
|
83
|
+
present.add(id);
|
|
84
|
+
counts[id] = rows.get(id)?.length ?? tables.counts[id] ?? 0;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
let heapSizes = 0;
|
|
88
|
+
if ((heapStreams.get("#Strings")?.length ?? 0) >= 0x10000) heapSizes |= 1;
|
|
89
|
+
if (Math.floor((heapStreams.get("#GUID")?.length ?? 0) / 16) >= 0x10000) heapSizes |= 2;
|
|
90
|
+
if ((heapStreams.get("#Blob")?.length ?? 0) >= 0x10000) heapSizes |= 4;
|
|
91
|
+
|
|
92
|
+
const output = [];
|
|
93
|
+
appendU4(output, tables.reserved ?? 0);
|
|
94
|
+
appendU1(output, tables.majorVersion);
|
|
95
|
+
appendU1(output, tables.minorVersion);
|
|
96
|
+
appendU1(output, heapSizes);
|
|
97
|
+
appendU1(output, tables.reservedByte ?? 1);
|
|
98
|
+
let validBits = 0n;
|
|
99
|
+
for (const id of present) validBits |= 1n << BigInt(id);
|
|
100
|
+
appendU8(output, validBits);
|
|
101
|
+
appendU8(output, tables.sorted);
|
|
102
|
+
for (const id of [...present].sort((a, b) => a - b)) appendU4(output, counts[id]);
|
|
103
|
+
for (const id of [...present].sort((a, b) => a - b)) {
|
|
104
|
+
const schema = metadataTableSchemas[id];
|
|
105
|
+
for (const row of rows.get(id) ?? []) writeRow(output, row, schema, counts, heapSizes, present);
|
|
106
|
+
}
|
|
107
|
+
return Uint8Array.from(output);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function appendCompressedUInt(value) {
|
|
111
|
+
if (!Number.isInteger(value) || value < 0 || value >= 0x20000000) {
|
|
112
|
+
throw new RangeError("Compressed unsigned integer is out of range");
|
|
113
|
+
}
|
|
114
|
+
if (value < 0x80) return Uint8Array.of(value);
|
|
115
|
+
if (value < 0x4000) return Uint8Array.of(0x80 | (value >>> 8), value & 0xff);
|
|
116
|
+
return Uint8Array.of(
|
|
117
|
+
0xc0 | (value >>> 24),
|
|
118
|
+
(value >>> 16) & 0xff,
|
|
119
|
+
(value >>> 8) & 0xff,
|
|
120
|
+
value & 0xff
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function appendCompressedInt(value) {
|
|
125
|
+
if (!Number.isInteger(value) || value < -0x10000000 || value > 0x0fffffff) {
|
|
126
|
+
throw new RangeError("Compressed signed integer is out of range");
|
|
127
|
+
}
|
|
128
|
+
const width = value >= -64 && value <= 63 ? 1 : value >= -0x2000 && value <= 0x1fff ? 2 : 4;
|
|
129
|
+
const modulus = 2 ** (width === 1 ? 7 : width === 2 ? 14 : 29);
|
|
130
|
+
const encoded = (((value * 2) % modulus) + modulus) % modulus + (value < 0 ? 1 : 0);
|
|
131
|
+
if (width === 1) return Uint8Array.of(encoded);
|
|
132
|
+
if (width === 2) return Uint8Array.of(0x80 | (encoded >>> 8), encoded & 0xff);
|
|
133
|
+
return Uint8Array.of(
|
|
134
|
+
0xc0 | Math.floor(encoded / 0x1000000),
|
|
135
|
+
Math.floor(encoded / 0x10000) & 0xff,
|
|
136
|
+
Math.floor(encoded / 0x100) & 0xff,
|
|
137
|
+
encoded & 0xff
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function appendStringHeap(heap, value) {
|
|
142
|
+
if (typeof value !== "string" || value.includes("\0")) throw new TypeError("Metadata strings must be null-free strings");
|
|
143
|
+
const source = heap.length === 0 ? Uint8Array.of(0) : heap;
|
|
144
|
+
const encoded = new TextEncoder().encode(value);
|
|
145
|
+
const output = new Uint8Array(source.length + encoded.length + 1);
|
|
146
|
+
output.set(source);
|
|
147
|
+
output.set(encoded, source.length);
|
|
148
|
+
const index = source.length;
|
|
149
|
+
return { heap: output, index };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function appendBlobHeap(heap, value) {
|
|
153
|
+
const payload = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
154
|
+
const source = heap.length === 0 ? Uint8Array.of(0) : heap;
|
|
155
|
+
const prefix = appendCompressedUInt(payload.length);
|
|
156
|
+
const output = new Uint8Array(source.length + prefix.length + payload.length);
|
|
157
|
+
output.set(source);
|
|
158
|
+
output.set(prefix, source.length);
|
|
159
|
+
output.set(payload, source.length + prefix.length);
|
|
160
|
+
return { heap: output, index: source.length };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function appendUserStringHeap(heap, value) {
|
|
164
|
+
if (typeof value !== "string") throw new TypeError("User strings must be strings");
|
|
165
|
+
const source = heap.length === 0 ? Uint8Array.of(0) : heap;
|
|
166
|
+
if (source.length > 0x00ffffff) throw new RangeError("#US offset exceeds the user-string token range");
|
|
167
|
+
const payloadLength = value.length * 2 + 1;
|
|
168
|
+
const prefix = appendCompressedUInt(payloadLength);
|
|
169
|
+
const output = new Uint8Array(source.length + prefix.length + payloadLength);
|
|
170
|
+
output.set(source);
|
|
171
|
+
output.set(prefix, source.length);
|
|
172
|
+
let hasSpecialCharacter = false;
|
|
173
|
+
let offset = source.length + prefix.length;
|
|
174
|
+
for (let index = 0; index < value.length; index++) {
|
|
175
|
+
const codeUnit = value.charCodeAt(index);
|
|
176
|
+
output[offset++] = codeUnit & 0xff;
|
|
177
|
+
output[offset++] = codeUnit >>> 8;
|
|
178
|
+
if ((codeUnit >= 0x0001 && codeUnit <= 0x0008) ||
|
|
179
|
+
(codeUnit >= 0x000e && codeUnit <= 0x001f) ||
|
|
180
|
+
codeUnit === 0x0027 || codeUnit === 0x002d ||
|
|
181
|
+
codeUnit === 0x007f || codeUnit > 0x007e) {
|
|
182
|
+
hasSpecialCharacter = true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
output[offset] = hasSpecialCharacter ? 1 : 0;
|
|
186
|
+
return { heap: output, index: source.length };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function tableName(id) {
|
|
190
|
+
return metadataTableNames[id] ?? `Table${id}`;
|
|
191
|
+
}
|