@adhisang/minecraft-modding-mcp 1.2.1 → 2.1.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 +73 -0
- package/README.md +184 -64
- package/dist/cli.js +31 -4
- package/dist/compat-stdio-transport.d.ts +2 -7
- package/dist/compat-stdio-transport.js +12 -154
- package/dist/index.js +537 -202
- package/dist/json-rpc-framing.d.ts +22 -0
- package/dist/json-rpc-framing.js +168 -0
- package/dist/mapping-pipeline-service.d.ts +1 -1
- package/dist/mapping-pipeline-service.js +13 -5
- package/dist/mapping-service.d.ts +12 -4
- package/dist/mapping-service.js +222 -105
- package/dist/mcp-helpers.d.ts +10 -2
- package/dist/mcp-helpers.js +59 -5
- package/dist/minecraft-explorer-service.d.ts +1 -2
- package/dist/minecraft-explorer-service.js +120 -24
- package/dist/mixin-validator.d.ts +24 -2
- package/dist/mixin-validator.js +228 -103
- package/dist/mod-decompile-service.d.ts +5 -0
- package/dist/mod-decompile-service.js +40 -5
- package/dist/mod-remap-service.js +142 -30
- package/dist/mojang-tiny-mapping-service.js +26 -26
- package/dist/path-resolver.js +41 -4
- package/dist/registry-service.d.ts +10 -1
- package/dist/registry-service.js +154 -22
- package/dist/resources.js +7 -7
- package/dist/search-hit-accumulator.d.ts +0 -3
- package/dist/search-hit-accumulator.js +27 -6
- package/dist/source-jar-reader.js +16 -2
- package/dist/source-resolver.d.ts +1 -0
- package/dist/source-resolver.js +93 -2
- package/dist/source-service.d.ts +76 -47
- package/dist/source-service.js +1344 -763
- package/dist/stdio-supervisor.d.ts +46 -0
- package/dist/stdio-supervisor.js +349 -0
- package/dist/storage/files-repo.d.ts +3 -0
- package/dist/storage/files-repo.js +66 -1
- package/dist/storage/migrations.d.ts +1 -1
- package/dist/storage/migrations.js +6 -2
- package/dist/storage/schema.d.ts +1 -0
- package/dist/storage/schema.js +7 -0
- package/dist/symbols/symbol-extractor.js +6 -4
- package/dist/tool-execution-gate.d.ts +15 -0
- package/dist/tool-execution-gate.js +58 -0
- package/dist/tool-input.d.ts +6 -0
- package/dist/tool-input.js +64 -0
- package/dist/types.d.ts +1 -1
- package/dist/version-diff-service.js +10 -5
- package/dist/version-service.js +7 -2
- package/dist/workspace-mapping-service.js +12 -0
- package/package.json +4 -1
package/dist/mapping-service.js
CHANGED
|
@@ -7,7 +7,7 @@ import { defaultDownloadPath, downloadToCache } from "./repo-downloader.js";
|
|
|
7
7
|
import { listJarEntries, readJarEntryAsUtf8 } from "./source-jar-reader.js";
|
|
8
8
|
import { VersionService, isUnobfuscatedVersion } from "./version-service.js";
|
|
9
9
|
const SUPPORTED_MAPPINGS = new Set([
|
|
10
|
-
"
|
|
10
|
+
"obfuscated",
|
|
11
11
|
"mojang",
|
|
12
12
|
"intermediary",
|
|
13
13
|
"yarn"
|
|
@@ -37,13 +37,21 @@ function addToSetMap(map, key, value) {
|
|
|
37
37
|
map.set(normalizedKey, existing);
|
|
38
38
|
}
|
|
39
39
|
function normalizedVariants(symbol) {
|
|
40
|
-
const variants =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
const variants = [symbol];
|
|
41
|
+
let dotted;
|
|
42
|
+
if (symbol.includes("/")) {
|
|
43
|
+
dotted = symbol.replace(/\//g, ".");
|
|
44
|
+
if (dotted !== symbol) {
|
|
45
|
+
variants.push(dotted);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (symbol.includes(".")) {
|
|
49
|
+
const slashed = symbol.replace(/\./g, "/");
|
|
50
|
+
if (slashed !== symbol && slashed !== dotted) {
|
|
51
|
+
variants.push(slashed);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return variants;
|
|
47
55
|
}
|
|
48
56
|
function simpleName(symbol) {
|
|
49
57
|
const trimmed = symbol.trim();
|
|
@@ -230,12 +238,42 @@ function pairKey(sourceMapping, targetMapping) {
|
|
|
230
238
|
return `${sourceMapping}->${targetMapping}`;
|
|
231
239
|
}
|
|
232
240
|
function parsePairKey(key) {
|
|
233
|
-
const
|
|
241
|
+
const separator = key.indexOf("->");
|
|
242
|
+
const source = separator >= 0 ? key.slice(0, separator) : key;
|
|
243
|
+
const target = separator >= 0 ? key.slice(separator + 2) : "";
|
|
234
244
|
return {
|
|
235
245
|
sourceMapping: source,
|
|
236
246
|
targetMapping: target
|
|
237
247
|
};
|
|
238
248
|
}
|
|
249
|
+
function buildAdjacency(pairs) {
|
|
250
|
+
const adjacency = new Map();
|
|
251
|
+
for (const key of pairs.keys()) {
|
|
252
|
+
const { sourceMapping, targetMapping } = parsePairKey(key);
|
|
253
|
+
let neighbors = adjacency.get(sourceMapping);
|
|
254
|
+
if (!neighbors) {
|
|
255
|
+
neighbors = new Set();
|
|
256
|
+
adjacency.set(sourceMapping, neighbors);
|
|
257
|
+
}
|
|
258
|
+
neighbors.add(targetMapping);
|
|
259
|
+
}
|
|
260
|
+
return new Map([...adjacency.entries()].map(([mapping, neighbors]) => [mapping, [...neighbors]]));
|
|
261
|
+
}
|
|
262
|
+
function buildTargetRecordIndex(pairs) {
|
|
263
|
+
const recordsByTarget = new Map();
|
|
264
|
+
for (const [key, pair] of pairs.entries()) {
|
|
265
|
+
const { targetMapping } = parsePairKey(key);
|
|
266
|
+
let bucket = recordsByTarget.get(targetMapping);
|
|
267
|
+
if (!bucket) {
|
|
268
|
+
bucket = new Map();
|
|
269
|
+
recordsByTarget.set(targetMapping, bucket);
|
|
270
|
+
}
|
|
271
|
+
for (const record of pair.index.records.values()) {
|
|
272
|
+
bucket.set(buildSymbolKey(record), record);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return new Map([...recordsByTarget.entries()].map(([mapping, records]) => [mapping, [...records.values()]]));
|
|
276
|
+
}
|
|
239
277
|
function ensurePairIndex(indexes, from, to) {
|
|
240
278
|
const key = pairKey(from, to);
|
|
241
279
|
const existing = indexes.get(key);
|
|
@@ -254,7 +292,7 @@ const PROGUARD_PRIMITIVES = {
|
|
|
254
292
|
/**
|
|
255
293
|
* Convert a single proguard type (e.g. "int", "net.minecraft.Foo", "int[][]")
|
|
256
294
|
* to JVM notation (e.g. "I", "Lnet/minecraft/Foo;", "[[I").
|
|
257
|
-
* `classLookup` maps mojang class names →
|
|
295
|
+
* `classLookup` maps mojang class names → obfuscated class names (for the obfuscated descriptor).
|
|
258
296
|
* Pass `undefined` to skip class name translation (for mojang descriptors).
|
|
259
297
|
*/
|
|
260
298
|
function proguardTypeToJvm(type, classLookup) {
|
|
@@ -294,12 +332,12 @@ function parseProguardMethod(value, classLookup) {
|
|
|
294
332
|
return { name, descriptor: `(${paramDescriptor})${returnDescriptor}` };
|
|
295
333
|
}
|
|
296
334
|
function parseClientMappings(text) {
|
|
297
|
-
const
|
|
298
|
-
const
|
|
335
|
+
const obfuscatedToMojang = createDirectionIndex();
|
|
336
|
+
const mojangToObfuscated = createDirectionIndex();
|
|
299
337
|
// Two-pass parsing: first collect class name mappings, then parse members with descriptors.
|
|
300
338
|
const lines = text.split(/\r?\n/);
|
|
301
|
-
// Pass 1: collect class name mappings (mojang →
|
|
302
|
-
const
|
|
339
|
+
// Pass 1: collect class name mappings (mojang → obfuscated)
|
|
340
|
+
const mojangToObfuscatedClass = new Map();
|
|
303
341
|
let classCount = 0;
|
|
304
342
|
for (const rawLine of lines) {
|
|
305
343
|
const line = rawLine.trim();
|
|
@@ -309,9 +347,9 @@ function parseClientMappings(text) {
|
|
|
309
347
|
const classMatch = /^(.+?)\s+->\s+(.+):$/.exec(line);
|
|
310
348
|
if (classMatch) {
|
|
311
349
|
const mojangClass = classMatch[1]?.trim() ?? "";
|
|
312
|
-
const
|
|
313
|
-
if (mojangClass &&
|
|
314
|
-
|
|
350
|
+
const obfuscatedClass = classMatch[2]?.trim() ?? "";
|
|
351
|
+
if (mojangClass && obfuscatedClass) {
|
|
352
|
+
mojangToObfuscatedClass.set(mojangClass, obfuscatedClass);
|
|
315
353
|
classCount += 1;
|
|
316
354
|
}
|
|
317
355
|
}
|
|
@@ -332,17 +370,17 @@ function parseClientMappings(text) {
|
|
|
332
370
|
const classMatch = /^(.+?)\s+->\s+(.+):$/.exec(line);
|
|
333
371
|
if (classMatch) {
|
|
334
372
|
const mojangClass = classMatch[1]?.trim() ?? "";
|
|
335
|
-
const
|
|
336
|
-
if (!mojangClass || !
|
|
373
|
+
const obfuscatedClass = classMatch[2]?.trim() ?? "";
|
|
374
|
+
if (!mojangClass || !obfuscatedClass) {
|
|
337
375
|
currentClass = undefined;
|
|
338
376
|
continue;
|
|
339
377
|
}
|
|
340
378
|
currentClass = {
|
|
341
|
-
|
|
379
|
+
obfuscated: obfuscatedClass,
|
|
342
380
|
mojang: mojangClass
|
|
343
381
|
};
|
|
344
|
-
addLookupEntries(
|
|
345
|
-
addLookupEntries(
|
|
382
|
+
addLookupEntries(obfuscatedToMojang, createClassSymbolRecord(obfuscatedClass), createClassSymbolRecord(mojangClass));
|
|
383
|
+
addLookupEntries(mojangToObfuscated, createClassSymbolRecord(mojangClass), createClassSymbolRecord(obfuscatedClass));
|
|
346
384
|
continue;
|
|
347
385
|
}
|
|
348
386
|
if (!currentClass) {
|
|
@@ -359,31 +397,31 @@ function parseClientMappings(text) {
|
|
|
359
397
|
}
|
|
360
398
|
const mojangMemberSignature = stripLineInfo(leftRaw);
|
|
361
399
|
// Try method parsing with JVM descriptor
|
|
362
|
-
const
|
|
363
|
-
if (
|
|
400
|
+
const obfuscatedMethod = parseProguardMethod(mojangMemberSignature, mojangToObfuscatedClass);
|
|
401
|
+
if (obfuscatedMethod) {
|
|
364
402
|
const mojangMethod = parseProguardMethod(mojangMemberSignature, undefined);
|
|
365
|
-
const
|
|
403
|
+
const obfuscatedDescriptor = obfuscatedMethod.descriptor;
|
|
366
404
|
const mojangDescriptor = mojangMethod?.descriptor;
|
|
367
|
-
addLookupEntries(
|
|
368
|
-
addLookupEntries(
|
|
405
|
+
addLookupEntries(obfuscatedToMojang, createMethodSymbolRecord(currentClass.obfuscated, rightRaw, obfuscatedDescriptor), createMethodSymbolRecord(currentClass.mojang, obfuscatedMethod.name, mojangDescriptor));
|
|
406
|
+
addLookupEntries(mojangToObfuscated, createMethodSymbolRecord(currentClass.mojang, obfuscatedMethod.name, mojangDescriptor), createMethodSymbolRecord(currentClass.obfuscated, rightRaw, obfuscatedDescriptor));
|
|
369
407
|
continue;
|
|
370
408
|
}
|
|
371
409
|
const fieldName = parseFieldName(mojangMemberSignature);
|
|
372
410
|
if (!fieldName) {
|
|
373
411
|
continue;
|
|
374
412
|
}
|
|
375
|
-
addLookupEntries(
|
|
376
|
-
addLookupEntries(
|
|
413
|
+
addLookupEntries(obfuscatedToMojang, createFieldSymbolRecord(currentClass.obfuscated, rightRaw), createFieldSymbolRecord(currentClass.mojang, fieldName));
|
|
414
|
+
addLookupEntries(mojangToObfuscated, createFieldSymbolRecord(currentClass.mojang, fieldName), createFieldSymbolRecord(currentClass.obfuscated, rightRaw));
|
|
377
415
|
}
|
|
378
416
|
const result = new Map();
|
|
379
|
-
result.set(pairKey("
|
|
380
|
-
result.set(pairKey("mojang", "
|
|
417
|
+
result.set(pairKey("obfuscated", "mojang"), obfuscatedToMojang);
|
|
418
|
+
result.set(pairKey("mojang", "obfuscated"), mojangToObfuscated);
|
|
381
419
|
return result;
|
|
382
420
|
}
|
|
383
421
|
function normalizeTinyNamespace(namespace) {
|
|
384
422
|
const normalized = namespace.trim().toLowerCase();
|
|
385
|
-
if (normalized === "
|
|
386
|
-
return "
|
|
423
|
+
if (normalized === "obfuscated") {
|
|
424
|
+
return "obfuscated";
|
|
387
425
|
}
|
|
388
426
|
if (normalized === "mojang") {
|
|
389
427
|
return "mojang";
|
|
@@ -552,38 +590,48 @@ function mappingSourceOrder(priority) {
|
|
|
552
590
|
}
|
|
553
591
|
return ["loom-cache", "maven"];
|
|
554
592
|
}
|
|
555
|
-
function namespacePath(
|
|
593
|
+
function namespacePath(graph, sourceMapping, targetMapping) {
|
|
556
594
|
if (sourceMapping === targetMapping) {
|
|
557
595
|
return [sourceMapping];
|
|
558
596
|
}
|
|
597
|
+
const key = pairKey(sourceMapping, targetMapping);
|
|
598
|
+
if (graph.pathCache.has(key)) {
|
|
599
|
+
return graph.pathCache.get(key);
|
|
600
|
+
}
|
|
601
|
+
const { adjacency } = graph;
|
|
559
602
|
const queue = [sourceMapping];
|
|
603
|
+
let queueIndex = 0;
|
|
560
604
|
const parent = new Map([[sourceMapping, undefined]]);
|
|
561
|
-
while (queue.length
|
|
562
|
-
const current = queue
|
|
605
|
+
while (queueIndex < queue.length) {
|
|
606
|
+
const current = queue[queueIndex];
|
|
607
|
+
queueIndex += 1;
|
|
563
608
|
if (current === targetMapping) {
|
|
564
609
|
break;
|
|
565
610
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
if (parent.has(
|
|
611
|
+
const neighbors = adjacency.get(current);
|
|
612
|
+
if (!neighbors) {
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
for (const neighbor of neighbors) {
|
|
616
|
+
if (parent.has(neighbor)) {
|
|
572
617
|
continue;
|
|
573
618
|
}
|
|
574
|
-
parent.set(
|
|
575
|
-
queue.push(
|
|
619
|
+
parent.set(neighbor, current);
|
|
620
|
+
queue.push(neighbor);
|
|
576
621
|
}
|
|
577
622
|
}
|
|
578
623
|
if (!parent.has(targetMapping)) {
|
|
624
|
+
graph.pathCache.set(key, undefined);
|
|
579
625
|
return undefined;
|
|
580
626
|
}
|
|
581
|
-
const
|
|
627
|
+
const reversedPath = [];
|
|
582
628
|
let cursor = targetMapping;
|
|
583
629
|
while (cursor) {
|
|
584
|
-
|
|
630
|
+
reversedPath.push(cursor);
|
|
585
631
|
cursor = parent.get(cursor);
|
|
586
632
|
}
|
|
633
|
+
const path = reversedPath.reverse();
|
|
634
|
+
graph.pathCache.set(key, path);
|
|
587
635
|
return path;
|
|
588
636
|
}
|
|
589
637
|
function pathUsesSource(pairs, path, source) {
|
|
@@ -765,17 +813,7 @@ function applyDisambiguationHints(candidates, disambiguation) {
|
|
|
765
813
|
return filtered;
|
|
766
814
|
}
|
|
767
815
|
function collectTargetRecords(graph, targetMapping) {
|
|
768
|
-
|
|
769
|
-
for (const [key, pair] of graph.pairs.entries()) {
|
|
770
|
-
const parsed = parsePairKey(key);
|
|
771
|
-
if (parsed.targetMapping !== targetMapping) {
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
for (const record of pair.index.records.values()) {
|
|
775
|
-
merged.set(buildSymbolKey(record), record);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
return [...merged.values()];
|
|
816
|
+
return [...(graph.recordsByTarget.get(targetMapping) ?? [])];
|
|
779
817
|
}
|
|
780
818
|
function normalizeIncludedKinds(inputKinds) {
|
|
781
819
|
const normalized = new Set();
|
|
@@ -823,6 +861,28 @@ function inferAmbiguityReasons(candidates, usedMojangClientMappings) {
|
|
|
823
861
|
}
|
|
824
862
|
return reasons;
|
|
825
863
|
}
|
|
864
|
+
function clampCandidateLimit(limit) {
|
|
865
|
+
if (!Number.isFinite(limit) || limit == null) {
|
|
866
|
+
return MAX_CANDIDATES;
|
|
867
|
+
}
|
|
868
|
+
return Math.max(1, Math.min(MAX_CANDIDATES, Math.trunc(limit)));
|
|
869
|
+
}
|
|
870
|
+
function limitResolutionCandidates(candidates, requestedLimit) {
|
|
871
|
+
const candidateCount = candidates.length;
|
|
872
|
+
const limit = clampCandidateLimit(requestedLimit);
|
|
873
|
+
const limitedCandidates = candidateCount > limit ? candidates.slice(0, limit) : candidates;
|
|
874
|
+
return {
|
|
875
|
+
candidates: limitedCandidates,
|
|
876
|
+
candidateCount,
|
|
877
|
+
...(limitedCandidates.length < candidateCount ? { candidatesTruncated: true } : {})
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
function clampRowLimit(limit) {
|
|
881
|
+
if (!Number.isFinite(limit) || limit == null) {
|
|
882
|
+
return undefined;
|
|
883
|
+
}
|
|
884
|
+
return Math.max(1, Math.min(5000, Math.trunc(limit)));
|
|
885
|
+
}
|
|
826
886
|
export class MappingService {
|
|
827
887
|
config;
|
|
828
888
|
versionService;
|
|
@@ -845,7 +905,7 @@ export class MappingService {
|
|
|
845
905
|
}
|
|
846
906
|
});
|
|
847
907
|
}
|
|
848
|
-
const { record: queryRecord, querySymbol } = normalizeQuerySymbol(input);
|
|
908
|
+
const { record: queryRecord, querySymbol } = normalizeQuerySymbol(input, input.signatureMode);
|
|
849
909
|
const sourceMapping = input.sourceMapping;
|
|
850
910
|
const targetMapping = input.targetMapping;
|
|
851
911
|
if (!SUPPORTED_MAPPINGS.has(sourceMapping) || !SUPPORTED_MAPPINGS.has(targetMapping)) {
|
|
@@ -872,18 +932,21 @@ export class MappingService {
|
|
|
872
932
|
matchKind: "exact",
|
|
873
933
|
confidence: 1
|
|
874
934
|
});
|
|
935
|
+
const limited = limitResolutionCandidates([identity], input.maxCandidates);
|
|
875
936
|
return {
|
|
876
937
|
querySymbol,
|
|
877
938
|
mappingContext,
|
|
878
939
|
resolved: true,
|
|
879
940
|
status: "resolved",
|
|
880
941
|
resolvedSymbol: querySymbol,
|
|
881
|
-
candidates:
|
|
942
|
+
candidates: limited.candidates,
|
|
943
|
+
candidateCount: limited.candidateCount,
|
|
944
|
+
candidatesTruncated: limited.candidatesTruncated,
|
|
882
945
|
warnings: []
|
|
883
946
|
};
|
|
884
947
|
}
|
|
885
948
|
const graph = await this.loadGraph(version, priority);
|
|
886
|
-
const path = namespacePath(graph
|
|
949
|
+
const path = namespacePath(graph, sourceMapping, targetMapping);
|
|
887
950
|
if (!path) {
|
|
888
951
|
return {
|
|
889
952
|
querySymbol,
|
|
@@ -891,6 +954,7 @@ export class MappingService {
|
|
|
891
954
|
resolved: false,
|
|
892
955
|
status: "mapping_unavailable",
|
|
893
956
|
candidates: [],
|
|
957
|
+
candidateCount: 0,
|
|
894
958
|
warnings: [
|
|
895
959
|
`No mapping path is available for ${sourceMapping} -> ${targetMapping} on version "${version}".`
|
|
896
960
|
]
|
|
@@ -903,6 +967,7 @@ export class MappingService {
|
|
|
903
967
|
warnings.push(`Disambiguation hints narrowed candidates from ${rawCandidates.length} to ${disambiguatedCandidates.length}.`);
|
|
904
968
|
}
|
|
905
969
|
const candidates = disambiguatedCandidates.map(toResolutionCandidate);
|
|
970
|
+
const limitedCandidates = limitResolutionCandidates(candidates, input.maxCandidates);
|
|
906
971
|
if (queryRecord.kind === "method" &&
|
|
907
972
|
queryRecord.descriptor &&
|
|
908
973
|
pathUsesSource(graph.pairs, path, "mojang-client-mappings") &&
|
|
@@ -925,7 +990,9 @@ export class MappingService {
|
|
|
925
990
|
resolved: status === "resolved",
|
|
926
991
|
status,
|
|
927
992
|
resolvedSymbol: status === "resolved" ? candidates[0] : undefined,
|
|
928
|
-
candidates,
|
|
993
|
+
candidates: limitedCandidates.candidates,
|
|
994
|
+
candidateCount: limitedCandidates.candidateCount,
|
|
995
|
+
candidatesTruncated: limitedCandidates.candidatesTruncated,
|
|
929
996
|
warnings,
|
|
930
997
|
provenance: this.provenanceForPath(graph, path),
|
|
931
998
|
ambiguityReasons
|
|
@@ -963,7 +1030,7 @@ export class MappingService {
|
|
|
963
1030
|
};
|
|
964
1031
|
}
|
|
965
1032
|
const graph = await this.loadGraph(version, priority);
|
|
966
|
-
const path = namespacePath(graph
|
|
1033
|
+
const path = namespacePath(graph, sourceMapping, targetMapping);
|
|
967
1034
|
if (!path) {
|
|
968
1035
|
throw createError({
|
|
969
1036
|
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
@@ -973,8 +1040,8 @@ export class MappingService {
|
|
|
973
1040
|
sourceMapping,
|
|
974
1041
|
targetMapping,
|
|
975
1042
|
sourcePriority: priority,
|
|
976
|
-
nextAction: "Try mapping=
|
|
977
|
-
suggestedCall: { tool: "resolve-artifact", params: { mapping: "
|
|
1043
|
+
nextAction: "Try mapping=obfuscated which is always available.",
|
|
1044
|
+
suggestedCall: { tool: "resolve-artifact", params: { mapping: "obfuscated" } }
|
|
978
1045
|
}
|
|
979
1046
|
});
|
|
980
1047
|
}
|
|
@@ -1000,16 +1067,10 @@ export class MappingService {
|
|
|
1000
1067
|
}
|
|
1001
1068
|
});
|
|
1002
1069
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
details: {
|
|
1008
|
-
kind: input.kind
|
|
1009
|
-
}
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
const { record: queryRecord, querySymbol } = normalizeQuerySymbol(input);
|
|
1070
|
+
const { record: queryRecord, querySymbol } = normalizeQuerySymbol({
|
|
1071
|
+
...input,
|
|
1072
|
+
kind: "method"
|
|
1073
|
+
});
|
|
1013
1074
|
const owner = queryRecord.owner;
|
|
1014
1075
|
const method = queryRecord.name;
|
|
1015
1076
|
const descriptor = queryRecord.descriptor;
|
|
@@ -1039,18 +1100,21 @@ export class MappingService {
|
|
|
1039
1100
|
matchKind: "exact",
|
|
1040
1101
|
confidence: 1
|
|
1041
1102
|
});
|
|
1103
|
+
const limited = limitResolutionCandidates([resolvedCandidate], input.maxCandidates);
|
|
1042
1104
|
return {
|
|
1043
1105
|
querySymbol,
|
|
1044
1106
|
mappingContext,
|
|
1045
1107
|
resolved: true,
|
|
1046
1108
|
status: "resolved",
|
|
1047
1109
|
resolvedSymbol: resolvedCandidate,
|
|
1048
|
-
candidates:
|
|
1110
|
+
candidates: limited.candidates,
|
|
1111
|
+
candidateCount: limited.candidateCount,
|
|
1112
|
+
candidatesTruncated: limited.candidatesTruncated,
|
|
1049
1113
|
warnings: []
|
|
1050
1114
|
};
|
|
1051
1115
|
}
|
|
1052
1116
|
const graph = await this.loadGraph(version, priority);
|
|
1053
|
-
const path = namespacePath(graph
|
|
1117
|
+
const path = namespacePath(graph, sourceMapping, targetMapping);
|
|
1054
1118
|
if (!path) {
|
|
1055
1119
|
return {
|
|
1056
1120
|
querySymbol,
|
|
@@ -1058,6 +1122,7 @@ export class MappingService {
|
|
|
1058
1122
|
resolved: false,
|
|
1059
1123
|
status: "mapping_unavailable",
|
|
1060
1124
|
candidates: [],
|
|
1125
|
+
candidateCount: 0,
|
|
1061
1126
|
warnings: [
|
|
1062
1127
|
`No mapping path is available for ${sourceMapping} -> ${targetMapping} on version "${version}".`
|
|
1063
1128
|
]
|
|
@@ -1068,6 +1133,7 @@ export class MappingService {
|
|
|
1068
1133
|
.mapCandidatesAlongPath(graph, path, queryRecord)
|
|
1069
1134
|
.filter((candidate) => candidate.kind === "method");
|
|
1070
1135
|
const candidates = rawCandidates.map(toResolutionCandidate);
|
|
1136
|
+
const limitedCandidates = limitResolutionCandidates(candidates, input.maxCandidates);
|
|
1071
1137
|
const strictCandidates = rawCandidates.filter((candidate) => candidate.descriptor === descriptor);
|
|
1072
1138
|
if (strictCandidates.length === 1) {
|
|
1073
1139
|
const resolved = toResolutionCandidate(strictCandidates[0]);
|
|
@@ -1077,7 +1143,9 @@ export class MappingService {
|
|
|
1077
1143
|
resolved: true,
|
|
1078
1144
|
status: "resolved",
|
|
1079
1145
|
resolvedSymbol: resolved,
|
|
1080
|
-
candidates,
|
|
1146
|
+
candidates: limitedCandidates.candidates,
|
|
1147
|
+
candidateCount: limitedCandidates.candidateCount,
|
|
1148
|
+
candidatesTruncated: limitedCandidates.candidatesTruncated,
|
|
1081
1149
|
warnings,
|
|
1082
1150
|
provenance: this.provenanceForPath(graph, path)
|
|
1083
1151
|
};
|
|
@@ -1089,7 +1157,9 @@ export class MappingService {
|
|
|
1089
1157
|
mappingContext,
|
|
1090
1158
|
resolved: false,
|
|
1091
1159
|
status: "ambiguous",
|
|
1092
|
-
candidates,
|
|
1160
|
+
candidates: limitedCandidates.candidates,
|
|
1161
|
+
candidateCount: limitedCandidates.candidateCount,
|
|
1162
|
+
candidatesTruncated: limitedCandidates.candidatesTruncated,
|
|
1093
1163
|
warnings,
|
|
1094
1164
|
provenance: this.provenanceForPath(graph, path)
|
|
1095
1165
|
};
|
|
@@ -1101,7 +1171,9 @@ export class MappingService {
|
|
|
1101
1171
|
mappingContext,
|
|
1102
1172
|
resolved: false,
|
|
1103
1173
|
status: "mapping_unavailable",
|
|
1104
|
-
candidates,
|
|
1174
|
+
candidates: limitedCandidates.candidates,
|
|
1175
|
+
candidateCount: limitedCandidates.candidateCount,
|
|
1176
|
+
candidatesTruncated: limitedCandidates.candidatesTruncated,
|
|
1105
1177
|
warnings,
|
|
1106
1178
|
provenance: this.provenanceForPath(graph, path)
|
|
1107
1179
|
};
|
|
@@ -1111,7 +1183,9 @@ export class MappingService {
|
|
|
1111
1183
|
mappingContext,
|
|
1112
1184
|
resolved: false,
|
|
1113
1185
|
status: "not_found",
|
|
1114
|
-
candidates,
|
|
1186
|
+
candidates: limitedCandidates.candidates,
|
|
1187
|
+
candidateCount: limitedCandidates.candidateCount,
|
|
1188
|
+
candidatesTruncated: limitedCandidates.candidatesTruncated,
|
|
1115
1189
|
warnings,
|
|
1116
1190
|
provenance: this.provenanceForPath(graph, path)
|
|
1117
1191
|
};
|
|
@@ -1143,6 +1217,19 @@ export class MappingService {
|
|
|
1143
1217
|
const graph = await this.loadGraph(version, priority);
|
|
1144
1218
|
const warnings = [...graph.warnings];
|
|
1145
1219
|
const includeKinds = normalizeIncludedKinds(input.includeKinds);
|
|
1220
|
+
const pathCache = new Map();
|
|
1221
|
+
const resolvePath = (sourceMapping, targetMapping) => {
|
|
1222
|
+
if (sourceMapping === targetMapping) {
|
|
1223
|
+
return [sourceMapping];
|
|
1224
|
+
}
|
|
1225
|
+
const key = pairKey(sourceMapping, targetMapping);
|
|
1226
|
+
if (pathCache.has(key)) {
|
|
1227
|
+
return pathCache.get(key);
|
|
1228
|
+
}
|
|
1229
|
+
const path = namespacePath(graph, sourceMapping, targetMapping);
|
|
1230
|
+
pathCache.set(key, path);
|
|
1231
|
+
return path;
|
|
1232
|
+
};
|
|
1146
1233
|
const classByMapping = {
|
|
1147
1234
|
[classNameMapping]: createClassSymbolRecord(className)
|
|
1148
1235
|
};
|
|
@@ -1150,7 +1237,7 @@ export class MappingService {
|
|
|
1150
1237
|
if (mapping === classNameMapping) {
|
|
1151
1238
|
continue;
|
|
1152
1239
|
}
|
|
1153
|
-
const mapped = this.mapRecordBetweenMappings(graph, classNameMapping, mapping, classByMapping[classNameMapping]);
|
|
1240
|
+
const mapped = this.mapRecordBetweenMappings(graph, classNameMapping, mapping, classByMapping[classNameMapping], resolvePath(classNameMapping, mapping));
|
|
1154
1241
|
if (mapped.length > 1) {
|
|
1155
1242
|
const competing = mapped.slice(0, 5).map((c) => c.symbol);
|
|
1156
1243
|
warnings.push(`Class identity mapping to ${mapping} is ambiguous for "${className}": competing=[${competing.join(", ")}].`);
|
|
@@ -1159,7 +1246,7 @@ export class MappingService {
|
|
|
1159
1246
|
classByMapping[mapping] = mapped[0];
|
|
1160
1247
|
}
|
|
1161
1248
|
}
|
|
1162
|
-
const baseMapping = classByMapping.
|
|
1249
|
+
const baseMapping = classByMapping.obfuscated ? "obfuscated" : classNameMapping;
|
|
1163
1250
|
const baseClass = classByMapping[baseMapping];
|
|
1164
1251
|
if (!baseClass) {
|
|
1165
1252
|
return {
|
|
@@ -1167,12 +1254,13 @@ export class MappingService {
|
|
|
1167
1254
|
className,
|
|
1168
1255
|
classNameMapping,
|
|
1169
1256
|
classIdentity: {
|
|
1170
|
-
|
|
1257
|
+
obfuscated: classByMapping.obfuscated?.symbol,
|
|
1171
1258
|
mojang: classByMapping.mojang?.symbol,
|
|
1172
1259
|
intermediary: classByMapping.intermediary?.symbol,
|
|
1173
1260
|
yarn: classByMapping.yarn?.symbol
|
|
1174
1261
|
},
|
|
1175
1262
|
rows: [],
|
|
1263
|
+
rowCount: 0,
|
|
1176
1264
|
warnings
|
|
1177
1265
|
};
|
|
1178
1266
|
}
|
|
@@ -1226,7 +1314,7 @@ export class MappingService {
|
|
|
1226
1314
|
resolved = baseRecord;
|
|
1227
1315
|
}
|
|
1228
1316
|
else {
|
|
1229
|
-
const mapped = this.mapRecordBetweenMappings(graph, baseMapping, mapping, baseRecord);
|
|
1317
|
+
const mapped = this.mapRecordBetweenMappings(graph, baseMapping, mapping, baseRecord, resolvePath(baseMapping, mapping));
|
|
1230
1318
|
let filtered = mapped;
|
|
1231
1319
|
if (baseRecord.kind !== "class" && classIdentity) {
|
|
1232
1320
|
filtered = filtered.filter((candidate) => candidate.owner === classIdentity.symbol);
|
|
@@ -1255,23 +1343,28 @@ export class MappingService {
|
|
|
1255
1343
|
};
|
|
1256
1344
|
row[mapping] = entry;
|
|
1257
1345
|
}
|
|
1258
|
-
row.completeness = Boolean(row.
|
|
1346
|
+
row.completeness = Boolean(row.obfuscated && row.mojang && row.intermediary && row.yarn);
|
|
1259
1347
|
rows.push(row);
|
|
1260
1348
|
if (rowHadAmbiguity) {
|
|
1261
1349
|
ambiguousRowCount += 1;
|
|
1262
1350
|
}
|
|
1263
1351
|
}
|
|
1352
|
+
const rowCount = rows.length;
|
|
1353
|
+
const rowLimit = clampRowLimit(input.maxRows);
|
|
1354
|
+
const limitedRows = rowLimit != null && rowCount > rowLimit ? rows.slice(0, rowLimit) : rows;
|
|
1264
1355
|
return {
|
|
1265
1356
|
version,
|
|
1266
1357
|
className,
|
|
1267
1358
|
classNameMapping,
|
|
1268
1359
|
classIdentity: {
|
|
1269
|
-
|
|
1360
|
+
obfuscated: classByMapping.obfuscated?.symbol,
|
|
1270
1361
|
mojang: classByMapping.mojang?.symbol,
|
|
1271
1362
|
intermediary: classByMapping.intermediary?.symbol,
|
|
1272
1363
|
yarn: classByMapping.yarn?.symbol
|
|
1273
1364
|
},
|
|
1274
|
-
rows,
|
|
1365
|
+
rows: limitedRows,
|
|
1366
|
+
rowCount,
|
|
1367
|
+
rowsTruncated: limitedRows.length < rowCount ? true : undefined,
|
|
1275
1368
|
warnings,
|
|
1276
1369
|
ambiguousRowCount: ambiguousRowCount > 0 ? ambiguousRowCount : undefined
|
|
1277
1370
|
};
|
|
@@ -1352,18 +1445,22 @@ export class MappingService {
|
|
|
1352
1445
|
resolved: false,
|
|
1353
1446
|
status: "mapping_unavailable",
|
|
1354
1447
|
candidates: [],
|
|
1448
|
+
candidateCount: 0,
|
|
1355
1449
|
warnings
|
|
1356
1450
|
};
|
|
1357
1451
|
}
|
|
1358
1452
|
const buildOutput = (querySymbol, matched, status) => {
|
|
1359
1453
|
const candidates = matched.map((record) => toResolutionCandidate(toLookupCandidate(record)));
|
|
1454
|
+
const limitedCandidates = limitResolutionCandidates(candidates, input.maxCandidates);
|
|
1360
1455
|
return {
|
|
1361
1456
|
querySymbol,
|
|
1362
1457
|
mappingContext,
|
|
1363
1458
|
resolved: status === "resolved",
|
|
1364
1459
|
status,
|
|
1365
1460
|
resolvedSymbol: status === "resolved" ? candidates[0] : undefined,
|
|
1366
|
-
candidates,
|
|
1461
|
+
candidates: limitedCandidates.candidates,
|
|
1462
|
+
candidateCount: limitedCandidates.candidateCount,
|
|
1463
|
+
candidatesTruncated: limitedCandidates.candidatesTruncated,
|
|
1367
1464
|
warnings
|
|
1368
1465
|
};
|
|
1369
1466
|
};
|
|
@@ -1414,11 +1511,11 @@ export class MappingService {
|
|
|
1414
1511
|
}
|
|
1415
1512
|
return buildOutput(querySymbol, [], "not_found");
|
|
1416
1513
|
}
|
|
1417
|
-
mapRecordBetweenMappings(graph, sourceMapping, targetMapping, record) {
|
|
1514
|
+
mapRecordBetweenMappings(graph, sourceMapping, targetMapping, record, resolvedPath) {
|
|
1418
1515
|
if (sourceMapping === targetMapping) {
|
|
1419
1516
|
return [record];
|
|
1420
1517
|
}
|
|
1421
|
-
const path = namespacePath(graph
|
|
1518
|
+
const path = resolvedPath ?? namespacePath(graph, sourceMapping, targetMapping);
|
|
1422
1519
|
if (!path) {
|
|
1423
1520
|
return [];
|
|
1424
1521
|
}
|
|
@@ -1568,16 +1665,16 @@ export class MappingService {
|
|
|
1568
1665
|
if (!tinyAvailable) {
|
|
1569
1666
|
degradations.push("No intermediary/yarn tiny mappings were found for this version.");
|
|
1570
1667
|
}
|
|
1571
|
-
// Check if member remap path exists (requestedMapping →
|
|
1668
|
+
// Check if member remap path exists (requestedMapping → obfuscated)
|
|
1572
1669
|
let memberRemapAvailable = false;
|
|
1573
|
-
if (input.requestedMapping === "
|
|
1670
|
+
if (input.requestedMapping === "obfuscated") {
|
|
1574
1671
|
memberRemapAvailable = true;
|
|
1575
1672
|
}
|
|
1576
1673
|
else {
|
|
1577
|
-
const path = namespacePath(graph
|
|
1674
|
+
const path = namespacePath(graph, input.requestedMapping, "obfuscated");
|
|
1578
1675
|
memberRemapAvailable = path != null && path.length > 1;
|
|
1579
1676
|
if (!memberRemapAvailable) {
|
|
1580
|
-
degradations.push(`No mapping path from ${input.requestedMapping} to
|
|
1677
|
+
degradations.push(`No mapping path from ${input.requestedMapping} to obfuscated; member remap will fail.`);
|
|
1581
1678
|
}
|
|
1582
1679
|
}
|
|
1583
1680
|
return {
|
|
@@ -1617,8 +1714,11 @@ export class MappingService {
|
|
|
1617
1714
|
version,
|
|
1618
1715
|
priority,
|
|
1619
1716
|
pairs: new Map(),
|
|
1717
|
+
adjacency: new Map(),
|
|
1718
|
+
pathCache: new Map(),
|
|
1719
|
+
recordsByTarget: new Map(),
|
|
1620
1720
|
warnings: [
|
|
1621
|
-
`Version ${version} is unobfuscated; mapping graph is empty
|
|
1721
|
+
`Version ${version} is unobfuscated; mapping graph is empty because the runtime already uses deobfuscated names.`
|
|
1622
1722
|
]
|
|
1623
1723
|
};
|
|
1624
1724
|
}
|
|
@@ -1626,27 +1726,38 @@ export class MappingService {
|
|
|
1626
1726
|
version,
|
|
1627
1727
|
priority,
|
|
1628
1728
|
pairs: new Map(),
|
|
1729
|
+
adjacency: new Map(),
|
|
1730
|
+
pathCache: new Map(),
|
|
1731
|
+
recordsByTarget: new Map(),
|
|
1629
1732
|
warnings: []
|
|
1630
1733
|
};
|
|
1631
1734
|
const mojangLoad = await this.loadMojangPairs(version);
|
|
1632
1735
|
graph.warnings.push(...mojangLoad.warnings);
|
|
1633
1736
|
this.mergePairs(graph.pairs, mojangLoad.pairs, "mojang-client-mappings", mojangLoad.mappingArtifact);
|
|
1634
1737
|
let tinyLoaded = false;
|
|
1738
|
+
const deferredTinyWarnings = [];
|
|
1635
1739
|
for (const source of mappingSourceOrder(priority)) {
|
|
1636
1740
|
const tinyLoad = source === "loom-cache"
|
|
1637
1741
|
? await this.loadTinyPairsFromLoom(version)
|
|
1638
1742
|
: await this.loadTinyPairsFromMaven(version);
|
|
1639
|
-
graph.warnings.push(...tinyLoad.warnings);
|
|
1640
1743
|
if (tinyLoad.pairs.size === 0) {
|
|
1744
|
+
deferredTinyWarnings.push(...tinyLoad.warnings);
|
|
1641
1745
|
continue;
|
|
1642
1746
|
}
|
|
1643
1747
|
tinyLoaded = true;
|
|
1644
1748
|
this.mergePairs(graph.pairs, tinyLoad.pairs, source, tinyLoad.mappingArtifact);
|
|
1749
|
+
graph.warnings.push(...tinyLoad.warnings);
|
|
1750
|
+
if (deferredTinyWarnings.length > 0) {
|
|
1751
|
+
graph.warnings.push(`Used ${source === "maven" ? "Maven" : "Loom cache"} tiny mappings for "${version}" after an earlier source lookup returned no data.`);
|
|
1752
|
+
}
|
|
1645
1753
|
break;
|
|
1646
1754
|
}
|
|
1647
1755
|
if (!tinyLoaded) {
|
|
1756
|
+
graph.warnings.push(...deferredTinyWarnings);
|
|
1648
1757
|
graph.warnings.push("No intermediary/yarn tiny mappings were found for this version.");
|
|
1649
1758
|
}
|
|
1759
|
+
graph.adjacency = buildAdjacency(graph.pairs);
|
|
1760
|
+
graph.recordsByTarget = buildTargetRecordIndex(graph.pairs);
|
|
1650
1761
|
return graph;
|
|
1651
1762
|
}
|
|
1652
1763
|
mergePairs(target, source, pairSource, mappingArtifact) {
|
|
@@ -1769,31 +1880,37 @@ export class MappingService {
|
|
|
1769
1880
|
async loadTinyPairsFromMaven(version) {
|
|
1770
1881
|
const warnings = [];
|
|
1771
1882
|
const merged = new Map();
|
|
1772
|
-
const attemptedArtifacts = [];
|
|
1773
1883
|
const repos = this.config.sourceRepos;
|
|
1774
1884
|
const intermediaryUrls = [];
|
|
1775
1885
|
const yarnUrls = [];
|
|
1776
|
-
|
|
1777
|
-
|
|
1886
|
+
const repoBases = repos.map((repo) => repo.replace(/\/+$/, ""));
|
|
1887
|
+
const yarnCoordinatesByRepo = await Promise.all(repoBases.map(async (base) => ({
|
|
1888
|
+
base,
|
|
1889
|
+
yarnCoordinates: await this.fetchYarnCoordinates(base, version)
|
|
1890
|
+
})));
|
|
1891
|
+
for (const { base, yarnCoordinates } of yarnCoordinatesByRepo) {
|
|
1778
1892
|
intermediaryUrls.push(`${base}/net/fabricmc/intermediary/${version}/intermediary-${version}-v2.jar`, `${base}/net/fabricmc/intermediary/${version}/intermediary-${version}.jar`);
|
|
1779
|
-
const yarnCoordinates = await this.fetchYarnCoordinates(base, version);
|
|
1780
1893
|
for (const coordinate of yarnCoordinates) {
|
|
1781
1894
|
yarnUrls.push(`${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}-v2.jar`, `${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}.jar`);
|
|
1782
1895
|
}
|
|
1783
1896
|
}
|
|
1784
1897
|
const allUrls = [...intermediaryUrls, ...yarnUrls];
|
|
1785
|
-
|
|
1786
|
-
attemptedArtifacts.push(url);
|
|
1898
|
+
const parsedResults = await Promise.allSettled(allUrls.map(async (url) => {
|
|
1787
1899
|
const downloaded = await downloadToCache(url, defaultDownloadPath(this.config.cacheDir, url), {
|
|
1788
1900
|
fetchFn: this.fetchFn,
|
|
1789
1901
|
retries: this.config.fetchRetries,
|
|
1790
1902
|
timeoutMs: this.config.fetchTimeoutMs
|
|
1791
1903
|
});
|
|
1792
1904
|
if (!downloaded.ok || !downloaded.path) {
|
|
1905
|
+
return undefined;
|
|
1906
|
+
}
|
|
1907
|
+
return this.parseTinyFromJar(downloaded.path);
|
|
1908
|
+
}));
|
|
1909
|
+
for (const result of parsedResults) {
|
|
1910
|
+
if (result.status !== "fulfilled" || !result.value) {
|
|
1793
1911
|
continue;
|
|
1794
1912
|
}
|
|
1795
|
-
const
|
|
1796
|
-
for (const [key, index] of parsed.entries()) {
|
|
1913
|
+
for (const [key, index] of result.value.entries()) {
|
|
1797
1914
|
const existing = merged.get(key);
|
|
1798
1915
|
if (!existing) {
|
|
1799
1916
|
merged.set(key, index);
|
|
@@ -1809,7 +1926,7 @@ export class MappingService {
|
|
|
1809
1926
|
return {
|
|
1810
1927
|
pairs: merged,
|
|
1811
1928
|
warnings,
|
|
1812
|
-
mappingArtifact:
|
|
1929
|
+
mappingArtifact: allUrls[0] ?? "maven:none"
|
|
1813
1930
|
};
|
|
1814
1931
|
}
|
|
1815
1932
|
async parseTinyFromJar(jarPath) {
|