@adhisang/minecraft-modding-mcp 4.0.0 → 4.1.1
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 +61 -0
- package/README.md +40 -23
- package/dist/build-suggested-call.d.ts +29 -0
- package/dist/build-suggested-call.js +58 -0
- package/dist/cache-registry.d.ts +3 -1
- package/dist/cache-registry.js +50 -6
- package/dist/entry-tools/analyze-symbol-service.d.ts +16 -16
- package/dist/entry-tools/batch-class-members-service.d.ts +34 -0
- package/dist/entry-tools/batch-class-members-service.js +97 -0
- package/dist/entry-tools/batch-class-source-service.d.ts +37 -0
- package/dist/entry-tools/batch-class-source-service.js +100 -0
- package/dist/entry-tools/batch-mappings-service.d.ts +36 -0
- package/dist/entry-tools/batch-mappings-service.js +66 -0
- package/dist/entry-tools/batch-runner.d.ts +72 -0
- package/dist/entry-tools/batch-runner.js +90 -0
- package/dist/entry-tools/batch-symbol-exists-service.d.ts +46 -0
- package/dist/entry-tools/batch-symbol-exists-service.js +113 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +6 -6
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/artifact.js +83 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.d.ts +6 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-members.js +80 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-overview.js +248 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/class-source.js +60 -0
- package/dist/entry-tools/inspect-minecraft/handlers/file.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/file.js +54 -0
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/list-files.js +100 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.d.ts +5 -0
- package/dist/entry-tools/inspect-minecraft/handlers/search.js +155 -0
- package/dist/entry-tools/inspect-minecraft/handlers/versions.d.ts +6 -0
- package/dist/entry-tools/inspect-minecraft/handlers/versions.js +49 -0
- package/dist/entry-tools/inspect-minecraft/internal.d.ts +1042 -0
- package/dist/entry-tools/inspect-minecraft/internal.js +448 -0
- package/dist/entry-tools/inspect-minecraft-service.d.ts +193 -308
- package/dist/entry-tools/inspect-minecraft-service.js +20 -1244
- package/dist/entry-tools/manage-cache-service.d.ts +16 -16
- package/dist/entry-tools/validate-project/cases/access-transformer.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/access-transformer.js +106 -0
- package/dist/entry-tools/validate-project/cases/access-widener.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/access-widener.js +86 -0
- package/dist/entry-tools/validate-project/cases/mixin.d.ts +6 -0
- package/dist/entry-tools/validate-project/cases/mixin.js +90 -0
- package/dist/entry-tools/validate-project/cases/project-summary.d.ts +102 -0
- package/dist/entry-tools/validate-project/cases/project-summary.js +415 -0
- package/dist/entry-tools/validate-project/internal.d.ts +142 -0
- package/dist/entry-tools/validate-project/internal.js +303 -0
- package/dist/entry-tools/validate-project-service.d.ts +67 -47
- package/dist/entry-tools/validate-project-service.js +13 -563
- package/dist/entry-tools/verify-mixin-target-service.d.ts +133 -0
- package/dist/entry-tools/verify-mixin-target-service.js +323 -0
- package/dist/error-mapping.d.ts +40 -0
- package/dist/error-mapping.js +139 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +147 -1354
- package/dist/mapping/internal-types.d.ts +54 -0
- package/dist/mapping/internal-types.js +14 -0
- package/dist/mapping/loaders/mojang.d.ts +2 -0
- package/dist/mapping/loaders/mojang.js +64 -0
- package/dist/mapping/loaders/tiny-loom.d.ts +2 -0
- package/dist/mapping/loaders/tiny-loom.js +73 -0
- package/dist/mapping/loaders/tiny-maven.d.ts +2 -0
- package/dist/mapping/loaders/tiny-maven.js +104 -0
- package/dist/mapping/loaders/types.d.ts +14 -0
- package/dist/mapping/loaders/types.js +2 -0
- package/dist/mapping/lookup.d.ts +52 -0
- package/dist/mapping/lookup.js +496 -0
- package/dist/mapping/parsers/normalize.d.ts +10 -0
- package/dist/mapping/parsers/normalize.js +52 -0
- package/dist/mapping/parsers/proguard.d.ts +20 -0
- package/dist/mapping/parsers/proguard.js +138 -0
- package/dist/mapping/parsers/symbol-records.d.ts +27 -0
- package/dist/mapping/parsers/symbol-records.js +216 -0
- package/dist/mapping/parsers/tiny.d.ts +9 -0
- package/dist/mapping/parsers/tiny.js +96 -0
- package/dist/mapping/types.d.ts +147 -0
- package/dist/mapping/types.js +2 -0
- package/dist/mapping-pipeline-service.js +3 -2
- package/dist/mapping-service.d.ts +8 -145
- package/dist/mapping-service.js +30 -1207
- package/dist/mixin/access-validators.d.ts +9 -0
- package/dist/mixin/access-validators.js +257 -0
- package/dist/mixin/annotation-validators.d.ts +5 -0
- package/dist/mixin/annotation-validators.js +162 -0
- package/dist/mixin/helpers.d.ts +28 -0
- package/dist/mixin/helpers.js +315 -0
- package/dist/mixin/parsed-validator.d.ts +8 -0
- package/dist/mixin/parsed-validator.js +337 -0
- package/dist/mixin/types.d.ts +208 -0
- package/dist/mixin/types.js +28 -0
- package/dist/mixin-validator.d.ts +9 -201
- package/dist/mixin-validator.js +8 -1020
- package/dist/source/access-validate.d.ts +4 -0
- package/dist/source/access-validate.js +254 -0
- package/dist/source/artifact-resolver.d.ts +111 -0
- package/dist/source/artifact-resolver.js +1271 -0
- package/dist/source/cache-metrics.d.ts +26 -0
- package/dist/source/cache-metrics.js +172 -0
- package/dist/source/class-source/members-builder.d.ts +34 -0
- package/dist/source/class-source/members-builder.js +46 -0
- package/dist/source/class-source/snippet-builder.d.ts +19 -0
- package/dist/source/class-source/snippet-builder.js +46 -0
- package/dist/source/class-source-helpers.d.ts +34 -0
- package/dist/source/class-source-helpers.js +140 -0
- package/dist/source/class-source.d.ts +42 -0
- package/dist/source/class-source.js +883 -0
- package/dist/source/descriptor-utils.d.ts +6 -0
- package/dist/source/descriptor-utils.js +37 -0
- package/dist/source/file-access.d.ts +4 -0
- package/dist/source/file-access.js +102 -0
- package/dist/source/indexer.d.ts +82 -0
- package/dist/source/indexer.js +522 -0
- package/dist/source/lifecycle/diff-utils.d.ts +9 -0
- package/dist/source/lifecycle/diff-utils.js +107 -0
- package/dist/source/lifecycle/diff.d.ts +2 -0
- package/dist/source/lifecycle/diff.js +265 -0
- package/dist/source/lifecycle/mapping-helpers.d.ts +22 -0
- package/dist/source/lifecycle/mapping-helpers.js +327 -0
- package/dist/source/lifecycle/runtime-check.d.ts +2 -0
- package/dist/source/lifecycle/runtime-check.js +142 -0
- package/dist/source/lifecycle/trace.d.ts +2 -0
- package/dist/source/lifecycle/trace.js +231 -0
- package/dist/source/lifecycle.d.ts +4 -0
- package/dist/source/lifecycle.js +5 -0
- package/dist/source/search.d.ts +51 -0
- package/dist/source/search.js +676 -0
- package/dist/source/shared-utils.d.ts +6 -0
- package/dist/source/shared-utils.js +55 -0
- package/dist/source/state.d.ts +26 -0
- package/dist/source/state.js +24 -0
- package/dist/source/symbol-resolver.d.ts +3 -0
- package/dist/source/symbol-resolver.js +212 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.d.ts +3 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.js +41 -0
- package/dist/source/validate-mixin/pipeline/parse.d.ts +2 -0
- package/dist/source/validate-mixin/pipeline/parse.js +10 -0
- package/dist/source/validate-mixin/pipeline/resolve.d.ts +3 -0
- package/dist/source/validate-mixin/pipeline/resolve.js +78 -0
- package/dist/source/validate-mixin/pipeline/target-lookup.d.ts +6 -0
- package/dist/source/validate-mixin/pipeline/target-lookup.js +260 -0
- package/dist/source/validate-mixin/pipeline-context.d.ts +72 -0
- package/dist/source/validate-mixin/pipeline-context.js +93 -0
- package/dist/source/validate-mixin.d.ts +22 -0
- package/dist/source/validate-mixin.js +799 -0
- package/dist/source/workspace-target.d.ts +18 -0
- package/dist/source/workspace-target.js +305 -0
- package/dist/source-resolver.d.ts +1 -0
- package/dist/source-resolver.js +1 -1
- package/dist/source-service.d.ts +164 -170
- package/dist/source-service.js +70 -6116
- package/dist/stage-emitter.d.ts +13 -0
- package/dist/stage-emitter.js +30 -0
- package/dist/stdio-supervisor.d.ts +61 -0
- package/dist/stdio-supervisor.js +326 -9
- package/dist/tool-contract-manifest.d.ts +1 -1
- package/dist/tool-contract-manifest.js +23 -6
- package/dist/tool-guidance.d.ts +82 -0
- package/dist/tool-guidance.js +734 -0
- package/dist/tool-schema-registry.d.ts +16 -0
- package/dist/tool-schema-registry.js +37 -0
- package/dist/tool-schemas.d.ts +3518 -0
- package/dist/tool-schemas.js +813 -0
- package/dist/types.d.ts +36 -0
- package/dist/version-service.js +7 -6
- package/dist/workspace-context-cache.d.ts +32 -0
- package/dist/workspace-context-cache.js +66 -0
- package/dist/workspace-mapping-service.d.ts +16 -0
- package/dist/workspace-mapping-service.js +173 -1
- package/docs/README-ja.md +416 -0
- package/docs/examples.md +483 -0
- package/docs/tool-reference.md +462 -0
- package/package.json +17 -4
package/dist/mapping-service.js
CHANGED
|
@@ -1,1003 +1,25 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { mkdir,
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import
|
|
4
|
+
import { buildSuggestedCall } from "./build-suggested-call.js";
|
|
5
5
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
6
|
-
import { buildVersionSourceSearchRoots, normalizeOptionalProjectPath } from "./gradle-paths.js";
|
|
7
6
|
import { defaultDownloadPath, downloadToCache } from "./repo-downloader.js";
|
|
8
7
|
import { collectMatchedJarEntriesAsUtf8 } from "./source-jar-reader.js";
|
|
9
8
|
import { VersionService, isUnobfuscatedVersion } from "./version-service.js";
|
|
9
|
+
import { buildSymbolKey, createClassSymbolRecord, mergeDirectionIndexes, normalizeMappedSymbolOutput } from "./mapping/parsers/symbol-records.js";
|
|
10
|
+
import { buildAdjacency, buildTargetRecordIndex, pairKey } from "./mapping/parsers/normalize.js";
|
|
11
|
+
import { MATCH_RANK, MAX_CANDIDATES } from "./mapping/internal-types.js";
|
|
12
|
+
import { applyDisambiguationHints, clampRowLimit, collectTargetRecords, effectiveLoomSearchProjectPath, inferAmbiguityReasons, invalidInputError, limitResolutionCandidates, lookupCandidates, mappingPriorityFromInput, mappingSourceOrder, namespacePath, normalizeIncludedKinds, normalizeQuerySymbol, pathToTransformChain, pathUsesSource, projectLookupCandidateDescriptor, requiresOnlyObfuscatedMojangGraph, toLookupCandidate, toResolutionCandidate } from "./mapping/lookup.js";
|
|
13
|
+
import { loadMojangPairs } from "./mapping/loaders/mojang.js";
|
|
14
|
+
import { loadTinyPairsFromLoom } from "./mapping/loaders/tiny-loom.js";
|
|
15
|
+
import { loadTinyPairsFromMaven } from "./mapping/loaders/tiny-maven.js";
|
|
10
16
|
const SUPPORTED_MAPPINGS = new Set([
|
|
11
17
|
"obfuscated",
|
|
12
18
|
"mojang",
|
|
13
19
|
"intermediary",
|
|
14
20
|
"yarn"
|
|
15
21
|
]);
|
|
16
|
-
|
|
17
|
-
exact: 3,
|
|
18
|
-
normalized: 2,
|
|
19
|
-
"simple-name": 1
|
|
20
|
-
};
|
|
21
|
-
const DESCRIPTOR_FALLBACK_CONFIDENCE = 0.85;
|
|
22
|
-
const MAX_CANDIDATES = 200;
|
|
23
|
-
const GLOB_SPECIAL_CHARS = /[\\!*+?()[\]{}@|]/g;
|
|
24
|
-
function createDirectionIndex() {
|
|
25
|
-
return {
|
|
26
|
-
exact: new Map(),
|
|
27
|
-
normalized: new Map(),
|
|
28
|
-
simple: new Map(),
|
|
29
|
-
records: new Map()
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
function addToSetMap(map, key, value) {
|
|
33
|
-
const normalizedKey = key.trim();
|
|
34
|
-
if (!normalizedKey) {
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const existing = map.get(normalizedKey) ?? new Set();
|
|
38
|
-
existing.add(value);
|
|
39
|
-
map.set(normalizedKey, existing);
|
|
40
|
-
}
|
|
41
|
-
function normalizedVariants(symbol) {
|
|
42
|
-
const variants = [symbol];
|
|
43
|
-
let dotted;
|
|
44
|
-
if (symbol.includes("/")) {
|
|
45
|
-
dotted = symbol.replace(/\//g, ".");
|
|
46
|
-
if (dotted !== symbol) {
|
|
47
|
-
variants.push(dotted);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (symbol.includes(".")) {
|
|
51
|
-
const slashed = symbol.replace(/\./g, "/");
|
|
52
|
-
if (slashed !== symbol && slashed !== dotted) {
|
|
53
|
-
variants.push(slashed);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return variants;
|
|
57
|
-
}
|
|
58
|
-
function simpleName(symbol) {
|
|
59
|
-
const trimmed = symbol.trim();
|
|
60
|
-
if (!trimmed) {
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
|
-
const withoutDescriptor = trimmed.includes("(") ? trimmed.slice(0, trimmed.indexOf("(")) : trimmed;
|
|
64
|
-
const base = withoutDescriptor.split(/[./]/).at(-1)?.trim();
|
|
65
|
-
return base || undefined;
|
|
66
|
-
}
|
|
67
|
-
function normalizeMappedSymbolOutput(symbol) {
|
|
68
|
-
return symbol.replace(/\//g, ".");
|
|
69
|
-
}
|
|
70
|
-
function splitOwnerAndName(symbol) {
|
|
71
|
-
const trimmed = symbol.trim();
|
|
72
|
-
const separatorIndex = Math.max(trimmed.lastIndexOf("."), trimmed.lastIndexOf("/"));
|
|
73
|
-
if (separatorIndex <= 0 || separatorIndex >= trimmed.length - 1) {
|
|
74
|
-
return undefined;
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
owner: trimmed.slice(0, separatorIndex),
|
|
78
|
-
name: trimmed.slice(separatorIndex + 1)
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function stripLineInfo(input) {
|
|
82
|
-
let value = input.trim();
|
|
83
|
-
while (/^\d+:\d+:/.test(value)) {
|
|
84
|
-
value = value.replace(/^\d+:\d+:/, "");
|
|
85
|
-
}
|
|
86
|
-
return value.replace(/:\d+:\d+$/, "").trim();
|
|
87
|
-
}
|
|
88
|
-
function parseMethodName(value) {
|
|
89
|
-
const match = /^(.+?)\s+([^\s(]+)\((.*)\)$/.exec(value);
|
|
90
|
-
if (!match) {
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
return match[2]?.trim() || undefined;
|
|
94
|
-
}
|
|
95
|
-
function parseFieldName(value) {
|
|
96
|
-
const match = /^(.+?)\s+([^\s]+)$/.exec(value);
|
|
97
|
-
if (!match) {
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
|
-
return match[2]?.trim() || undefined;
|
|
101
|
-
}
|
|
102
|
-
function buildSymbolKey(record) {
|
|
103
|
-
return `${record.kind}|${record.owner ?? ""}|${record.name}|${record.descriptor ?? ""}`;
|
|
104
|
-
}
|
|
105
|
-
function classNameParts(classFqn) {
|
|
106
|
-
const separatorIndex = classFqn.lastIndexOf(".");
|
|
107
|
-
if (separatorIndex <= 0 || separatorIndex >= classFqn.length - 1) {
|
|
108
|
-
return {
|
|
109
|
-
owner: undefined,
|
|
110
|
-
name: classFqn
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
owner: classFqn.slice(0, separatorIndex),
|
|
115
|
-
name: classFqn.slice(separatorIndex + 1)
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function createClassSymbolRecord(className) {
|
|
119
|
-
const symbol = normalizeMappedSymbolOutput(className.trim());
|
|
120
|
-
const parts = classNameParts(symbol);
|
|
121
|
-
return {
|
|
122
|
-
kind: "class",
|
|
123
|
-
symbol,
|
|
124
|
-
owner: parts.owner,
|
|
125
|
-
name: parts.name
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
function createFieldSymbolRecord(owner, fieldName) {
|
|
129
|
-
const normalizedOwner = normalizeMappedSymbolOutput(owner.trim());
|
|
130
|
-
const normalizedName = fieldName.trim();
|
|
131
|
-
return {
|
|
132
|
-
kind: "field",
|
|
133
|
-
symbol: `${normalizedOwner}.${normalizedName}`,
|
|
134
|
-
owner: normalizedOwner,
|
|
135
|
-
name: normalizedName
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
function createMethodSymbolRecord(owner, methodName, descriptor) {
|
|
139
|
-
const normalizedOwner = normalizeMappedSymbolOutput(owner.trim());
|
|
140
|
-
const normalizedName = methodName.trim();
|
|
141
|
-
const normalizedDescriptor = descriptor?.trim() || undefined;
|
|
142
|
-
return {
|
|
143
|
-
kind: "method",
|
|
144
|
-
symbol: `${normalizedOwner}.${normalizedName}${normalizedDescriptor ?? ""}`,
|
|
145
|
-
owner: normalizedOwner,
|
|
146
|
-
name: normalizedName,
|
|
147
|
-
descriptor: normalizedDescriptor
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
function parseInputSymbol(symbol) {
|
|
151
|
-
const trimmed = symbol.trim();
|
|
152
|
-
if (!trimmed || /\s/.test(trimmed)) {
|
|
153
|
-
return undefined;
|
|
154
|
-
}
|
|
155
|
-
const openIndex = trimmed.indexOf("(");
|
|
156
|
-
if (openIndex >= 0) {
|
|
157
|
-
const closeIndex = trimmed.indexOf(")", openIndex);
|
|
158
|
-
if (closeIndex < 0) {
|
|
159
|
-
return undefined;
|
|
160
|
-
}
|
|
161
|
-
const ownerAndMethod = splitOwnerAndName(trimmed.slice(0, openIndex));
|
|
162
|
-
if (!ownerAndMethod) {
|
|
163
|
-
return undefined;
|
|
164
|
-
}
|
|
165
|
-
const descriptor = trimmed.slice(openIndex);
|
|
166
|
-
return createMethodSymbolRecord(ownerAndMethod.owner, ownerAndMethod.name, descriptor);
|
|
167
|
-
}
|
|
168
|
-
const ownerAndName = splitOwnerAndName(trimmed);
|
|
169
|
-
if (!ownerAndName) {
|
|
170
|
-
return createClassSymbolRecord(trimmed);
|
|
171
|
-
}
|
|
172
|
-
if (/^[A-Z$]/.test(ownerAndName.name)) {
|
|
173
|
-
return createClassSymbolRecord(trimmed);
|
|
174
|
-
}
|
|
175
|
-
return createFieldSymbolRecord(ownerAndName.owner, ownerAndName.name);
|
|
176
|
-
}
|
|
177
|
-
function exactLookupKeys(record) {
|
|
178
|
-
const keys = new Set([record.symbol]);
|
|
179
|
-
if (record.kind === "method" && record.owner && record.descriptor) {
|
|
180
|
-
keys.add(`${record.owner}.${record.name}`);
|
|
181
|
-
}
|
|
182
|
-
return [...keys];
|
|
183
|
-
}
|
|
184
|
-
function simpleLookupKeys(record) {
|
|
185
|
-
if (record.kind === "class") {
|
|
186
|
-
return [record.name];
|
|
187
|
-
}
|
|
188
|
-
if (record.kind === "field") {
|
|
189
|
-
return [record.name];
|
|
190
|
-
}
|
|
191
|
-
if (record.descriptor) {
|
|
192
|
-
return [record.name, `${record.name}${record.descriptor}`];
|
|
193
|
-
}
|
|
194
|
-
return [record.name];
|
|
195
|
-
}
|
|
196
|
-
function registerRecord(index, record) {
|
|
197
|
-
const key = buildSymbolKey(record);
|
|
198
|
-
if (!index.records.has(key)) {
|
|
199
|
-
index.records.set(key, record);
|
|
200
|
-
}
|
|
201
|
-
return key;
|
|
202
|
-
}
|
|
203
|
-
function addLookupEntries(index, fromRecord, toRecord) {
|
|
204
|
-
if (!fromRecord.symbol || !toRecord.symbol) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const targetKey = registerRecord(index, toRecord);
|
|
208
|
-
for (const key of exactLookupKeys(fromRecord)) {
|
|
209
|
-
addToSetMap(index.exact, key, targetKey);
|
|
210
|
-
for (const variant of normalizedVariants(key)) {
|
|
211
|
-
if (variant !== key) {
|
|
212
|
-
addToSetMap(index.normalized, variant, targetKey);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
for (const key of simpleLookupKeys(fromRecord)) {
|
|
217
|
-
addToSetMap(index.simple, key, targetKey);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
function mergeDirectionIndexes(target, source) {
|
|
221
|
-
const mergeMap = (targetMap, sourceMap) => {
|
|
222
|
-
for (const [key, values] of sourceMap.entries()) {
|
|
223
|
-
const existing = targetMap.get(key) ?? new Set();
|
|
224
|
-
for (const value of values) {
|
|
225
|
-
existing.add(value);
|
|
226
|
-
}
|
|
227
|
-
targetMap.set(key, existing);
|
|
228
|
-
}
|
|
229
|
-
};
|
|
230
|
-
mergeMap(target.exact, source.exact);
|
|
231
|
-
mergeMap(target.normalized, source.normalized);
|
|
232
|
-
mergeMap(target.simple, source.simple);
|
|
233
|
-
for (const [key, value] of source.records.entries()) {
|
|
234
|
-
if (!target.records.has(key)) {
|
|
235
|
-
target.records.set(key, value);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
function pairKey(sourceMapping, targetMapping) {
|
|
240
|
-
return `${sourceMapping}->${targetMapping}`;
|
|
241
|
-
}
|
|
242
|
-
function parsePairKey(key) {
|
|
243
|
-
const separator = key.indexOf("->");
|
|
244
|
-
const source = separator >= 0 ? key.slice(0, separator) : key;
|
|
245
|
-
const target = separator >= 0 ? key.slice(separator + 2) : "";
|
|
246
|
-
return {
|
|
247
|
-
sourceMapping: source,
|
|
248
|
-
targetMapping: target
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
function buildAdjacency(pairs) {
|
|
252
|
-
const adjacency = new Map();
|
|
253
|
-
for (const key of pairs.keys()) {
|
|
254
|
-
const { sourceMapping, targetMapping } = parsePairKey(key);
|
|
255
|
-
let neighbors = adjacency.get(sourceMapping);
|
|
256
|
-
if (!neighbors) {
|
|
257
|
-
neighbors = new Set();
|
|
258
|
-
adjacency.set(sourceMapping, neighbors);
|
|
259
|
-
}
|
|
260
|
-
neighbors.add(targetMapping);
|
|
261
|
-
}
|
|
262
|
-
return new Map([...adjacency.entries()].map(([mapping, neighbors]) => [mapping, [...neighbors]]));
|
|
263
|
-
}
|
|
264
|
-
function buildTargetRecordIndex(pairs) {
|
|
265
|
-
const recordsByTarget = new Map();
|
|
266
|
-
for (const [key, pair] of pairs.entries()) {
|
|
267
|
-
const { targetMapping } = parsePairKey(key);
|
|
268
|
-
let bucket = recordsByTarget.get(targetMapping);
|
|
269
|
-
if (!bucket) {
|
|
270
|
-
bucket = new Map();
|
|
271
|
-
recordsByTarget.set(targetMapping, bucket);
|
|
272
|
-
}
|
|
273
|
-
for (const record of pair.index.records.values()) {
|
|
274
|
-
bucket.set(buildSymbolKey(record), record);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return new Map([...recordsByTarget.entries()].map(([mapping, records]) => [mapping, [...records.values()]]));
|
|
278
|
-
}
|
|
279
|
-
function ensurePairIndex(indexes, from, to) {
|
|
280
|
-
const key = pairKey(from, to);
|
|
281
|
-
const existing = indexes.get(key);
|
|
282
|
-
if (existing) {
|
|
283
|
-
return existing;
|
|
284
|
-
}
|
|
285
|
-
const created = createDirectionIndex();
|
|
286
|
-
indexes.set(key, created);
|
|
287
|
-
return created;
|
|
288
|
-
}
|
|
289
|
-
/** Map of proguard primitive type names to JVM type characters. */
|
|
290
|
-
const PROGUARD_PRIMITIVES = {
|
|
291
|
-
void: "V", boolean: "Z", byte: "B", char: "C",
|
|
292
|
-
short: "S", int: "I", long: "J", float: "F", double: "D"
|
|
293
|
-
};
|
|
294
|
-
/**
|
|
295
|
-
* Convert a single proguard type (e.g. "int", "net.minecraft.Foo", "int[][]")
|
|
296
|
-
* to JVM notation (e.g. "I", "Lnet/minecraft/Foo;", "[[I").
|
|
297
|
-
* `classLookup` maps mojang class names → obfuscated class names (for the obfuscated descriptor).
|
|
298
|
-
* Pass `undefined` to skip class name translation (for mojang descriptors).
|
|
299
|
-
*/
|
|
300
|
-
function proguardTypeToJvm(type, classLookup) {
|
|
301
|
-
let arrayDepth = 0;
|
|
302
|
-
let base = type;
|
|
303
|
-
while (base.endsWith("[]")) {
|
|
304
|
-
arrayDepth += 1;
|
|
305
|
-
base = base.slice(0, -2);
|
|
306
|
-
}
|
|
307
|
-
const prefix = "[".repeat(arrayDepth);
|
|
308
|
-
const primitive = PROGUARD_PRIMITIVES[base];
|
|
309
|
-
if (primitive) {
|
|
310
|
-
return `${prefix}${primitive}`;
|
|
311
|
-
}
|
|
312
|
-
const translated = classLookup ? (classLookup.get(base) ?? base) : base;
|
|
313
|
-
return `${prefix}L${translated.replace(/\./g, "/")};`;
|
|
314
|
-
}
|
|
315
|
-
/**
|
|
316
|
-
* Parse a proguard method signature (after stripLineInfo) into a JVM descriptor.
|
|
317
|
-
* Input format: "returnType methodName(paramType1,paramType2,...)"
|
|
318
|
-
* Returns `{ name, descriptor }` or `undefined` if parsing fails.
|
|
319
|
-
*/
|
|
320
|
-
function parseProguardMethod(value, classLookup) {
|
|
321
|
-
const match = /^(.+?)\s+([^\s(]+)\((.*)\)$/.exec(value);
|
|
322
|
-
if (!match) {
|
|
323
|
-
return undefined;
|
|
324
|
-
}
|
|
325
|
-
const returnType = match[1].trim();
|
|
326
|
-
const name = match[2].trim();
|
|
327
|
-
const params = match[3].trim();
|
|
328
|
-
if (!name) {
|
|
329
|
-
return undefined;
|
|
330
|
-
}
|
|
331
|
-
const paramParts = params ? params.split(",").map((p) => p.trim()) : [];
|
|
332
|
-
const paramDescriptor = paramParts.map((p) => proguardTypeToJvm(p, classLookup)).join("");
|
|
333
|
-
const returnDescriptor = proguardTypeToJvm(returnType, classLookup);
|
|
334
|
-
return { name, descriptor: `(${paramDescriptor})${returnDescriptor}` };
|
|
335
|
-
}
|
|
336
|
-
function parseClientMappings(text) {
|
|
337
|
-
const obfuscatedToMojang = createDirectionIndex();
|
|
338
|
-
const mojangToObfuscated = createDirectionIndex();
|
|
339
|
-
// Two-pass parsing: first collect class name mappings, then parse members with descriptors.
|
|
340
|
-
const lines = text.split(/\r?\n/);
|
|
341
|
-
// Pass 1: collect class name mappings (mojang → obfuscated)
|
|
342
|
-
const mojangToObfuscatedClass = new Map();
|
|
343
|
-
let classCount = 0;
|
|
344
|
-
for (const rawLine of lines) {
|
|
345
|
-
const line = rawLine.trim();
|
|
346
|
-
if (!line || line.startsWith("#")) {
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
const classMatch = /^(.+?)\s+->\s+(.+):$/.exec(line);
|
|
350
|
-
if (classMatch) {
|
|
351
|
-
const mojangClass = classMatch[1]?.trim() ?? "";
|
|
352
|
-
const obfuscatedClass = classMatch[2]?.trim() ?? "";
|
|
353
|
-
if (mojangClass && obfuscatedClass) {
|
|
354
|
-
mojangToObfuscatedClass.set(mojangClass, obfuscatedClass);
|
|
355
|
-
classCount += 1;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
if (classCount === 0) {
|
|
360
|
-
throw createError({
|
|
361
|
-
code: ERROR_CODES.MAPPING_UNAVAILABLE,
|
|
362
|
-
message: "No class mappings could be parsed from client mappings."
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
// Pass 2: build full index with descriptors
|
|
366
|
-
let currentClass;
|
|
367
|
-
for (const rawLine of lines) {
|
|
368
|
-
const line = rawLine.trim();
|
|
369
|
-
if (!line || line.startsWith("#")) {
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
const classMatch = /^(.+?)\s+->\s+(.+):$/.exec(line);
|
|
373
|
-
if (classMatch) {
|
|
374
|
-
const mojangClass = classMatch[1]?.trim() ?? "";
|
|
375
|
-
const obfuscatedClass = classMatch[2]?.trim() ?? "";
|
|
376
|
-
if (!mojangClass || !obfuscatedClass) {
|
|
377
|
-
currentClass = undefined;
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
currentClass = {
|
|
381
|
-
obfuscated: obfuscatedClass,
|
|
382
|
-
mojang: mojangClass
|
|
383
|
-
};
|
|
384
|
-
addLookupEntries(obfuscatedToMojang, createClassSymbolRecord(obfuscatedClass), createClassSymbolRecord(mojangClass));
|
|
385
|
-
addLookupEntries(mojangToObfuscated, createClassSymbolRecord(mojangClass), createClassSymbolRecord(obfuscatedClass));
|
|
386
|
-
continue;
|
|
387
|
-
}
|
|
388
|
-
if (!currentClass) {
|
|
389
|
-
continue;
|
|
390
|
-
}
|
|
391
|
-
const arrowIndex = line.indexOf(" -> ");
|
|
392
|
-
if (arrowIndex < 0) {
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
const leftRaw = line.slice(0, arrowIndex).trim();
|
|
396
|
-
const rightRaw = line.slice(arrowIndex + 4).trim();
|
|
397
|
-
if (!leftRaw || !rightRaw) {
|
|
398
|
-
continue;
|
|
399
|
-
}
|
|
400
|
-
const mojangMemberSignature = stripLineInfo(leftRaw);
|
|
401
|
-
// Try method parsing with JVM descriptor
|
|
402
|
-
const obfuscatedMethod = parseProguardMethod(mojangMemberSignature, mojangToObfuscatedClass);
|
|
403
|
-
if (obfuscatedMethod) {
|
|
404
|
-
const mojangMethod = parseProguardMethod(mojangMemberSignature, undefined);
|
|
405
|
-
const obfuscatedDescriptor = obfuscatedMethod.descriptor;
|
|
406
|
-
const mojangDescriptor = mojangMethod?.descriptor;
|
|
407
|
-
addLookupEntries(obfuscatedToMojang, createMethodSymbolRecord(currentClass.obfuscated, rightRaw, obfuscatedDescriptor), createMethodSymbolRecord(currentClass.mojang, obfuscatedMethod.name, mojangDescriptor));
|
|
408
|
-
addLookupEntries(mojangToObfuscated, createMethodSymbolRecord(currentClass.mojang, obfuscatedMethod.name, mojangDescriptor), createMethodSymbolRecord(currentClass.obfuscated, rightRaw, obfuscatedDescriptor));
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
const fieldName = parseFieldName(mojangMemberSignature);
|
|
412
|
-
if (!fieldName) {
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
addLookupEntries(obfuscatedToMojang, createFieldSymbolRecord(currentClass.obfuscated, rightRaw), createFieldSymbolRecord(currentClass.mojang, fieldName));
|
|
416
|
-
addLookupEntries(mojangToObfuscated, createFieldSymbolRecord(currentClass.mojang, fieldName), createFieldSymbolRecord(currentClass.obfuscated, rightRaw));
|
|
417
|
-
}
|
|
418
|
-
const result = new Map();
|
|
419
|
-
result.set(pairKey("obfuscated", "mojang"), obfuscatedToMojang);
|
|
420
|
-
result.set(pairKey("mojang", "obfuscated"), mojangToObfuscated);
|
|
421
|
-
return result;
|
|
422
|
-
}
|
|
423
|
-
function normalizeTinyNamespace(namespace) {
|
|
424
|
-
const normalized = namespace.trim().toLowerCase();
|
|
425
|
-
if (normalized === "obfuscated" || normalized === "official") {
|
|
426
|
-
return "obfuscated";
|
|
427
|
-
}
|
|
428
|
-
if (normalized === "mojang") {
|
|
429
|
-
return "mojang";
|
|
430
|
-
}
|
|
431
|
-
if (normalized === "intermediary") {
|
|
432
|
-
return "intermediary";
|
|
433
|
-
}
|
|
434
|
-
if (normalized === "named" || normalized === "yarn") {
|
|
435
|
-
return "yarn";
|
|
436
|
-
}
|
|
437
|
-
return undefined;
|
|
438
|
-
}
|
|
439
|
-
function addPairRecords(target, records) {
|
|
440
|
-
for (const [sourceMapping, sourceRecord] of records.entries()) {
|
|
441
|
-
for (const [targetMapping, targetRecord] of records.entries()) {
|
|
442
|
-
if (sourceMapping === targetMapping) {
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
addLookupEntries(ensurePairIndex(target, sourceMapping, targetMapping), sourceRecord, targetRecord);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
function parseTinyMappings(text) {
|
|
450
|
-
const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
451
|
-
if (lines.length === 0) {
|
|
452
|
-
return new Map();
|
|
453
|
-
}
|
|
454
|
-
const header = lines[0].split("\t");
|
|
455
|
-
if (header.length < 5 || header[0] !== "tiny" || header[1] !== "2") {
|
|
456
|
-
return new Map();
|
|
457
|
-
}
|
|
458
|
-
const namespaceColumns = header.slice(3).map((namespace, index) => ({
|
|
459
|
-
mapping: normalizeTinyNamespace(namespace),
|
|
460
|
-
columnIndex: index + 1
|
|
461
|
-
}));
|
|
462
|
-
const recognized = namespaceColumns.filter((entry) => entry.mapping != null);
|
|
463
|
-
if (recognized.length < 2) {
|
|
464
|
-
return new Map();
|
|
465
|
-
}
|
|
466
|
-
const result = new Map();
|
|
467
|
-
const currentClassNames = new Map();
|
|
468
|
-
for (const line of lines.slice(1)) {
|
|
469
|
-
const columns = line.split("\t");
|
|
470
|
-
if (columns[0] === "c") {
|
|
471
|
-
const classRecords = new Map();
|
|
472
|
-
for (const namespace of recognized) {
|
|
473
|
-
const value = columns[namespace.columnIndex]?.trim() ?? "";
|
|
474
|
-
if (!value) {
|
|
475
|
-
continue;
|
|
476
|
-
}
|
|
477
|
-
currentClassNames.set(namespace.mapping, value);
|
|
478
|
-
classRecords.set(namespace.mapping, createClassSymbolRecord(value));
|
|
479
|
-
}
|
|
480
|
-
addPairRecords(result, classRecords);
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
if (columns[0] === "" && columns[1] === "f") {
|
|
484
|
-
const fieldRecords = new Map();
|
|
485
|
-
for (const namespace of recognized) {
|
|
486
|
-
const owner = currentClassNames.get(namespace.mapping);
|
|
487
|
-
const value = columns[namespace.columnIndex + 2]?.trim() ?? "";
|
|
488
|
-
if (!owner || !value) {
|
|
489
|
-
continue;
|
|
490
|
-
}
|
|
491
|
-
fieldRecords.set(namespace.mapping, createFieldSymbolRecord(owner, value));
|
|
492
|
-
}
|
|
493
|
-
addPairRecords(result, fieldRecords);
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
if (columns[0] === "" && columns[1] === "m") {
|
|
497
|
-
const descriptor = columns[2]?.trim() || undefined;
|
|
498
|
-
const methodRecords = new Map();
|
|
499
|
-
for (const namespace of recognized) {
|
|
500
|
-
const owner = currentClassNames.get(namespace.mapping);
|
|
501
|
-
const value = columns[namespace.columnIndex + 2]?.trim() ?? "";
|
|
502
|
-
if (!owner || !value) {
|
|
503
|
-
continue;
|
|
504
|
-
}
|
|
505
|
-
methodRecords.set(namespace.mapping, createMethodSymbolRecord(owner, value, descriptor));
|
|
506
|
-
}
|
|
507
|
-
addPairRecords(result, methodRecords);
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
return result;
|
|
511
|
-
}
|
|
512
|
-
function addCandidates(target, index, symbols, kind, confidence) {
|
|
513
|
-
if (!symbols || symbols.size === 0) {
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
const rank = MATCH_RANK[kind];
|
|
517
|
-
for (const key of symbols) {
|
|
518
|
-
const record = index.records.get(key);
|
|
519
|
-
if (!record) {
|
|
520
|
-
continue;
|
|
521
|
-
}
|
|
522
|
-
const current = target.get(key);
|
|
523
|
-
if (!current || rank > current.rank || (rank === current.rank && confidence > current.confidence)) {
|
|
524
|
-
target.set(key, {
|
|
525
|
-
key,
|
|
526
|
-
record,
|
|
527
|
-
matchKind: kind,
|
|
528
|
-
confidence,
|
|
529
|
-
rank
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
function lookupCandidates(index, query) {
|
|
535
|
-
const trimmedQuery = query.symbol.trim();
|
|
536
|
-
const collected = new Map();
|
|
537
|
-
addCandidates(collected, index, index.exact.get(trimmedQuery), "exact", 1);
|
|
538
|
-
for (const variant of normalizedVariants(trimmedQuery)) {
|
|
539
|
-
addCandidates(collected, index, index.normalized.get(variant), "normalized", 0.9);
|
|
540
|
-
}
|
|
541
|
-
if (query.kind === "method" && query.owner && query.descriptor) {
|
|
542
|
-
const descriptorlessKey = `${query.owner}.${query.name}`;
|
|
543
|
-
addCandidates(collected, index, index.exact.get(descriptorlessKey), "normalized", DESCRIPTOR_FALLBACK_CONFIDENCE);
|
|
544
|
-
for (const variant of normalizedVariants(descriptorlessKey)) {
|
|
545
|
-
addCandidates(collected, index, index.normalized.get(variant), "normalized", DESCRIPTOR_FALLBACK_CONFIDENCE);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
const simpleKeys = new Set();
|
|
549
|
-
const shortName = simpleName(trimmedQuery);
|
|
550
|
-
if (shortName) {
|
|
551
|
-
simpleKeys.add(shortName);
|
|
552
|
-
}
|
|
553
|
-
if (query.kind !== "class") {
|
|
554
|
-
simpleKeys.add(query.name);
|
|
555
|
-
}
|
|
556
|
-
if (query.kind === "method" && query.descriptor) {
|
|
557
|
-
simpleKeys.add(`${query.name}${query.descriptor}`);
|
|
558
|
-
}
|
|
559
|
-
for (const key of simpleKeys) {
|
|
560
|
-
addCandidates(collected, index, index.simple.get(key), "simple-name", 0.75);
|
|
561
|
-
}
|
|
562
|
-
return [...collected.values()]
|
|
563
|
-
.sort((left, right) => {
|
|
564
|
-
if (right.confidence !== left.confidence) {
|
|
565
|
-
return right.confidence - left.confidence;
|
|
566
|
-
}
|
|
567
|
-
if (right.rank !== left.rank) {
|
|
568
|
-
return right.rank - left.rank;
|
|
569
|
-
}
|
|
570
|
-
return left.record.symbol.localeCompare(right.record.symbol);
|
|
571
|
-
})
|
|
572
|
-
.slice(0, MAX_CANDIDATES)
|
|
573
|
-
.map(({ record, matchKind, confidence }) => ({
|
|
574
|
-
symbol: record.symbol,
|
|
575
|
-
matchKind,
|
|
576
|
-
confidence,
|
|
577
|
-
kind: record.kind,
|
|
578
|
-
owner: record.owner,
|
|
579
|
-
name: record.name,
|
|
580
|
-
descriptor: record.descriptor
|
|
581
|
-
}));
|
|
582
|
-
}
|
|
583
|
-
function mappingPriorityFromInput(configPriority, override) {
|
|
584
|
-
if (override === "loom-first" || override === "maven-first") {
|
|
585
|
-
return override;
|
|
586
|
-
}
|
|
587
|
-
return configPriority;
|
|
588
|
-
}
|
|
589
|
-
function mappingSourceOrder(priority) {
|
|
590
|
-
if (priority === "maven-first") {
|
|
591
|
-
return ["maven", "loom-cache"];
|
|
592
|
-
}
|
|
593
|
-
return ["loom-cache", "maven"];
|
|
594
|
-
}
|
|
595
|
-
function requiresOnlyObfuscatedMojangGraph(sourceMapping, targetMapping) {
|
|
596
|
-
return sourceMapping !== "intermediary" &&
|
|
597
|
-
sourceMapping !== "yarn" &&
|
|
598
|
-
targetMapping !== "intermediary" &&
|
|
599
|
-
targetMapping !== "yarn";
|
|
600
|
-
}
|
|
601
|
-
function namespacePath(graph, sourceMapping, targetMapping) {
|
|
602
|
-
if (sourceMapping === targetMapping) {
|
|
603
|
-
return [sourceMapping];
|
|
604
|
-
}
|
|
605
|
-
const key = pairKey(sourceMapping, targetMapping);
|
|
606
|
-
if (graph.pathCache.has(key)) {
|
|
607
|
-
return graph.pathCache.get(key);
|
|
608
|
-
}
|
|
609
|
-
const { adjacency } = graph;
|
|
610
|
-
const queue = [sourceMapping];
|
|
611
|
-
let queueIndex = 0;
|
|
612
|
-
const parent = new Map([[sourceMapping, undefined]]);
|
|
613
|
-
while (queueIndex < queue.length) {
|
|
614
|
-
const current = queue[queueIndex];
|
|
615
|
-
queueIndex += 1;
|
|
616
|
-
if (current === targetMapping) {
|
|
617
|
-
break;
|
|
618
|
-
}
|
|
619
|
-
const neighbors = adjacency.get(current);
|
|
620
|
-
if (!neighbors) {
|
|
621
|
-
continue;
|
|
622
|
-
}
|
|
623
|
-
for (const neighbor of neighbors) {
|
|
624
|
-
if (parent.has(neighbor)) {
|
|
625
|
-
continue;
|
|
626
|
-
}
|
|
627
|
-
parent.set(neighbor, current);
|
|
628
|
-
queue.push(neighbor);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
if (!parent.has(targetMapping)) {
|
|
632
|
-
graph.pathCache.set(key, undefined);
|
|
633
|
-
return undefined;
|
|
634
|
-
}
|
|
635
|
-
const reversedPath = [];
|
|
636
|
-
let cursor = targetMapping;
|
|
637
|
-
while (cursor) {
|
|
638
|
-
reversedPath.push(cursor);
|
|
639
|
-
cursor = parent.get(cursor);
|
|
640
|
-
}
|
|
641
|
-
const path = reversedPath.reverse();
|
|
642
|
-
graph.pathCache.set(key, path);
|
|
643
|
-
return path;
|
|
644
|
-
}
|
|
645
|
-
function pathUsesSource(pairs, path, source) {
|
|
646
|
-
for (let index = 0; index < path.length - 1; index += 1) {
|
|
647
|
-
const hop = pairs.get(pairKey(path[index], path[index + 1]));
|
|
648
|
-
if (hop?.source === source) {
|
|
649
|
-
return true;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
return false;
|
|
653
|
-
}
|
|
654
|
-
function pathToTransformChain(path) {
|
|
655
|
-
if (path.length <= 1) {
|
|
656
|
-
return [];
|
|
657
|
-
}
|
|
658
|
-
const transform = [];
|
|
659
|
-
for (let index = 0; index < path.length - 1; index += 1) {
|
|
660
|
-
transform.push(`mapping:${path[index]}->${path[index + 1]}`);
|
|
661
|
-
}
|
|
662
|
-
return transform;
|
|
663
|
-
}
|
|
664
|
-
function toLookupCandidate(record) {
|
|
665
|
-
return {
|
|
666
|
-
symbol: record.symbol,
|
|
667
|
-
matchKind: "exact",
|
|
668
|
-
confidence: 1,
|
|
669
|
-
kind: record.kind,
|
|
670
|
-
owner: record.owner,
|
|
671
|
-
name: record.name,
|
|
672
|
-
descriptor: record.descriptor
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
function toSymbolReference(record) {
|
|
676
|
-
return {
|
|
677
|
-
kind: record.kind,
|
|
678
|
-
name: record.kind === "class" ? record.symbol : record.name,
|
|
679
|
-
owner: record.kind === "class" ? undefined : record.owner,
|
|
680
|
-
descriptor: record.kind === "method" ? record.descriptor : undefined,
|
|
681
|
-
symbol: record.symbol
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
function toResolutionCandidate(candidate) {
|
|
685
|
-
return {
|
|
686
|
-
kind: candidate.kind,
|
|
687
|
-
name: candidate.kind === "class" ? candidate.symbol : candidate.name,
|
|
688
|
-
owner: candidate.kind === "class" ? undefined : candidate.owner,
|
|
689
|
-
descriptor: candidate.kind === "method" ? candidate.descriptor : undefined,
|
|
690
|
-
symbol: candidate.symbol,
|
|
691
|
-
matchKind: candidate.matchKind,
|
|
692
|
-
confidence: candidate.confidence
|
|
693
|
-
};
|
|
694
|
-
}
|
|
695
|
-
function invalidInputError(message, details) {
|
|
696
|
-
return createError({
|
|
697
|
-
code: ERROR_CODES.INVALID_INPUT,
|
|
698
|
-
message,
|
|
699
|
-
details
|
|
700
|
-
});
|
|
701
|
-
}
|
|
702
|
-
function normalizeMemberName(name) {
|
|
703
|
-
const normalized = name.trim();
|
|
704
|
-
if (!normalized || /[\s./()]/.test(normalized)) {
|
|
705
|
-
throw invalidInputError("name must be a simple member name without separators when kind is field or method.", {
|
|
706
|
-
name
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
return normalized;
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Validate a JVM method descriptor such as `(I)V`, `()Lfoo/Bar;`, `(Lfoo/Bar;[I)V`.
|
|
713
|
-
* Rejects empty strings, missing/mis-positioned parens, empty return type, and invalid base
|
|
714
|
-
* type tokens so "(" or "()" style half-descriptors surface as ERR_INVALID_INPUT instead of
|
|
715
|
-
* being silently accepted.
|
|
716
|
-
*/
|
|
717
|
-
function normalizeMethodDescriptor(descriptor) {
|
|
718
|
-
const normalized = descriptor?.trim() ?? "";
|
|
719
|
-
if (!normalized) {
|
|
720
|
-
throw invalidInputError("descriptor must be a valid JVM descriptor when kind=method.", {
|
|
721
|
-
descriptor
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
if (!isValidMethodDescriptor(normalized)) {
|
|
725
|
-
throw invalidInputError("descriptor must be a valid JVM descriptor when kind=method.", {
|
|
726
|
-
descriptor
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
return normalized;
|
|
730
|
-
}
|
|
731
|
-
function isValidMethodDescriptor(descriptor) {
|
|
732
|
-
if (!descriptor.startsWith("("))
|
|
733
|
-
return false;
|
|
734
|
-
const closingIndex = descriptor.indexOf(")");
|
|
735
|
-
if (closingIndex < 0)
|
|
736
|
-
return false;
|
|
737
|
-
const argsSection = descriptor.slice(1, closingIndex);
|
|
738
|
-
const returnSection = descriptor.slice(closingIndex + 1);
|
|
739
|
-
if (returnSection.length === 0)
|
|
740
|
-
return false;
|
|
741
|
-
let cursor = 0;
|
|
742
|
-
while (cursor < argsSection.length) {
|
|
743
|
-
const next = consumeFieldType(argsSection, cursor, /*allowVoid*/ false);
|
|
744
|
-
if (next < 0)
|
|
745
|
-
return false;
|
|
746
|
-
cursor = next;
|
|
747
|
-
}
|
|
748
|
-
const returnEnd = consumeFieldType(returnSection, 0, /*allowVoid*/ true);
|
|
749
|
-
return returnEnd === returnSection.length;
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* JVM specification §4.3.2: "An array type descriptor is valid only if it represents a type
|
|
753
|
-
* with 255 or fewer dimensions." Matches the `multianewarray` / field-signature limit.
|
|
754
|
-
*/
|
|
755
|
-
const JVM_MAX_ARRAY_DIMENSIONS = 255;
|
|
756
|
-
function consumeFieldType(descriptor, position, allowVoid) {
|
|
757
|
-
// Arrays are handled iteratively so pathological inputs such as `(` + "[".repeat(20000) + `I)V`
|
|
758
|
-
// cannot blow the call stack. After consuming every leading `[`, only the element type token
|
|
759
|
-
// is dispatched through the switch below. Dimensions above the JVM limit are rejected rather
|
|
760
|
-
// than merely accepted as "syntactically valid but semantically absurd" — clients must not be
|
|
761
|
-
// able to push a 20000-dimension descriptor through cache-key construction.
|
|
762
|
-
let cursor = position;
|
|
763
|
-
let arrayDimensions = 0;
|
|
764
|
-
while (cursor < descriptor.length && descriptor[cursor] === "[") {
|
|
765
|
-
cursor += 1;
|
|
766
|
-
arrayDimensions += 1;
|
|
767
|
-
if (arrayDimensions > JVM_MAX_ARRAY_DIMENSIONS)
|
|
768
|
-
return -1;
|
|
769
|
-
}
|
|
770
|
-
if (cursor >= descriptor.length)
|
|
771
|
-
return -1;
|
|
772
|
-
// Void is only valid at the outermost position — inside an array element it is illegal.
|
|
773
|
-
const elementAllowsVoid = cursor === position && allowVoid;
|
|
774
|
-
const token = descriptor[cursor];
|
|
775
|
-
switch (token) {
|
|
776
|
-
case "B":
|
|
777
|
-
case "C":
|
|
778
|
-
case "D":
|
|
779
|
-
case "F":
|
|
780
|
-
case "I":
|
|
781
|
-
case "J":
|
|
782
|
-
case "S":
|
|
783
|
-
case "Z":
|
|
784
|
-
return cursor + 1;
|
|
785
|
-
case "V":
|
|
786
|
-
return elementAllowsVoid ? cursor + 1 : -1;
|
|
787
|
-
case "L": {
|
|
788
|
-
const end = descriptor.indexOf(";", cursor);
|
|
789
|
-
// Reject empty class names like L; and unterminated references.
|
|
790
|
-
if (end < 0 || end === cursor + 1)
|
|
791
|
-
return -1;
|
|
792
|
-
return end + 1;
|
|
793
|
-
}
|
|
794
|
-
default:
|
|
795
|
-
return -1;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
function normalizeQuerySymbol(input, signatureMode, options) {
|
|
799
|
-
if (input.kind !== "class" && input.kind !== "field" && input.kind !== "method") {
|
|
800
|
-
throw invalidInputError('kind must be one of "class", "field", or "method".', {
|
|
801
|
-
kind: input.kind
|
|
802
|
-
});
|
|
803
|
-
}
|
|
804
|
-
const normalizedName = input.name?.trim() ?? "";
|
|
805
|
-
if (!normalizedName) {
|
|
806
|
-
throw invalidInputError("name must be a non-empty string.", {
|
|
807
|
-
name: input.name
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
|
-
if (input.kind === "class") {
|
|
811
|
-
const owner = input.owner?.trim();
|
|
812
|
-
if (owner) {
|
|
813
|
-
throw invalidInputError("owner is not allowed when kind=class. Use name as FQCN.", {
|
|
814
|
-
owner: input.owner,
|
|
815
|
-
nextAction: 'Provide class as name, e.g. "net.minecraft.server.Main".'
|
|
816
|
-
});
|
|
817
|
-
}
|
|
818
|
-
if (input.descriptor?.trim()) {
|
|
819
|
-
throw invalidInputError("descriptor is not allowed when kind=class.", {
|
|
820
|
-
descriptor: input.descriptor
|
|
821
|
-
});
|
|
822
|
-
}
|
|
823
|
-
const className = normalizeMappedSymbolOutput(normalizedName);
|
|
824
|
-
if (!className.includes(".") && !options?.allowShortClassName) {
|
|
825
|
-
throw invalidInputError("name must be a fully qualified class name when kind=class.", {
|
|
826
|
-
name: input.name
|
|
827
|
-
});
|
|
828
|
-
}
|
|
829
|
-
const record = createClassSymbolRecord(className);
|
|
830
|
-
return {
|
|
831
|
-
record,
|
|
832
|
-
querySymbol: toSymbolReference(record)
|
|
833
|
-
};
|
|
834
|
-
}
|
|
835
|
-
const owner = normalizeMappedSymbolOutput(input.owner?.trim() ?? "");
|
|
836
|
-
if (!owner) {
|
|
837
|
-
throw invalidInputError("owner is required when kind is field or method.", {
|
|
838
|
-
owner: input.owner,
|
|
839
|
-
kind: input.kind
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
if (input.kind === "field") {
|
|
843
|
-
if (input.descriptor?.trim()) {
|
|
844
|
-
throw invalidInputError("descriptor is not allowed when kind=field.", {
|
|
845
|
-
descriptor: input.descriptor
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
const record = createFieldSymbolRecord(owner, normalizeMemberName(normalizedName));
|
|
849
|
-
return {
|
|
850
|
-
record,
|
|
851
|
-
querySymbol: toSymbolReference(record)
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
|
-
let descriptor;
|
|
855
|
-
if (signatureMode === "name-only") {
|
|
856
|
-
// name-only matches by owner+name only; a supplied descriptor is validated (so malformed
|
|
857
|
-
// input still surfaces as ERR_INVALID_INPUT) but discarded afterwards so downstream
|
|
858
|
-
// projection / filtering treats the query as "no descriptor".
|
|
859
|
-
if (input.descriptor?.trim()) {
|
|
860
|
-
normalizeMethodDescriptor(input.descriptor);
|
|
861
|
-
}
|
|
862
|
-
descriptor = "";
|
|
863
|
-
}
|
|
864
|
-
else {
|
|
865
|
-
descriptor = normalizeMethodDescriptor(input.descriptor);
|
|
866
|
-
}
|
|
867
|
-
const record = createMethodSymbolRecord(owner, normalizeMemberName(normalizedName), descriptor);
|
|
868
|
-
return {
|
|
869
|
-
record,
|
|
870
|
-
querySymbol: toSymbolReference(record)
|
|
871
|
-
};
|
|
872
|
-
}
|
|
873
|
-
function normalizeOwnerHint(ownerHint) {
|
|
874
|
-
const normalized = ownerHint?.trim();
|
|
875
|
-
if (!normalized) {
|
|
876
|
-
return undefined;
|
|
877
|
-
}
|
|
878
|
-
return normalizeMappedSymbolOutput(normalized);
|
|
879
|
-
}
|
|
880
|
-
function normalizeDescriptorHint(descriptorHint) {
|
|
881
|
-
const normalized = descriptorHint?.trim();
|
|
882
|
-
return normalized || undefined;
|
|
883
|
-
}
|
|
884
|
-
function applyDisambiguationHints(candidates, disambiguation) {
|
|
885
|
-
if (!disambiguation || candidates.length <= 1) {
|
|
886
|
-
return candidates;
|
|
887
|
-
}
|
|
888
|
-
let filtered = [...candidates];
|
|
889
|
-
const ownerHint = normalizeOwnerHint(disambiguation.ownerHint);
|
|
890
|
-
if (ownerHint) {
|
|
891
|
-
const ownerMatched = filtered.filter((candidate) => {
|
|
892
|
-
if (candidate.owner) {
|
|
893
|
-
return normalizeMappedSymbolOutput(candidate.owner) === ownerHint;
|
|
894
|
-
}
|
|
895
|
-
const normalizedSymbol = normalizeMappedSymbolOutput(candidate.symbol);
|
|
896
|
-
return normalizedSymbol.startsWith(`${ownerHint}.`);
|
|
897
|
-
});
|
|
898
|
-
if (ownerMatched.length > 0) {
|
|
899
|
-
filtered = ownerMatched;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
const descriptorHint = normalizeDescriptorHint(disambiguation.descriptorHint);
|
|
903
|
-
if (descriptorHint) {
|
|
904
|
-
const descriptorMatched = filtered.filter((candidate) => candidate.descriptor != null && candidate.descriptor === descriptorHint);
|
|
905
|
-
if (descriptorMatched.length > 0) {
|
|
906
|
-
filtered = descriptorMatched;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
return filtered;
|
|
910
|
-
}
|
|
911
|
-
function projectLookupCandidateDescriptor(candidate, sourceDescriptor, targetDescriptor) {
|
|
912
|
-
// Tiny mappings preserve method descriptors verbatim, so single-hop tiny paths often
|
|
913
|
-
// return the source descriptor even though the final symbol is already in the target
|
|
914
|
-
// namespace. Multi-hop paths that already produced a target-side descriptor are left
|
|
915
|
-
// unchanged by design.
|
|
916
|
-
if (candidate.kind !== "method" ||
|
|
917
|
-
!candidate.descriptor ||
|
|
918
|
-
!targetDescriptor ||
|
|
919
|
-
candidate.descriptor !== sourceDescriptor) {
|
|
920
|
-
return candidate;
|
|
921
|
-
}
|
|
922
|
-
return {
|
|
923
|
-
...candidate,
|
|
924
|
-
descriptor: targetDescriptor
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
function effectiveLoomSearchProjectPath(projectPath) {
|
|
928
|
-
return normalizeOptionalProjectPath(projectPath) ?? normalizeOptionalProjectPath(process.cwd());
|
|
929
|
-
}
|
|
930
|
-
function collectTargetRecords(graph, targetMapping) {
|
|
931
|
-
return [...(graph.recordsByTarget.get(targetMapping) ?? [])];
|
|
932
|
-
}
|
|
933
|
-
function normalizeIncludedKinds(inputKinds) {
|
|
934
|
-
const normalized = new Set();
|
|
935
|
-
const kinds = inputKinds ?? ["class", "field", "method"];
|
|
936
|
-
for (const kind of kinds) {
|
|
937
|
-
if (kind === "class" || kind === "field" || kind === "method") {
|
|
938
|
-
normalized.add(kind);
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
if (normalized.size === 0) {
|
|
942
|
-
normalized.add("class");
|
|
943
|
-
normalized.add("field");
|
|
944
|
-
normalized.add("method");
|
|
945
|
-
}
|
|
946
|
-
return normalized;
|
|
947
|
-
}
|
|
948
|
-
function inferAmbiguityReasons(candidates, usedMojangClientMappings) {
|
|
949
|
-
if (candidates.length <= 1) {
|
|
950
|
-
return [];
|
|
951
|
-
}
|
|
952
|
-
const reasons = [];
|
|
953
|
-
const owners = [...new Set(candidates.map((c) => c.owner).filter(Boolean))];
|
|
954
|
-
if (owners.length > 1) {
|
|
955
|
-
reasons.push(`Multiple owner classes matched: ${owners.join(", ")}`);
|
|
956
|
-
}
|
|
957
|
-
const matchKinds = [...new Set(candidates.map((c) => c.matchKind))];
|
|
958
|
-
if (matchKinds.length > 1) {
|
|
959
|
-
reasons.push(`Candidates matched at different precision levels: ${matchKinds.join(", ")}`);
|
|
960
|
-
}
|
|
961
|
-
if (usedMojangClientMappings) {
|
|
962
|
-
const hasDescriptor = candidates.some((c) => c.descriptor);
|
|
963
|
-
const missingDescriptor = candidates.some((c) => !c.descriptor);
|
|
964
|
-
if (hasDescriptor && missingDescriptor) {
|
|
965
|
-
reasons.push("Method descriptor was lost through mojang-client-mappings path, causing broader matching.");
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
if (owners.length <= 1) {
|
|
969
|
-
const descriptors = [...new Set(candidates.map((c) => c.descriptor).filter(Boolean))];
|
|
970
|
-
if (descriptors.length > 1) {
|
|
971
|
-
reasons.push(`Overloaded method: ${descriptors.length} variants`);
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
if (reasons.length === 0) {
|
|
975
|
-
reasons.push(`${candidates.length} candidates matched with similar confidence scores.`);
|
|
976
|
-
}
|
|
977
|
-
return reasons;
|
|
978
|
-
}
|
|
979
|
-
function clampCandidateLimit(limit) {
|
|
980
|
-
if (!Number.isFinite(limit) || limit == null) {
|
|
981
|
-
return MAX_CANDIDATES;
|
|
982
|
-
}
|
|
983
|
-
return Math.max(1, Math.min(MAX_CANDIDATES, Math.trunc(limit)));
|
|
984
|
-
}
|
|
985
|
-
function limitResolutionCandidates(candidates, requestedLimit) {
|
|
986
|
-
const candidateCount = candidates.length;
|
|
987
|
-
const limit = clampCandidateLimit(requestedLimit);
|
|
988
|
-
const limitedCandidates = candidateCount > limit ? candidates.slice(0, limit) : candidates;
|
|
989
|
-
return {
|
|
990
|
-
candidates: limitedCandidates,
|
|
991
|
-
candidateCount,
|
|
992
|
-
...(limitedCandidates.length < candidateCount ? { candidatesTruncated: true } : {})
|
|
993
|
-
};
|
|
994
|
-
}
|
|
995
|
-
function clampRowLimit(limit) {
|
|
996
|
-
if (!Number.isFinite(limit) || limit == null) {
|
|
997
|
-
return undefined;
|
|
998
|
-
}
|
|
999
|
-
return Math.max(1, Math.min(5000, Math.trunc(limit)));
|
|
1000
|
-
}
|
|
22
|
+
/* parsers extracted to src/mapping/parsers/{symbol-records,normalize,proguard,tiny}.ts */
|
|
1001
23
|
export class MappingService {
|
|
1002
24
|
config;
|
|
1003
25
|
versionService;
|
|
@@ -1244,7 +266,7 @@ export class MappingService {
|
|
|
1244
266
|
targetMapping,
|
|
1245
267
|
sourcePriority: priority,
|
|
1246
268
|
nextAction: "Try mapping=obfuscated which is always available.",
|
|
1247
|
-
|
|
269
|
+
...buildSuggestedCall({ tool: "resolve-artifact", params: { mapping: "obfuscated" } })
|
|
1248
270
|
}
|
|
1249
271
|
});
|
|
1250
272
|
}
|
|
@@ -1904,11 +926,16 @@ export class MappingService {
|
|
|
1904
926
|
}
|
|
1905
927
|
/**
|
|
1906
928
|
* Probe the mapping graph health for a given version.
|
|
1907
|
-
*
|
|
929
|
+
*
|
|
930
|
+
* `tinyMappingsAvailable` reports whether Tiny is sufficient for the
|
|
931
|
+
* request: `true` when Tiny is not required (obfuscated/mojang) or is
|
|
932
|
+
* loaded, `false` only when intermediary/yarn was requested and Tiny is
|
|
933
|
+
* unavailable.
|
|
1908
934
|
*/
|
|
1909
935
|
async checkMappingHealth(input) {
|
|
1910
936
|
const priority = mappingPriorityFromInput(this.config.mappingSourcePriority, input.sourcePriority);
|
|
1911
937
|
const degradations = [];
|
|
938
|
+
const needsTinyMappings = input.requestedMapping === "intermediary" || input.requestedMapping === "yarn";
|
|
1912
939
|
if (isUnobfuscatedVersion(input.version)) {
|
|
1913
940
|
const requestFulfillable = input.requestedMapping === "obfuscated" || input.requestedMapping === "mojang";
|
|
1914
941
|
if (!requestFulfillable) {
|
|
@@ -1916,19 +943,19 @@ export class MappingService {
|
|
|
1916
943
|
}
|
|
1917
944
|
return {
|
|
1918
945
|
mojangMappingsAvailable: true,
|
|
1919
|
-
tinyMappingsAvailable:
|
|
946
|
+
tinyMappingsAvailable: !needsTinyMappings,
|
|
1920
947
|
memberRemapAvailable: requestFulfillable,
|
|
1921
948
|
degradations
|
|
1922
949
|
};
|
|
1923
950
|
}
|
|
1924
951
|
let graph;
|
|
1925
952
|
try {
|
|
1926
|
-
graph = await this.loadGraph(input.version, priority, "full");
|
|
953
|
+
graph = await this.loadGraph(input.version, priority, needsTinyMappings ? "full" : "obfuscated-mojang-only");
|
|
1927
954
|
}
|
|
1928
955
|
catch {
|
|
1929
956
|
return {
|
|
1930
957
|
mojangMappingsAvailable: false,
|
|
1931
|
-
tinyMappingsAvailable:
|
|
958
|
+
tinyMappingsAvailable: !needsTinyMappings,
|
|
1932
959
|
memberRemapAvailable: false,
|
|
1933
960
|
degradations: ["Mapping graph could not be loaded."]
|
|
1934
961
|
};
|
|
@@ -1945,7 +972,7 @@ export class MappingService {
|
|
|
1945
972
|
if (!mojangAvailable) {
|
|
1946
973
|
degradations.push("Mojang client mappings are not available for this version.");
|
|
1947
974
|
}
|
|
1948
|
-
if (!tinyAvailable) {
|
|
975
|
+
if (needsTinyMappings && !tinyAvailable) {
|
|
1949
976
|
degradations.push("No intermediary/yarn tiny mappings were found for this version.");
|
|
1950
977
|
}
|
|
1951
978
|
// Check if member remap path exists (requestedMapping → obfuscated)
|
|
@@ -1962,7 +989,7 @@ export class MappingService {
|
|
|
1962
989
|
}
|
|
1963
990
|
return {
|
|
1964
991
|
mojangMappingsAvailable: mojangAvailable,
|
|
1965
|
-
tinyMappingsAvailable: tinyAvailable,
|
|
992
|
+
tinyMappingsAvailable: needsTinyMappings ? tinyAvailable : true,
|
|
1966
993
|
memberRemapAvailable,
|
|
1967
994
|
degradations
|
|
1968
995
|
};
|
|
@@ -2066,225 +1093,21 @@ export class MappingService {
|
|
|
2066
1093
|
}
|
|
2067
1094
|
}
|
|
2068
1095
|
async loadMojangPairs(version) {
|
|
2069
|
-
|
|
2070
|
-
let metadata;
|
|
2071
|
-
try {
|
|
2072
|
-
metadata = await this.versionService.resolveVersionMappings(version);
|
|
2073
|
-
}
|
|
2074
|
-
catch (caughtError) {
|
|
2075
|
-
return {
|
|
2076
|
-
pairs: new Map(),
|
|
2077
|
-
warnings: [
|
|
2078
|
-
`Failed to resolve version metadata for "${version}": ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`
|
|
2079
|
-
],
|
|
2080
|
-
mappingArtifact: `version:${version}`
|
|
2081
|
-
};
|
|
2082
|
-
}
|
|
2083
|
-
const clientMappingsUrl = metadata.clientMappingsUrl ?? metadata.mappingsUrl;
|
|
2084
|
-
if (!clientMappingsUrl) {
|
|
2085
|
-
warnings.push(`Minecraft version "${version}" does not expose client mappings URL.`);
|
|
2086
|
-
return {
|
|
2087
|
-
pairs: new Map(),
|
|
2088
|
-
warnings,
|
|
2089
|
-
mappingArtifact: metadata.versionDetailUrl
|
|
2090
|
-
};
|
|
2091
|
-
}
|
|
2092
|
-
const mappingsPath = join(this.config.cacheDir, "mappings", version, "client_mappings.txt");
|
|
2093
|
-
if (!existsSync(mappingsPath)) {
|
|
2094
|
-
await mkdir(dirname(mappingsPath), { recursive: true });
|
|
2095
|
-
const downloaded = await downloadToCache(clientMappingsUrl, mappingsPath, {
|
|
2096
|
-
fetchFn: this.fetchFn,
|
|
2097
|
-
retries: this.config.fetchRetries,
|
|
2098
|
-
timeoutMs: this.config.fetchTimeoutMs
|
|
2099
|
-
});
|
|
2100
|
-
if (!downloaded.ok || !downloaded.path) {
|
|
2101
|
-
warnings.push(`Failed to download client mappings from "${clientMappingsUrl}" (status: ${downloaded.statusCode ?? "unknown"}).`);
|
|
2102
|
-
return {
|
|
2103
|
-
pairs: new Map(),
|
|
2104
|
-
warnings,
|
|
2105
|
-
mappingArtifact: clientMappingsUrl
|
|
2106
|
-
};
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
try {
|
|
2110
|
-
const content = await readFile(mappingsPath, "utf8");
|
|
2111
|
-
return {
|
|
2112
|
-
pairs: parseClientMappings(content),
|
|
2113
|
-
warnings,
|
|
2114
|
-
mappingArtifact: clientMappingsUrl
|
|
2115
|
-
};
|
|
2116
|
-
}
|
|
2117
|
-
catch (caughtError) {
|
|
2118
|
-
warnings.push(`Failed to parse client mappings for "${version}": ${caughtError instanceof Error ? caughtError.message : String(caughtError)}`);
|
|
2119
|
-
return {
|
|
2120
|
-
pairs: new Map(),
|
|
2121
|
-
warnings,
|
|
2122
|
-
mappingArtifact: clientMappingsUrl
|
|
2123
|
-
};
|
|
2124
|
-
}
|
|
1096
|
+
return loadMojangPairs(this.loaderDeps(), version);
|
|
2125
1097
|
}
|
|
2126
1098
|
async loadTinyPairsFromLoom(version, projectPath) {
|
|
2127
|
-
|
|
2128
|
-
const merged = new Map();
|
|
2129
|
-
const discoveredPaths = new Set();
|
|
2130
|
-
for (const root of searchRoots) {
|
|
2131
|
-
let discovered = [];
|
|
2132
|
-
const versionRoot = join(root, version);
|
|
2133
|
-
try {
|
|
2134
|
-
discovered = existsSync(versionRoot)
|
|
2135
|
-
? await fastGlob.glob(["**/*.tiny", "**/*.tinyv2"], {
|
|
2136
|
-
cwd: versionRoot,
|
|
2137
|
-
absolute: true,
|
|
2138
|
-
onlyFiles: true
|
|
2139
|
-
})
|
|
2140
|
-
: await fastGlob.glob([`${version.replace(GLOB_SPECIAL_CHARS, "\\$&")}/**/*.tiny`, `${version.replace(GLOB_SPECIAL_CHARS, "\\$&")}/**/*.tinyv2`], {
|
|
2141
|
-
cwd: root,
|
|
2142
|
-
absolute: true,
|
|
2143
|
-
onlyFiles: true
|
|
2144
|
-
});
|
|
2145
|
-
}
|
|
2146
|
-
catch {
|
|
2147
|
-
continue;
|
|
2148
|
-
}
|
|
2149
|
-
const byVersion = discovered
|
|
2150
|
-
.filter((path) => path.replaceAll("\\", "/").includes(`/${version}/`))
|
|
2151
|
-
.sort((left, right) => left.localeCompare(right));
|
|
2152
|
-
if (byVersion.length === 0) {
|
|
2153
|
-
continue;
|
|
2154
|
-
}
|
|
2155
|
-
for (const path of byVersion) {
|
|
2156
|
-
discoveredPaths.add(path);
|
|
2157
|
-
try {
|
|
2158
|
-
const content = await readFile(path, "utf8");
|
|
2159
|
-
const parsed = parseTinyMappings(content);
|
|
2160
|
-
for (const [key, index] of parsed.entries()) {
|
|
2161
|
-
const existing = merged.get(key);
|
|
2162
|
-
if (!existing) {
|
|
2163
|
-
merged.set(key, index);
|
|
2164
|
-
}
|
|
2165
|
-
else {
|
|
2166
|
-
mergeDirectionIndexes(existing, index);
|
|
2167
|
-
}
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
catch {
|
|
2171
|
-
// best effort: skip unreadable or invalid files
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
const orderedPaths = [...discoveredPaths].sort((left, right) => left.localeCompare(right));
|
|
2176
|
-
if (orderedPaths.length > 0) {
|
|
2177
|
-
return {
|
|
2178
|
-
pairs: merged,
|
|
2179
|
-
warnings: [],
|
|
2180
|
-
mappingArtifact: orderedPaths[0]
|
|
2181
|
-
};
|
|
2182
|
-
}
|
|
2183
|
-
return {
|
|
2184
|
-
pairs: new Map(),
|
|
2185
|
-
warnings: [`No Loom tiny mapping files matched version "${version}".`],
|
|
2186
|
-
mappingArtifact: "loom-cache:none"
|
|
2187
|
-
};
|
|
1099
|
+
return loadTinyPairsFromLoom(version, projectPath);
|
|
2188
1100
|
}
|
|
2189
1101
|
async loadTinyPairsFromMaven(version) {
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
const intermediaryUrls = [];
|
|
2194
|
-
const yarnUrls = [];
|
|
2195
|
-
const repoBases = repos.map((repo) => repo.replace(/\/+$/, ""));
|
|
2196
|
-
const yarnCoordinatesByRepo = await Promise.all(repoBases.map(async (base) => ({
|
|
2197
|
-
base,
|
|
2198
|
-
yarnCoordinates: await this.fetchYarnCoordinates(base, version)
|
|
2199
|
-
})));
|
|
2200
|
-
for (const { base, yarnCoordinates } of yarnCoordinatesByRepo) {
|
|
2201
|
-
intermediaryUrls.push(`${base}/net/fabricmc/intermediary/${version}/intermediary-${version}-v2.jar`, `${base}/net/fabricmc/intermediary/${version}/intermediary-${version}.jar`);
|
|
2202
|
-
for (const coordinate of yarnCoordinates) {
|
|
2203
|
-
yarnUrls.push(`${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}-v2.jar`, `${base}/net/fabricmc/yarn/${coordinate}/yarn-${coordinate}.jar`);
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
const allUrls = [...intermediaryUrls, ...yarnUrls];
|
|
2207
|
-
const parsedResults = await Promise.allSettled(allUrls.map(async (url) => {
|
|
2208
|
-
const downloaded = await downloadToCache(url, defaultDownloadPath(this.config.cacheDir, url), {
|
|
2209
|
-
fetchFn: this.fetchFn,
|
|
2210
|
-
retries: this.config.fetchRetries,
|
|
2211
|
-
timeoutMs: this.config.fetchTimeoutMs
|
|
2212
|
-
});
|
|
2213
|
-
if (!downloaded.ok || !downloaded.path) {
|
|
2214
|
-
return undefined;
|
|
2215
|
-
}
|
|
2216
|
-
return this.parseTinyFromJar(downloaded.path);
|
|
2217
|
-
}));
|
|
2218
|
-
for (const result of parsedResults) {
|
|
2219
|
-
if (result.status !== "fulfilled" || !result.value) {
|
|
2220
|
-
continue;
|
|
2221
|
-
}
|
|
2222
|
-
for (const [key, index] of result.value.entries()) {
|
|
2223
|
-
const existing = merged.get(key);
|
|
2224
|
-
if (!existing) {
|
|
2225
|
-
merged.set(key, index);
|
|
2226
|
-
}
|
|
2227
|
-
else {
|
|
2228
|
-
mergeDirectionIndexes(existing, index);
|
|
2229
|
-
}
|
|
2230
|
-
}
|
|
2231
|
-
}
|
|
2232
|
-
if (merged.size === 0) {
|
|
2233
|
-
warnings.push(`No Maven tiny mappings could be loaded for "${version}".`);
|
|
2234
|
-
}
|
|
1102
|
+
return loadTinyPairsFromMaven(this.loaderDeps(), version);
|
|
1103
|
+
}
|
|
1104
|
+
loaderDeps() {
|
|
2235
1105
|
return {
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
1106
|
+
config: this.config,
|
|
1107
|
+
fetchFn: this.fetchFn,
|
|
1108
|
+
versionService: this.versionService
|
|
2239
1109
|
};
|
|
2240
1110
|
}
|
|
2241
|
-
async parseTinyFromJar(jarPath) {
|
|
2242
|
-
const tinyEntries = (await collectMatchedJarEntriesAsUtf8(jarPath, (entry) => entry.toLowerCase().endsWith(".tiny") || entry.toLowerCase().endsWith(".tinyv2"), { continueOnError: true })).sort((left, right) => left.filePath.localeCompare(right.filePath));
|
|
2243
|
-
const merged = new Map();
|
|
2244
|
-
for (const entry of tinyEntries) {
|
|
2245
|
-
try {
|
|
2246
|
-
const parsed = parseTinyMappings(entry.content);
|
|
2247
|
-
for (const [key, index] of parsed.entries()) {
|
|
2248
|
-
const existing = merged.get(key);
|
|
2249
|
-
if (!existing) {
|
|
2250
|
-
merged.set(key, index);
|
|
2251
|
-
}
|
|
2252
|
-
else {
|
|
2253
|
-
mergeDirectionIndexes(existing, index);
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
catch {
|
|
2258
|
-
// skip malformed tiny entries
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
return merged;
|
|
2262
|
-
}
|
|
2263
|
-
async fetchYarnCoordinates(repoBase, version) {
|
|
2264
|
-
const metadataUrl = `${repoBase}/net/fabricmc/yarn/maven-metadata.xml`;
|
|
2265
|
-
try {
|
|
2266
|
-
const response = await this.fetchFn(metadataUrl);
|
|
2267
|
-
if (!response.ok) {
|
|
2268
|
-
return [];
|
|
2269
|
-
}
|
|
2270
|
-
const xml = await response.text();
|
|
2271
|
-
const versions = [...xml.matchAll(/<version>([^<]+)<\/version>/g)]
|
|
2272
|
-
.map((match) => match[1]?.trim() ?? "")
|
|
2273
|
-
.filter((value) => value.startsWith(`${version}+build.`));
|
|
2274
|
-
const sorted = versions.sort((left, right) => {
|
|
2275
|
-
const leftBuild = Number.parseInt(left.split("+build.")[1] ?? "0", 10);
|
|
2276
|
-
const rightBuild = Number.parseInt(right.split("+build.")[1] ?? "0", 10);
|
|
2277
|
-
return rightBuild - leftBuild;
|
|
2278
|
-
});
|
|
2279
|
-
if (sorted.length > 0) {
|
|
2280
|
-
return sorted.slice(0, 3);
|
|
2281
|
-
}
|
|
2282
|
-
return [version];
|
|
2283
|
-
}
|
|
2284
|
-
catch {
|
|
2285
|
-
return [version];
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
1111
|
trimGraphCache() {
|
|
2289
1112
|
const maxEntries = Math.max(1, this.config.maxMappingGraphCache ?? 16);
|
|
2290
1113
|
while (this.graphCache.size > maxEntries) {
|