@adhisang/minecraft-modding-mcp 1.0.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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +765 -0
- package/dist/access-widener-parser.d.ts +24 -0
- package/dist/access-widener-parser.js +77 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +4 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +178 -0
- package/dist/decompiler/vineflower.d.ts +15 -0
- package/dist/decompiler/vineflower.js +185 -0
- package/dist/errors.d.ts +50 -0
- package/dist/errors.js +49 -0
- package/dist/hash.d.ts +1 -0
- package/dist/hash.js +12 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1447 -0
- package/dist/java-process.d.ts +16 -0
- package/dist/java-process.js +120 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +21 -0
- package/dist/mapping-pipeline-service.d.ts +18 -0
- package/dist/mapping-pipeline-service.js +60 -0
- package/dist/mapping-service.d.ts +161 -0
- package/dist/mapping-service.js +1706 -0
- package/dist/maven-resolver.d.ts +22 -0
- package/dist/maven-resolver.js +122 -0
- package/dist/minecraft-explorer-service.d.ts +43 -0
- package/dist/minecraft-explorer-service.js +562 -0
- package/dist/mixin-parser.d.ts +34 -0
- package/dist/mixin-parser.js +194 -0
- package/dist/mixin-validator.d.ts +59 -0
- package/dist/mixin-validator.js +274 -0
- package/dist/mod-analyzer.d.ts +23 -0
- package/dist/mod-analyzer.js +346 -0
- package/dist/mod-decompile-service.d.ts +39 -0
- package/dist/mod-decompile-service.js +136 -0
- package/dist/mod-remap-service.d.ts +17 -0
- package/dist/mod-remap-service.js +186 -0
- package/dist/mod-search-service.d.ts +28 -0
- package/dist/mod-search-service.js +174 -0
- package/dist/mojang-tiny-mapping-service.d.ts +13 -0
- package/dist/mojang-tiny-mapping-service.js +351 -0
- package/dist/nbt/java-nbt-codec.d.ts +3 -0
- package/dist/nbt/java-nbt-codec.js +385 -0
- package/dist/nbt/json-patch.d.ts +3 -0
- package/dist/nbt/json-patch.js +352 -0
- package/dist/nbt/pipeline.d.ts +39 -0
- package/dist/nbt/pipeline.js +173 -0
- package/dist/nbt/typed-json.d.ts +10 -0
- package/dist/nbt/typed-json.js +205 -0
- package/dist/nbt/types.d.ts +66 -0
- package/dist/nbt/types.js +2 -0
- package/dist/observability.d.ts +88 -0
- package/dist/observability.js +165 -0
- package/dist/path-converter.d.ts +12 -0
- package/dist/path-converter.js +161 -0
- package/dist/path-resolver.d.ts +19 -0
- package/dist/path-resolver.js +78 -0
- package/dist/registry-service.d.ts +29 -0
- package/dist/registry-service.js +214 -0
- package/dist/repo-downloader.d.ts +15 -0
- package/dist/repo-downloader.js +111 -0
- package/dist/resources.d.ts +3 -0
- package/dist/resources.js +154 -0
- package/dist/search-hit-accumulator.d.ts +38 -0
- package/dist/search-hit-accumulator.js +153 -0
- package/dist/source-jar-reader.d.ts +13 -0
- package/dist/source-jar-reader.js +216 -0
- package/dist/source-resolver.d.ts +14 -0
- package/dist/source-resolver.js +274 -0
- package/dist/source-service.d.ts +404 -0
- package/dist/source-service.js +2881 -0
- package/dist/storage/artifacts-repo.d.ts +45 -0
- package/dist/storage/artifacts-repo.js +209 -0
- package/dist/storage/db.d.ts +14 -0
- package/dist/storage/db.js +132 -0
- package/dist/storage/files-repo.d.ts +78 -0
- package/dist/storage/files-repo.js +437 -0
- package/dist/storage/index-meta-repo.d.ts +35 -0
- package/dist/storage/index-meta-repo.js +97 -0
- package/dist/storage/migrations.d.ts +11 -0
- package/dist/storage/migrations.js +71 -0
- package/dist/storage/schema.d.ts +1 -0
- package/dist/storage/schema.js +160 -0
- package/dist/storage/sqlite.d.ts +20 -0
- package/dist/storage/sqlite.js +111 -0
- package/dist/storage/symbols-repo.d.ts +63 -0
- package/dist/storage/symbols-repo.js +401 -0
- package/dist/symbols/symbol-extractor.d.ts +7 -0
- package/dist/symbols/symbol-extractor.js +64 -0
- package/dist/tiny-remapper-resolver.d.ts +1 -0
- package/dist/tiny-remapper-resolver.js +62 -0
- package/dist/tiny-remapper-service.d.ts +16 -0
- package/dist/tiny-remapper-service.js +73 -0
- package/dist/types.d.ts +120 -0
- package/dist/types.js +2 -0
- package/dist/version-diff-service.d.ts +41 -0
- package/dist/version-diff-service.js +222 -0
- package/dist/version-service.d.ts +70 -0
- package/dist/version-service.js +411 -0
- package/dist/vineflower-resolver.d.ts +1 -0
- package/dist/vineflower-resolver.js +62 -0
- package/dist/workspace-mapping-service.d.ts +18 -0
- package/dist/workspace-mapping-service.js +89 -0
- package/package.json +61 -0
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
import { createError, ERROR_CODES } from "./errors.js";
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
import { artifactSignatureFromFile, normalizeJarPath } from "./path-resolver.js";
|
|
4
|
+
import { readJarEntryAsBuffer } from "./source-jar-reader.js";
|
|
5
|
+
const CLASSFILE_MAGIC = 0xcafebabe;
|
|
6
|
+
const MAX_INHERITANCE_DEPTH = 64;
|
|
7
|
+
const ACC_PUBLIC = 0x0001;
|
|
8
|
+
const ACC_PRIVATE = 0x0002;
|
|
9
|
+
const ACC_PROTECTED = 0x0004;
|
|
10
|
+
const ACC_STATIC = 0x0008;
|
|
11
|
+
const ACC_FINAL = 0x0010;
|
|
12
|
+
const ACC_SYNCHRONIZED = 0x0020;
|
|
13
|
+
const ACC_VOLATILE = 0x0040;
|
|
14
|
+
const ACC_BRIDGE = 0x0040;
|
|
15
|
+
const ACC_TRANSIENT = 0x0080;
|
|
16
|
+
const ACC_VARARGS = 0x0080;
|
|
17
|
+
const ACC_NATIVE = 0x0100;
|
|
18
|
+
const ACC_ABSTRACT = 0x0400;
|
|
19
|
+
const ACC_STRICT = 0x0800;
|
|
20
|
+
const ACC_SYNTHETIC = 0x1000;
|
|
21
|
+
function lower(value) {
|
|
22
|
+
return value.toLocaleLowerCase();
|
|
23
|
+
}
|
|
24
|
+
function modifierPrefix(flags, category) {
|
|
25
|
+
const parts = [];
|
|
26
|
+
if ((flags & ACC_PUBLIC) !== 0) {
|
|
27
|
+
parts.push("public");
|
|
28
|
+
}
|
|
29
|
+
else if ((flags & ACC_PROTECTED) !== 0) {
|
|
30
|
+
parts.push("protected");
|
|
31
|
+
}
|
|
32
|
+
else if ((flags & ACC_PRIVATE) !== 0) {
|
|
33
|
+
parts.push("private");
|
|
34
|
+
}
|
|
35
|
+
if ((flags & ACC_STATIC) !== 0) {
|
|
36
|
+
parts.push("static");
|
|
37
|
+
}
|
|
38
|
+
if ((flags & ACC_FINAL) !== 0) {
|
|
39
|
+
parts.push("final");
|
|
40
|
+
}
|
|
41
|
+
if (category === "method") {
|
|
42
|
+
if ((flags & ACC_ABSTRACT) !== 0) {
|
|
43
|
+
parts.push("abstract");
|
|
44
|
+
}
|
|
45
|
+
if ((flags & ACC_SYNCHRONIZED) !== 0) {
|
|
46
|
+
parts.push("synchronized");
|
|
47
|
+
}
|
|
48
|
+
if ((flags & ACC_NATIVE) !== 0) {
|
|
49
|
+
parts.push("native");
|
|
50
|
+
}
|
|
51
|
+
if ((flags & ACC_STRICT) !== 0) {
|
|
52
|
+
parts.push("strictfp");
|
|
53
|
+
}
|
|
54
|
+
if ((flags & ACC_BRIDGE) !== 0) {
|
|
55
|
+
parts.push("bridge");
|
|
56
|
+
}
|
|
57
|
+
if ((flags & ACC_VARARGS) !== 0) {
|
|
58
|
+
parts.push("varargs");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
if ((flags & ACC_VOLATILE) !== 0) {
|
|
63
|
+
parts.push("volatile");
|
|
64
|
+
}
|
|
65
|
+
if ((flags & ACC_TRANSIENT) !== 0) {
|
|
66
|
+
parts.push("transient");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return parts.join(" ");
|
|
70
|
+
}
|
|
71
|
+
function parseFieldType(descriptor, position = 0) {
|
|
72
|
+
if (position >= descriptor.length) {
|
|
73
|
+
throw createError({
|
|
74
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
75
|
+
message: "Unexpected end of descriptor.",
|
|
76
|
+
details: { descriptor, position }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const token = descriptor[position];
|
|
80
|
+
switch (token) {
|
|
81
|
+
case "B":
|
|
82
|
+
return { type: "byte", next: position + 1 };
|
|
83
|
+
case "C":
|
|
84
|
+
return { type: "char", next: position + 1 };
|
|
85
|
+
case "D":
|
|
86
|
+
return { type: "double", next: position + 1 };
|
|
87
|
+
case "F":
|
|
88
|
+
return { type: "float", next: position + 1 };
|
|
89
|
+
case "I":
|
|
90
|
+
return { type: "int", next: position + 1 };
|
|
91
|
+
case "J":
|
|
92
|
+
return { type: "long", next: position + 1 };
|
|
93
|
+
case "S":
|
|
94
|
+
return { type: "short", next: position + 1 };
|
|
95
|
+
case "Z":
|
|
96
|
+
return { type: "boolean", next: position + 1 };
|
|
97
|
+
case "V":
|
|
98
|
+
return { type: "void", next: position + 1 };
|
|
99
|
+
case "L": {
|
|
100
|
+
const end = descriptor.indexOf(";", position);
|
|
101
|
+
if (end < 0) {
|
|
102
|
+
throw createError({
|
|
103
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
104
|
+
message: `Invalid descriptor: ${descriptor}`,
|
|
105
|
+
details: { descriptor, position }
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const type = descriptor.slice(position + 1, end).replace(/\//g, ".");
|
|
109
|
+
return { type, next: end + 1 };
|
|
110
|
+
}
|
|
111
|
+
case "[": {
|
|
112
|
+
const inner = parseFieldType(descriptor, position + 1);
|
|
113
|
+
return { type: `${inner.type}[]`, next: inner.next };
|
|
114
|
+
}
|
|
115
|
+
default:
|
|
116
|
+
throw createError({
|
|
117
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
118
|
+
message: `Unsupported descriptor token "${token}" in "${descriptor}".`,
|
|
119
|
+
details: { descriptor, token, position }
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function parseMethodDescriptor(descriptor) {
|
|
124
|
+
if (!descriptor.startsWith("(")) {
|
|
125
|
+
throw createError({
|
|
126
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
127
|
+
message: `Invalid method descriptor "${descriptor}".`,
|
|
128
|
+
details: { descriptor }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const args = [];
|
|
132
|
+
let cursor = 1;
|
|
133
|
+
while (cursor < descriptor.length && descriptor[cursor] !== ")") {
|
|
134
|
+
const parsed = parseFieldType(descriptor, cursor);
|
|
135
|
+
args.push(parsed.type);
|
|
136
|
+
cursor = parsed.next;
|
|
137
|
+
}
|
|
138
|
+
if (descriptor[cursor] !== ")") {
|
|
139
|
+
throw createError({
|
|
140
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
141
|
+
message: `Invalid method descriptor "${descriptor}".`,
|
|
142
|
+
details: { descriptor, cursor }
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const returnType = parseFieldType(descriptor, cursor + 1).type;
|
|
146
|
+
return { args, returnType };
|
|
147
|
+
}
|
|
148
|
+
function hasPublicVisibility(flags) {
|
|
149
|
+
return (flags & ACC_PUBLIC) !== 0 || (flags & ACC_PROTECTED) !== 0;
|
|
150
|
+
}
|
|
151
|
+
function toInternalName(fqn) {
|
|
152
|
+
return fqn.trim().replace(/\./g, "/");
|
|
153
|
+
}
|
|
154
|
+
function extractVersionFromPath(inputPath) {
|
|
155
|
+
return inputPath.match(/(\d+\.\d+(?:\.\d+)?)/)?.[1];
|
|
156
|
+
}
|
|
157
|
+
class ByteReader {
|
|
158
|
+
offset = 0;
|
|
159
|
+
buffer;
|
|
160
|
+
constructor(buffer) {
|
|
161
|
+
this.buffer = buffer;
|
|
162
|
+
}
|
|
163
|
+
readU1() {
|
|
164
|
+
const value = this.buffer.readUInt8(this.offset);
|
|
165
|
+
this.offset += 1;
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
readU2() {
|
|
169
|
+
const value = this.buffer.readUInt16BE(this.offset);
|
|
170
|
+
this.offset += 2;
|
|
171
|
+
return value;
|
|
172
|
+
}
|
|
173
|
+
readU4() {
|
|
174
|
+
const value = this.buffer.readUInt32BE(this.offset);
|
|
175
|
+
this.offset += 4;
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
readBytes(length) {
|
|
179
|
+
const slice = this.buffer.subarray(this.offset, this.offset + length);
|
|
180
|
+
this.offset += length;
|
|
181
|
+
return slice;
|
|
182
|
+
}
|
|
183
|
+
skip(length) {
|
|
184
|
+
this.offset += length;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function readUtf8(cp, index) {
|
|
188
|
+
const entry = cp[index];
|
|
189
|
+
if (!entry || entry.tag !== 1) {
|
|
190
|
+
throw createError({
|
|
191
|
+
code: ERROR_CODES.CLASS_NOT_FOUND,
|
|
192
|
+
message: `Invalid UTF8 constant pool index ${index}. Class file may be corrupted.`,
|
|
193
|
+
details: { index }
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
return entry.value;
|
|
197
|
+
}
|
|
198
|
+
function readClassName(cp, index) {
|
|
199
|
+
const entry = cp[index];
|
|
200
|
+
if (!entry || entry.tag !== 7) {
|
|
201
|
+
throw createError({
|
|
202
|
+
code: ERROR_CODES.CLASS_NOT_FOUND,
|
|
203
|
+
message: `Invalid class constant pool index ${index}. Class file may be corrupted.`,
|
|
204
|
+
details: { index }
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return readUtf8(cp, entry.index);
|
|
208
|
+
}
|
|
209
|
+
function readOptionalClassName(cp, index) {
|
|
210
|
+
if (index === 0) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
return readClassName(cp, index);
|
|
214
|
+
}
|
|
215
|
+
function readAttributes(reader, cp, count) {
|
|
216
|
+
const names = [];
|
|
217
|
+
for (let index = 0; index < count; index += 1) {
|
|
218
|
+
const nameIndex = reader.readU2();
|
|
219
|
+
const length = reader.readU4();
|
|
220
|
+
const attributeName = readUtf8(cp, nameIndex);
|
|
221
|
+
names.push(attributeName);
|
|
222
|
+
reader.skip(length);
|
|
223
|
+
}
|
|
224
|
+
return names;
|
|
225
|
+
}
|
|
226
|
+
function parseClassFile(buffer) {
|
|
227
|
+
const reader = new ByteReader(buffer);
|
|
228
|
+
if (reader.readU4() !== CLASSFILE_MAGIC) {
|
|
229
|
+
throw createError({
|
|
230
|
+
code: ERROR_CODES.INTERNAL,
|
|
231
|
+
message: "Invalid class file magic. File is not a valid Java class."
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
reader.readU2();
|
|
235
|
+
reader.readU2();
|
|
236
|
+
const cpCount = reader.readU2();
|
|
237
|
+
const cp = new Array(cpCount).fill(undefined);
|
|
238
|
+
for (let index = 1; index < cpCount; index += 1) {
|
|
239
|
+
const tag = reader.readU1();
|
|
240
|
+
switch (tag) {
|
|
241
|
+
case 1: {
|
|
242
|
+
const length = reader.readU2();
|
|
243
|
+
cp[index] = { tag: 1, value: reader.readBytes(length).toString("utf8") };
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
case 3:
|
|
247
|
+
case 4:
|
|
248
|
+
reader.skip(4);
|
|
249
|
+
cp[index] = { tag };
|
|
250
|
+
break;
|
|
251
|
+
case 5:
|
|
252
|
+
case 6:
|
|
253
|
+
reader.skip(8);
|
|
254
|
+
cp[index] = { tag };
|
|
255
|
+
index += 1;
|
|
256
|
+
break;
|
|
257
|
+
case 7:
|
|
258
|
+
case 8:
|
|
259
|
+
case 16:
|
|
260
|
+
case 19:
|
|
261
|
+
case 20:
|
|
262
|
+
cp[index] = { tag, index: reader.readU2() };
|
|
263
|
+
break;
|
|
264
|
+
case 9:
|
|
265
|
+
case 10:
|
|
266
|
+
case 11:
|
|
267
|
+
case 12:
|
|
268
|
+
case 17:
|
|
269
|
+
case 18:
|
|
270
|
+
cp[index] = { tag, index1: reader.readU2(), index2: reader.readU2() };
|
|
271
|
+
break;
|
|
272
|
+
case 15:
|
|
273
|
+
cp[index] = { tag, refKind: reader.readU1(), refIndex: reader.readU2() };
|
|
274
|
+
break;
|
|
275
|
+
default:
|
|
276
|
+
throw createError({
|
|
277
|
+
code: ERROR_CODES.INTERNAL,
|
|
278
|
+
message: `Unsupported constant pool tag ${tag}. Class file may be corrupted.`,
|
|
279
|
+
details: { tag, index }
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
reader.readU2();
|
|
284
|
+
const thisClassIndex = reader.readU2();
|
|
285
|
+
const superClassIndex = reader.readU2();
|
|
286
|
+
const interfacesCount = reader.readU2();
|
|
287
|
+
const interfaceInternalNames = [];
|
|
288
|
+
for (let index = 0; index < interfacesCount; index += 1) {
|
|
289
|
+
interfaceInternalNames.push(readClassName(cp, reader.readU2()));
|
|
290
|
+
}
|
|
291
|
+
const readMembers = () => {
|
|
292
|
+
const count = reader.readU2();
|
|
293
|
+
const members = [];
|
|
294
|
+
for (let index = 0; index < count; index += 1) {
|
|
295
|
+
const accessFlags = reader.readU2();
|
|
296
|
+
const nameIndex = reader.readU2();
|
|
297
|
+
const descriptorIndex = reader.readU2();
|
|
298
|
+
const attributesCount = reader.readU2();
|
|
299
|
+
const attributeNames = readAttributes(reader, cp, attributesCount);
|
|
300
|
+
members.push({
|
|
301
|
+
name: readUtf8(cp, nameIndex),
|
|
302
|
+
descriptor: readUtf8(cp, descriptorIndex),
|
|
303
|
+
accessFlags,
|
|
304
|
+
isSynthetic: (accessFlags & ACC_SYNTHETIC) !== 0 ||
|
|
305
|
+
attributeNames.some((attributeName) => attributeName === "Synthetic")
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return members;
|
|
309
|
+
};
|
|
310
|
+
const fields = readMembers();
|
|
311
|
+
const methods = readMembers();
|
|
312
|
+
const attributesCount = reader.readU2();
|
|
313
|
+
readAttributes(reader, cp, attributesCount);
|
|
314
|
+
return {
|
|
315
|
+
internalName: readClassName(cp, thisClassIndex),
|
|
316
|
+
superInternalName: readOptionalClassName(cp, superClassIndex),
|
|
317
|
+
interfaceInternalNames,
|
|
318
|
+
fields,
|
|
319
|
+
methods
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
export class MinecraftExplorerService {
|
|
323
|
+
config;
|
|
324
|
+
signatureCache = new Map();
|
|
325
|
+
constructor(explicitConfig) {
|
|
326
|
+
this.config = explicitConfig ?? loadConfig();
|
|
327
|
+
}
|
|
328
|
+
async getSignature(input) {
|
|
329
|
+
const jarPath = this.normalizeJarPathOrThrow(input.jarPath);
|
|
330
|
+
const fqn = input.fqn.trim();
|
|
331
|
+
if (!fqn) {
|
|
332
|
+
throw createError({
|
|
333
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
334
|
+
message: "fqn must be a non-empty string."
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
const access = input.access ?? "public";
|
|
338
|
+
const includeSynthetic = input.includeSynthetic ?? false;
|
|
339
|
+
const includeInherited = input.includeInherited ?? false;
|
|
340
|
+
const memberPattern = input.memberPattern ? lower(input.memberPattern) : undefined;
|
|
341
|
+
const cacheKey = [
|
|
342
|
+
jarPath,
|
|
343
|
+
fqn,
|
|
344
|
+
access,
|
|
345
|
+
includeSynthetic ? "1" : "0",
|
|
346
|
+
includeInherited ? "1" : "0",
|
|
347
|
+
memberPattern ?? ""
|
|
348
|
+
].join("|");
|
|
349
|
+
const cached = this.signatureCache.get(cacheKey);
|
|
350
|
+
if (cached) {
|
|
351
|
+
this.signatureCache.delete(cacheKey);
|
|
352
|
+
this.signatureCache.set(cacheKey, cached);
|
|
353
|
+
return {
|
|
354
|
+
...cached,
|
|
355
|
+
context: this.contextForJar(jarPath)
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const classEntryPath = `${toInternalName(fqn)}.class`;
|
|
359
|
+
let classBuffer;
|
|
360
|
+
try {
|
|
361
|
+
classBuffer = await readJarEntryAsBuffer(jarPath, classEntryPath);
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
throw createError({
|
|
365
|
+
code: ERROR_CODES.CLASS_NOT_FOUND,
|
|
366
|
+
message: `Class "${fqn}" was not found in "${jarPath}".`,
|
|
367
|
+
details: { fqn, jarPath, classEntryPath }
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
const parsed = parseClassFile(classBuffer);
|
|
371
|
+
const parsedClassCache = new Map([[parsed.internalName, parsed]]);
|
|
372
|
+
const warnings = [];
|
|
373
|
+
const warnMissingInheritedClass = (internalName, relation) => {
|
|
374
|
+
warnings.push(`Could not resolve ${relation} class "${internalName.replace(/\//g, ".")}" while expanding inherited members.`);
|
|
375
|
+
};
|
|
376
|
+
const readParsedClassByInternalName = async (internalName, relation) => {
|
|
377
|
+
const cachedParsed = parsedClassCache.get(internalName);
|
|
378
|
+
if (cachedParsed) {
|
|
379
|
+
return cachedParsed;
|
|
380
|
+
}
|
|
381
|
+
const classPath = `${internalName}.class`;
|
|
382
|
+
try {
|
|
383
|
+
const classBytes = await readJarEntryAsBuffer(jarPath, classPath);
|
|
384
|
+
const parsedClass = parseClassFile(classBytes);
|
|
385
|
+
parsedClassCache.set(parsedClass.internalName, parsedClass);
|
|
386
|
+
return parsedClass;
|
|
387
|
+
}
|
|
388
|
+
catch {
|
|
389
|
+
warnMissingInheritedClass(internalName, relation);
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
const hierarchyClasses = [parsed];
|
|
394
|
+
if (includeInherited) {
|
|
395
|
+
const visited = new Set([parsed.internalName]);
|
|
396
|
+
let currentSuper = parsed.superInternalName;
|
|
397
|
+
let depth = 0;
|
|
398
|
+
while (currentSuper) {
|
|
399
|
+
if (depth >= MAX_INHERITANCE_DEPTH) {
|
|
400
|
+
warnings.push(`Stopped inherited member expansion at depth ${MAX_INHERITANCE_DEPTH} while resolving "${fqn}".`);
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
if (visited.has(currentSuper)) {
|
|
404
|
+
warnings.push(`Detected class hierarchy cycle at "${currentSuper.replace(/\//g, ".")}".`);
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
const parsedSuper = await readParsedClassByInternalName(currentSuper, "super");
|
|
408
|
+
if (!parsedSuper) {
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
visited.add(parsedSuper.internalName);
|
|
412
|
+
hierarchyClasses.push(parsedSuper);
|
|
413
|
+
currentSuper = parsedSuper.superInternalName;
|
|
414
|
+
depth += 1;
|
|
415
|
+
}
|
|
416
|
+
const queue = [];
|
|
417
|
+
const queued = new Set();
|
|
418
|
+
const interfaceClasses = [];
|
|
419
|
+
const enqueueInterfaces = (classFile) => {
|
|
420
|
+
for (const interfaceInternalName of classFile.interfaceInternalNames) {
|
|
421
|
+
if (visited.has(interfaceInternalName) || queued.has(interfaceInternalName)) {
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
queue.push(interfaceInternalName);
|
|
425
|
+
queued.add(interfaceInternalName);
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
for (const classFile of hierarchyClasses) {
|
|
429
|
+
enqueueInterfaces(classFile);
|
|
430
|
+
}
|
|
431
|
+
while (queue.length > 0) {
|
|
432
|
+
const interfaceInternalName = queue.shift();
|
|
433
|
+
queued.delete(interfaceInternalName);
|
|
434
|
+
if (visited.has(interfaceInternalName)) {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const parsedInterface = await readParsedClassByInternalName(interfaceInternalName, "interface");
|
|
438
|
+
if (!parsedInterface) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
visited.add(parsedInterface.internalName);
|
|
442
|
+
interfaceClasses.push(parsedInterface);
|
|
443
|
+
enqueueInterfaces(parsedInterface);
|
|
444
|
+
}
|
|
445
|
+
hierarchyClasses.push(...interfaceClasses);
|
|
446
|
+
}
|
|
447
|
+
const toSignatureMember = (ownerFqn, ownerSimpleClassName, member, category) => {
|
|
448
|
+
if (category === "field") {
|
|
449
|
+
const fieldType = parseFieldType(member.descriptor).type;
|
|
450
|
+
const modifiers = modifierPrefix(member.accessFlags, "field");
|
|
451
|
+
return {
|
|
452
|
+
ownerFqn,
|
|
453
|
+
name: member.name,
|
|
454
|
+
javaSignature: `${modifiers ? `${modifiers} ` : ""}${fieldType} ${member.name}`.trim(),
|
|
455
|
+
jvmDescriptor: member.descriptor,
|
|
456
|
+
accessFlags: member.accessFlags,
|
|
457
|
+
isSynthetic: member.isSynthetic
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
const parsedMethod = parseMethodDescriptor(member.descriptor);
|
|
461
|
+
const modifiers = modifierPrefix(member.accessFlags, "method");
|
|
462
|
+
const args = parsedMethod.args.join(", ");
|
|
463
|
+
if (member.name === "<init>") {
|
|
464
|
+
return {
|
|
465
|
+
ownerFqn,
|
|
466
|
+
name: member.name,
|
|
467
|
+
javaSignature: `${modifiers ? `${modifiers} ` : ""}${ownerSimpleClassName}(${args})`.trim(),
|
|
468
|
+
jvmDescriptor: member.descriptor,
|
|
469
|
+
accessFlags: member.accessFlags,
|
|
470
|
+
isSynthetic: member.isSynthetic
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
ownerFqn,
|
|
475
|
+
name: member.name,
|
|
476
|
+
javaSignature: `${modifiers ? `${modifiers} ` : ""}${parsedMethod.returnType} ${member.name}(${args})`.trim(),
|
|
477
|
+
jvmDescriptor: member.descriptor,
|
|
478
|
+
accessFlags: member.accessFlags,
|
|
479
|
+
isSynthetic: member.isSynthetic
|
|
480
|
+
};
|
|
481
|
+
};
|
|
482
|
+
const shouldIncludeMember = (member) => {
|
|
483
|
+
if (!includeSynthetic && member.isSynthetic) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
if (access === "public" && !hasPublicVisibility(member.accessFlags)) {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
if (memberPattern && !lower(member.name).includes(memberPattern)) {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
return true;
|
|
493
|
+
};
|
|
494
|
+
const toMembers = (classFile, category, predicate) => {
|
|
495
|
+
const ownerFqn = classFile.internalName.replace(/\//g, ".");
|
|
496
|
+
const ownerSimpleClassName = ownerFqn.split(".").at(-1) ?? ownerFqn;
|
|
497
|
+
const sourceMembers = category === "field" ? classFile.fields : classFile.methods;
|
|
498
|
+
return sourceMembers
|
|
499
|
+
.filter(predicate)
|
|
500
|
+
.map((member) => toSignatureMember(ownerFqn, ownerSimpleClassName, member, category));
|
|
501
|
+
};
|
|
502
|
+
const dedupeMembers = (members) => {
|
|
503
|
+
const seen = new Set();
|
|
504
|
+
const result = [];
|
|
505
|
+
for (const member of members) {
|
|
506
|
+
const key = `${member.ownerFqn}|${member.name}|${member.jvmDescriptor}`;
|
|
507
|
+
if (seen.has(key)) {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
seen.add(key);
|
|
511
|
+
result.push(member);
|
|
512
|
+
}
|
|
513
|
+
return result;
|
|
514
|
+
};
|
|
515
|
+
const targetClass = hierarchyClasses[0];
|
|
516
|
+
const constructors = toMembers(targetClass, "method", (member) => member.name === "<init>" && shouldIncludeMember(member));
|
|
517
|
+
const fields = dedupeMembers(hierarchyClasses.flatMap((classFile) => toMembers(classFile, "field", (member) => shouldIncludeMember(member))));
|
|
518
|
+
const methods = dedupeMembers(hierarchyClasses.flatMap((classFile) => toMembers(classFile, "method", (member) => member.name !== "<init>" && shouldIncludeMember(member))));
|
|
519
|
+
const output = {
|
|
520
|
+
constructors,
|
|
521
|
+
methods,
|
|
522
|
+
fields,
|
|
523
|
+
warnings,
|
|
524
|
+
context: this.contextForJar(jarPath)
|
|
525
|
+
};
|
|
526
|
+
this.signatureCache.set(cacheKey, output);
|
|
527
|
+
this.trimSignatureCache();
|
|
528
|
+
return output;
|
|
529
|
+
}
|
|
530
|
+
contextForJar(jarPath) {
|
|
531
|
+
return {
|
|
532
|
+
minecraftVersion: extractVersionFromPath(jarPath) ?? "unknown",
|
|
533
|
+
mappingType: "unknown",
|
|
534
|
+
mappingNamespace: "official",
|
|
535
|
+
jarHash: artifactSignatureFromFile(jarPath).sourceArtifactId,
|
|
536
|
+
generatedAt: new Date().toISOString()
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
normalizeJarPathOrThrow(jarPath) {
|
|
540
|
+
try {
|
|
541
|
+
return normalizeJarPath(jarPath);
|
|
542
|
+
}
|
|
543
|
+
catch (error) {
|
|
544
|
+
throw createError({
|
|
545
|
+
code: ERROR_CODES.JAR_NOT_FOUND,
|
|
546
|
+
message: error instanceof Error ? error.message : `Could not resolve jar "${jarPath}".`,
|
|
547
|
+
details: { jarPath }
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
trimSignatureCache() {
|
|
552
|
+
const maxEntries = Math.max(1, this.config.maxSignatureCache ?? 2_000);
|
|
553
|
+
while (this.signatureCache.size > maxEntries) {
|
|
554
|
+
const oldest = this.signatureCache.keys().next().value;
|
|
555
|
+
if (!oldest) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
this.signatureCache.delete(oldest);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
//# sourceMappingURL=minecraft-explorer-service.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight regex-based parser for Fabric Mixin Java sources.
|
|
3
|
+
* No AST required — pattern-matches annotations and member declarations.
|
|
4
|
+
*/
|
|
5
|
+
export type ParsedMixinTarget = {
|
|
6
|
+
className: string;
|
|
7
|
+
};
|
|
8
|
+
export type ParsedInjection = {
|
|
9
|
+
annotation: string;
|
|
10
|
+
method: string;
|
|
11
|
+
line: number;
|
|
12
|
+
};
|
|
13
|
+
export type ParsedShadow = {
|
|
14
|
+
kind: "field" | "method";
|
|
15
|
+
name: string;
|
|
16
|
+
descriptor?: string;
|
|
17
|
+
line: number;
|
|
18
|
+
};
|
|
19
|
+
export type ParsedAccessor = {
|
|
20
|
+
annotation: "Accessor" | "Invoker";
|
|
21
|
+
name: string;
|
|
22
|
+
targetName: string;
|
|
23
|
+
line: number;
|
|
24
|
+
};
|
|
25
|
+
export type ParsedMixin = {
|
|
26
|
+
className: string;
|
|
27
|
+
targets: ParsedMixinTarget[];
|
|
28
|
+
priority?: number;
|
|
29
|
+
injections: ParsedInjection[];
|
|
30
|
+
shadows: ParsedShadow[];
|
|
31
|
+
accessors: ParsedAccessor[];
|
|
32
|
+
parseWarnings: string[];
|
|
33
|
+
};
|
|
34
|
+
export declare function parseMixinSource(source: string): ParsedMixin;
|