@adhisang/minecraft-modding-mcp 7.0.0 → 7.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 +30 -0
- package/README.md +3 -2
- package/dist/entry-tools/analyze-mod-service.d.ts +2 -2
- package/dist/entry-tools/analyze-symbol-service.d.ts +2 -2
- package/dist/entry-tools/batch-class-members-service.d.ts +3 -2
- package/dist/entry-tools/batch-class-members-service.js +20 -6
- package/dist/entry-tools/batch-class-source-service.d.ts +3 -2
- package/dist/entry-tools/batch-class-source-service.js +10 -0
- package/dist/entry-tools/compare-minecraft-service.d.ts +27 -4
- package/dist/entry-tools/compare-minecraft-service.js +65 -4
- package/dist/entry-tools/entry-tool-schema.d.ts +2 -2
- package/dist/entry-tools/inspect-minecraft-service.d.ts +2 -2
- package/dist/entry-tools/manage-cache-service.d.ts +2 -2
- package/dist/entry-tools/validate-project/cases/project-summary.js +71 -12
- package/dist/entry-tools/validate-project-service.d.ts +2 -2
- package/dist/index.js +37 -15
- package/dist/source/artifact-resolver.d.ts +14 -0
- package/dist/source/artifact-resolver.js +106 -12
- package/dist/source/class-source/members-builder.d.ts +7 -0
- package/dist/source/class-source/members-builder.js +4 -1
- package/dist/source/class-source.d.ts +9 -2
- package/dist/source/class-source.js +173 -25
- package/dist/source/indexer.js +69 -1
- package/dist/source/lifecycle/mapping-helpers.d.ts +20 -1
- package/dist/source/lifecycle/mapping-helpers.js +29 -3
- package/dist/source/lifecycle/runtime-check.d.ts +25 -0
- package/dist/source/lifecycle/runtime-check.js +68 -39
- package/dist/source/symbol-resolver.js +88 -0
- package/dist/source-jar-reader.d.ts +33 -0
- package/dist/source-jar-reader.js +58 -0
- package/dist/source-resolver.d.ts +7 -0
- package/dist/source-resolver.js +20 -5
- package/dist/source-service.d.ts +5 -0
- package/dist/source-service.js +7 -0
- package/dist/storage/db.d.ts +62 -2
- package/dist/storage/db.js +181 -20
- package/dist/storage/sqlite.d.ts +31 -1
- package/dist/storage/sqlite.js +125 -16
- package/dist/tool-guidance.js +4 -1
- package/dist/tool-schemas.d.ts +64 -52
- package/dist/tool-schemas.js +9 -7
- package/dist/types.d.ts +9 -0
- package/dist/v1-parity-schemas.js +36 -2
- package/dist/version-diff-service.d.ts +23 -0
- package/dist/version-diff-service.js +101 -0
- package/dist/version-service.d.ts +14 -0
- package/dist/version-service.js +45 -3
- package/dist/workspace-mapping-service.d.ts +8 -0
- package/dist/workspace-mapping-service.js +35 -7
- package/docs/README-ja.md +2 -0
- package/docs/tool-reference.md +55 -11
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import { isAbsolute, resolve as resolvePath } from "node:path";
|
|
|
3
3
|
import { buildSuggestedCall } from "../build-suggested-call.js";
|
|
4
4
|
import { ERROR_CODES, createError, isAppError } from "../errors.js";
|
|
5
5
|
import * as artifactResolver from "./artifact-resolver.js";
|
|
6
|
+
import { isUnobfuscatedIdentityPair } from "./lifecycle/mapping-helpers.js";
|
|
6
7
|
import * as classSourceHelpers from "./class-source-helpers.js";
|
|
7
8
|
import { buildClassSourceSnippet } from "./class-source/snippet-builder.js";
|
|
8
9
|
import { remapAndCountMembers, sliceMembersWithLimit, projectMembersForWire, projectMembersByLevel } from "./class-source/members-builder.js";
|
|
@@ -80,12 +81,33 @@ function looksLikeDeobfuscatedClassName(value) {
|
|
|
80
81
|
function obfuscatedNamespaceHint(className) {
|
|
81
82
|
return `Artifact is indexed in obfuscated runtime names. Deobfuscated names like "${className}" usually require mapping="mojang" or a find-mapping lookup to obfuscated names.`;
|
|
82
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* The artifact's Minecraft version when its runtime ships unobfuscated (26.1+)
|
|
86
|
+
* names, else undefined. A native dependency's version is its own release number,
|
|
87
|
+
* not a Minecraft version, so it is never put to `isUnobfuscatedVersion` (the same
|
|
88
|
+
* rule as the `reconcileUnobfuscatedNamespace` call sites).
|
|
89
|
+
*/
|
|
90
|
+
function unobfuscatedMinecraftVersion(input) {
|
|
91
|
+
if (input.nativeDependency || !input.version || !isUnobfuscatedVersion(input.version)) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
return input.version;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Said where the obfuscated namespace hint would otherwise have been: on 26.1+
|
|
98
|
+
* the as-shipped names are the Mojang names, so no mapping choice finds another class.
|
|
99
|
+
*/
|
|
100
|
+
function unobfuscatedRuntimeNamesNote(version) {
|
|
101
|
+
return `Minecraft ${version} ships Mojang names at runtime, so retrying with a different mapping will not change the class names.`;
|
|
102
|
+
}
|
|
83
103
|
function hasPartialNetMinecraftCoverage(qualityFlags) {
|
|
84
104
|
return qualityFlags.includes("partial-source-no-net-minecraft");
|
|
85
105
|
}
|
|
86
106
|
/**
|
|
87
|
-
* Whether "this artifact is indexed in obfuscated names, ask
|
|
88
|
-
*
|
|
107
|
+
* Whether a miss has the shape "this artifact is indexed in obfuscated names, ask
|
|
108
|
+
* for mapping=mojang" answers, from the pieces of the artifact the caller sees.
|
|
109
|
+
* The claim can still be false on Minecraft 26.1+; `isObfuscatedNamespaceHintTrue`
|
|
110
|
+
* adds that gate.
|
|
89
111
|
*
|
|
90
112
|
* Two artifact kinds report `mappingApplied: "obfuscated"` without being an
|
|
91
113
|
* obfuscated Minecraft index, and the hint is simply false for them:
|
|
@@ -99,22 +121,41 @@ function hasPartialNetMinecraftCoverage(qualityFlags) {
|
|
|
99
121
|
* without these exclusions an ordinary library class such as "GameTest"
|
|
100
122
|
* qualifies and the caller is told to remap a jar that was never obfuscated.
|
|
101
123
|
*/
|
|
102
|
-
function
|
|
124
|
+
function isObfuscatedLabelMiss(input) {
|
|
103
125
|
return (input.mappingApplied === "obfuscated" &&
|
|
104
126
|
!input.nativeDependency &&
|
|
105
127
|
!input.qualityFlags.includes("shell-jar") &&
|
|
106
128
|
looksLikeDeobfuscatedClassName(input.className));
|
|
107
129
|
}
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
/**
|
|
131
|
+
* `isObfuscatedLabelMiss` is the shape of the miss the hint answers; this adds
|
|
132
|
+
* the one fact that makes its claim false even then. A Minecraft 26.1+ artifact
|
|
133
|
+
* labelled "obfuscated" is indexed in the as-shipped names, which ARE Mojang
|
|
134
|
+
* names: telling the caller to ask for mapping="mojang" sends them round a
|
|
135
|
+
* retry that returns the same names.
|
|
136
|
+
*/
|
|
137
|
+
function isObfuscatedNamespaceHintTrue(input) {
|
|
138
|
+
return (isObfuscatedLabelMiss(input) &&
|
|
139
|
+
unobfuscatedMinecraftVersion({
|
|
140
|
+
version: input.minecraftVersion,
|
|
141
|
+
nativeDependency: input.nativeDependency
|
|
142
|
+
}) === undefined);
|
|
143
|
+
}
|
|
144
|
+
function describeArtifactForNamespaceHint(artifact, className) {
|
|
145
|
+
return {
|
|
110
146
|
mappingApplied: artifact.mappingApplied,
|
|
111
147
|
qualityFlags: artifact.qualityFlags,
|
|
112
148
|
nativeDependency: artifactResolver.isDependencyLikeArtifact({
|
|
113
149
|
provenance: artifact.provenance,
|
|
114
150
|
coordinate: artifact.coordinate
|
|
115
151
|
}),
|
|
116
|
-
className
|
|
117
|
-
|
|
152
|
+
className,
|
|
153
|
+
minecraftVersion: artifactResolver.inferVersionFromContext({
|
|
154
|
+
version: artifact.version,
|
|
155
|
+
provenance: artifact.provenance,
|
|
156
|
+
coordinate: artifact.coordinate
|
|
157
|
+
})
|
|
158
|
+
};
|
|
118
159
|
}
|
|
119
160
|
function classNameToClassPath(className) {
|
|
120
161
|
const normalized = normalizePathStyle(className.trim()).replace(/\//g, ".");
|
|
@@ -149,10 +190,31 @@ export function resolveClassFilePath(svc, artifactId, className) {
|
|
|
149
190
|
const expectedPrefix = lastSlash < 0 ? "" : classPath.slice(0, lastSlash + 1);
|
|
150
191
|
return svc.filesRepo.findBestClassLookupPath(artifactId, [...candidates], normalizedClassName, simpleName, expectedPrefix);
|
|
151
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* The class-name translation behind `svc.resolveClassNameForLookup`, whose callers
|
|
195
|
+
* (the lifecycle tools) pass a Minecraft version, so `version` also keys the 26.1+
|
|
196
|
+
* identity shortcut.
|
|
197
|
+
*/
|
|
152
198
|
export async function resolveClassNameForLookup(svc, input) {
|
|
199
|
+
return resolveArtifactClassNameForLookup(svc, { ...input, minecraftVersion: input.version });
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* `resolveClassNameForLookup` for an artifact the caller named, whose `version`
|
|
203
|
+
* need not be a Minecraft version: a dependency artifact's is its own release
|
|
204
|
+
* number. `version` still drives the mapping lookup, exactly as before; only
|
|
205
|
+
* `minecraftVersion`, which must be a proven Minecraft version, may take the 26.1+
|
|
206
|
+
* identity shortcut.
|
|
207
|
+
*/
|
|
208
|
+
async function resolveArtifactClassNameForLookup(svc, input) {
|
|
153
209
|
if (input.sourceMapping === input.targetMapping) {
|
|
154
210
|
return input.className;
|
|
155
211
|
}
|
|
212
|
+
// On 26.1+ the as-shipped ("obfuscated") names ARE the Mojang names and the
|
|
213
|
+
// mapping graph between them is empty by design, so the lookup can only miss
|
|
214
|
+
// and warn about a translation that is the identity.
|
|
215
|
+
if (isUnobfuscatedIdentityPair(input.minecraftVersion, input.sourceMapping, input.targetMapping)) {
|
|
216
|
+
return input.className;
|
|
217
|
+
}
|
|
156
218
|
if (!input.version) {
|
|
157
219
|
input.warnings.push(`Could not map class "${input.className}" from ${input.sourceMapping} to ${input.targetMapping} for ${input.context} because version is unavailable.`);
|
|
158
220
|
return input.className;
|
|
@@ -177,6 +239,38 @@ export async function resolveClassNameForLookup(svc, input) {
|
|
|
177
239
|
}
|
|
178
240
|
return input.className;
|
|
179
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* The provenance as returned, carrying `unobfuscatedRuntime` whenever the artifact
|
|
244
|
+
* it describes is a Minecraft 26.1+ runtime. The resolver stamps the flag on every
|
|
245
|
+
* fresh resolve, but a row indexed before the flag existed is served warm and never
|
|
246
|
+
* rewritten, and a row with no provenance gets `buildFallbackProvenance`. Only the
|
|
247
|
+
* provenance's OWN recorded version counts - never a project-derived one - and a
|
|
248
|
+
* dependency-like provenance is excluded, so a library's release number is never
|
|
249
|
+
* read as a Minecraft version. A jar row that predates the in-jar proof records no
|
|
250
|
+
* version and stays unflagged until a resolve proves the jar again, which backfills
|
|
251
|
+
* the row (`backfillResolvedVersion` in the indexer).
|
|
252
|
+
*/
|
|
253
|
+
function withUnobfuscatedRuntimeFlag(provenance) {
|
|
254
|
+
if (provenance.unobfuscatedRuntime === true ||
|
|
255
|
+
provenance.nestedJar ||
|
|
256
|
+
artifactResolver.isDependencyLikeArtifact({ provenance })) {
|
|
257
|
+
return provenance;
|
|
258
|
+
}
|
|
259
|
+
// A persisted or stubbed object can be partial; with nothing recorded there is
|
|
260
|
+
// nothing to derive the flag from.
|
|
261
|
+
const partial = provenance;
|
|
262
|
+
if (!partial.resolvedFrom || !partial.target) {
|
|
263
|
+
return provenance;
|
|
264
|
+
}
|
|
265
|
+
const version = unobfuscatedMinecraftVersion({
|
|
266
|
+
version: artifactResolver.inferVersionFromContext({
|
|
267
|
+
provenance,
|
|
268
|
+
coordinate: partial.resolvedFrom.coordinate
|
|
269
|
+
}),
|
|
270
|
+
nativeDependency: false
|
|
271
|
+
});
|
|
272
|
+
return version ? { ...provenance, unobfuscatedRuntime: true } : provenance;
|
|
273
|
+
}
|
|
180
274
|
export function buildFallbackProvenance(svc, input) {
|
|
181
275
|
const artifact = svc.getArtifact(input.artifactId);
|
|
182
276
|
const fallbackTarget = artifact.version
|
|
@@ -241,7 +335,21 @@ export function buildClassSourceNotFoundError(svc, input) {
|
|
|
241
335
|
// jar. Candidates from the artifact the caller did not name carry its id.
|
|
242
336
|
didYouMean: unionDidYouMeanCandidates(svc, requestedArtifactId, input.artifactId, input.className)
|
|
243
337
|
};
|
|
338
|
+
const unobfuscatedVersion = unobfuscatedMinecraftVersion({
|
|
339
|
+
version: input.version,
|
|
340
|
+
nativeDependency: input.nativeDependency === true
|
|
341
|
+
});
|
|
244
342
|
let nextAction = `Use find-class to resolve the correct fully-qualified name for "${simpleName}".`;
|
|
343
|
+
// On 26.1+ there is no namespace to switch to, so the most useful thing to say
|
|
344
|
+
// first is the nearest class the index does hold.
|
|
345
|
+
const nearestCandidate = unobfuscatedVersion
|
|
346
|
+
? details.didYouMean[0]
|
|
347
|
+
: undefined;
|
|
348
|
+
if (nearestCandidate) {
|
|
349
|
+
nextAction =
|
|
350
|
+
`Did you mean "${nearestCandidate.className}"? didYouMean lists near-miss classes from the index; ` +
|
|
351
|
+
`otherwise use find-class to resolve the correct fully-qualified name for "${simpleName}".`;
|
|
352
|
+
}
|
|
245
353
|
let suggestionSpec = {
|
|
246
354
|
tool: "find-class",
|
|
247
355
|
params: { className: simpleName, artifactId: requestedArtifactId }
|
|
@@ -291,16 +399,26 @@ export function buildClassSourceNotFoundError(svc, input) {
|
|
|
291
399
|
// imperative "Provide/Pass mapping", and the sentence is concatenated into
|
|
292
400
|
// the same `nextAction` string as the find-class guidance, which is published
|
|
293
401
|
// as a single hint that no mid-string excision can repair.
|
|
402
|
+
//
|
|
403
|
+
// A third gate lives inside `isObfuscatedNamespaceHintTrue`: on Minecraft 26.1+
|
|
404
|
+
// the claim is false, and the same miss gets a note that no mapping will help.
|
|
294
405
|
const callerAskedForNonObfuscatedMapping = input.callerSuppliedMapping != null && input.callerSuppliedMapping !== "obfuscated";
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
406
|
+
const namespaceHintInput = {
|
|
407
|
+
mappingApplied: input.mappingApplied,
|
|
408
|
+
qualityFlags: input.qualityFlags,
|
|
409
|
+
nativeDependency: input.nativeDependency === true,
|
|
410
|
+
className: input.className,
|
|
411
|
+
minecraftVersion: input.version
|
|
412
|
+
};
|
|
413
|
+
if (!callerAskedForNonObfuscatedMapping && isObfuscatedNamespaceHintTrue(namespaceHintInput)) {
|
|
302
414
|
nextAction += ` ${obfuscatedNamespaceHint(input.className)}`;
|
|
303
415
|
}
|
|
416
|
+
else if (!callerAskedForNonObfuscatedMapping &&
|
|
417
|
+
unobfuscatedVersion &&
|
|
418
|
+
!hasPartialNetMinecraftCoverage(input.qualityFlags) &&
|
|
419
|
+
isObfuscatedLabelMiss(namespaceHintInput)) {
|
|
420
|
+
nextAction += ` ${unobfuscatedRuntimeNamesNote(unobfuscatedVersion)}`;
|
|
421
|
+
}
|
|
304
422
|
details.nextAction = nextAction;
|
|
305
423
|
Object.assign(details, buildSuggestedCall(suggestionSpec));
|
|
306
424
|
// Split-source workspaces can omit client-only classes from merged indexes;
|
|
@@ -609,8 +727,23 @@ function finishFindClass(svc, input) {
|
|
|
609
727
|
}
|
|
610
728
|
}).suggestedCall;
|
|
611
729
|
}
|
|
612
|
-
if (matches.length === 0
|
|
613
|
-
|
|
730
|
+
if (matches.length === 0) {
|
|
731
|
+
const hintInput = describeArtifactForNamespaceHint(artifact, className);
|
|
732
|
+
const unobfuscatedVersion = unobfuscatedMinecraftVersion({
|
|
733
|
+
version: hintInput.minecraftVersion,
|
|
734
|
+
nativeDependency: hintInput.nativeDependency
|
|
735
|
+
});
|
|
736
|
+
if (isObfuscatedNamespaceHintTrue(hintInput)) {
|
|
737
|
+
warnings.push(`No exact class symbol matched "${className}". ${obfuscatedNamespaceHint(className)}`);
|
|
738
|
+
}
|
|
739
|
+
else if (unobfuscatedVersion && isObfuscatedLabelMiss(hintInput)) {
|
|
740
|
+
const nearest = collectDidYouMeanCandidates(svc, artifactId, className)
|
|
741
|
+
.slice(0, 3)
|
|
742
|
+
.map((candidate) => `"${candidate.className}"`);
|
|
743
|
+
warnings.push(`No exact class symbol matched "${className}".` +
|
|
744
|
+
(nearest.length > 0 ? ` Did you mean ${nearest.join(", ")}?` : "") +
|
|
745
|
+
` ${unobfuscatedRuntimeNamesNote(unobfuscatedVersion)}`);
|
|
746
|
+
}
|
|
614
747
|
}
|
|
615
748
|
return {
|
|
616
749
|
matches,
|
|
@@ -757,6 +890,12 @@ export async function getClassSource(svc, input) {
|
|
|
757
890
|
if (!artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })) {
|
|
758
891
|
mappingApplied = reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied);
|
|
759
892
|
}
|
|
893
|
+
// The same rule for the class-name lookup: its 26.1+ identity shortcut trusts only
|
|
894
|
+
// a Minecraft version, so a library's 26.x-shaped release number keeps the lookup
|
|
895
|
+
// (and its warnings) that any other library gets.
|
|
896
|
+
const minecraftVersion = artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })
|
|
897
|
+
? undefined
|
|
898
|
+
: version;
|
|
760
899
|
let activeArtifactId = artifactId;
|
|
761
900
|
let activeOrigin = origin;
|
|
762
901
|
let activeIsDecompiled = isDecompiled;
|
|
@@ -857,9 +996,10 @@ export async function getClassSource(svc, input) {
|
|
|
857
996
|
warnings.push(`Class "${className}" lives in nested jar "${match.entryName}" bundled by the shell jar; the lookup was redirected there automatically.`);
|
|
858
997
|
return true;
|
|
859
998
|
};
|
|
860
|
-
let activeLookupClassName = await svc
|
|
999
|
+
let activeLookupClassName = await resolveArtifactClassNameForLookup(svc, {
|
|
861
1000
|
className,
|
|
862
1001
|
version,
|
|
1002
|
+
minecraftVersion,
|
|
863
1003
|
sourceMapping: requestedMapping,
|
|
864
1004
|
targetMapping: activeMappingApplied,
|
|
865
1005
|
sourcePriority: input.sourcePriority,
|
|
@@ -872,9 +1012,10 @@ export async function getClassSource(svc, input) {
|
|
|
872
1012
|
filePath = resolveClassFilePath(svc, activeArtifactId, activeLookupClassName);
|
|
873
1013
|
}
|
|
874
1014
|
if (!filePath && (await tryBinaryFallback())) {
|
|
875
|
-
activeLookupClassName = await svc
|
|
1015
|
+
activeLookupClassName = await resolveArtifactClassNameForLookup(svc, {
|
|
876
1016
|
className,
|
|
877
1017
|
version,
|
|
1018
|
+
minecraftVersion,
|
|
878
1019
|
sourceMapping: requestedMapping,
|
|
879
1020
|
targetMapping: activeMappingApplied,
|
|
880
1021
|
sourcePriority: input.sourcePriority,
|
|
@@ -916,9 +1057,10 @@ export async function getClassSource(svc, input) {
|
|
|
916
1057
|
}
|
|
917
1058
|
}
|
|
918
1059
|
if (!row && (await tryBinaryFallback())) {
|
|
919
|
-
activeLookupClassName = await svc
|
|
1060
|
+
activeLookupClassName = await resolveArtifactClassNameForLookup(svc, {
|
|
920
1061
|
className,
|
|
921
1062
|
version,
|
|
1063
|
+
minecraftVersion,
|
|
922
1064
|
sourceMapping: requestedMapping,
|
|
923
1065
|
targetMapping: activeMappingApplied,
|
|
924
1066
|
sourcePriority: input.sourcePriority,
|
|
@@ -977,13 +1119,13 @@ export async function getClassSource(svc, input) {
|
|
|
977
1119
|
resolvedOutputFile = outputPath;
|
|
978
1120
|
sourceText = `[Written to ${outputPath}]`;
|
|
979
1121
|
}
|
|
980
|
-
const normalizedProvenance = activeProvenance ??
|
|
1122
|
+
const normalizedProvenance = withUnobfuscatedRuntimeFlag(activeProvenance ??
|
|
981
1123
|
buildFallbackProvenance(svc, {
|
|
982
1124
|
artifactId: activeArtifactId,
|
|
983
1125
|
origin: activeOrigin,
|
|
984
1126
|
requestedMapping,
|
|
985
1127
|
mappingApplied: activeMappingApplied
|
|
986
|
-
});
|
|
1128
|
+
}));
|
|
987
1129
|
const nextStartLine = snippet.nextStartLine;
|
|
988
1130
|
// Continuation guidance: when output was truncated and was not redirected to
|
|
989
1131
|
// a file, hand the caller the next line to read plus a replayable call that
|
|
@@ -1152,10 +1294,14 @@ export async function getClassMembers(svc, input) {
|
|
|
1152
1294
|
});
|
|
1153
1295
|
// Gated on the same predicate as the source path above, and for the same
|
|
1154
1296
|
// reason: a dependency's `version` is its own coordinate version, so the
|
|
1155
|
-
// unobfuscated-runtime relabel must not reach it.
|
|
1297
|
+
// unobfuscated-runtime relabel must not reach it - nor the 26.1+ identity
|
|
1298
|
+
// shortcut of the class and member lookups below.
|
|
1156
1299
|
if (!artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })) {
|
|
1157
1300
|
mappingApplied = reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied);
|
|
1158
1301
|
}
|
|
1302
|
+
const minecraftVersion = artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })
|
|
1303
|
+
? undefined
|
|
1304
|
+
: version;
|
|
1159
1305
|
if (requestedMapping !== "obfuscated" && !version) {
|
|
1160
1306
|
throw createError({
|
|
1161
1307
|
code: ERROR_CODES.MAPPING_NOT_APPLIED,
|
|
@@ -1222,9 +1368,10 @@ export async function getClassMembers(svc, input) {
|
|
|
1222
1368
|
}
|
|
1223
1369
|
});
|
|
1224
1370
|
}
|
|
1225
|
-
const lookupClassName = await svc
|
|
1371
|
+
const lookupClassName = await resolveArtifactClassNameForLookup(svc, {
|
|
1226
1372
|
className,
|
|
1227
1373
|
version,
|
|
1374
|
+
minecraftVersion,
|
|
1228
1375
|
sourceMapping: requestedMapping,
|
|
1229
1376
|
targetMapping: mappingApplied,
|
|
1230
1377
|
sourcePriority: input.sourcePriority,
|
|
@@ -1347,6 +1494,7 @@ export async function getClassMembers(svc, input) {
|
|
|
1347
1494
|
signatureFields,
|
|
1348
1495
|
signatureMethods,
|
|
1349
1496
|
version,
|
|
1497
|
+
minecraftVersion,
|
|
1350
1498
|
mappingApplied,
|
|
1351
1499
|
requestedMapping,
|
|
1352
1500
|
sourcePriority: input.sourcePriority,
|
|
@@ -1379,13 +1527,13 @@ export async function getClassMembers(svc, input) {
|
|
|
1379
1527
|
const projectedMembers = projectMembersByLevel(projectMembersForWire({ constructors, fields, methods }, includeInherited, input.includeDescriptors ?? false), projection);
|
|
1380
1528
|
const truncated = sliced.truncated;
|
|
1381
1529
|
const nextCursor = sliced.nextOffset != null ? encodeOffsetCursor(sliced.nextOffset, memberCursorContext) : undefined;
|
|
1382
|
-
const baseProvenance = provenance ??
|
|
1530
|
+
const baseProvenance = withUnobfuscatedRuntimeFlag(provenance ??
|
|
1383
1531
|
buildFallbackProvenance(svc, {
|
|
1384
1532
|
artifactId,
|
|
1385
1533
|
origin,
|
|
1386
1534
|
requestedMapping,
|
|
1387
1535
|
mappingApplied
|
|
1388
|
-
});
|
|
1536
|
+
}));
|
|
1389
1537
|
const normalizedProvenance = nestedJarRedirect
|
|
1390
1538
|
? { ...baseProvenance, nestedJar: nestedJarRedirect }
|
|
1391
1539
|
: baseProvenance;
|
package/dist/source/indexer.js
CHANGED
|
@@ -415,8 +415,11 @@ export async function ingestIfNeeded(svc, resolved) {
|
|
|
415
415
|
if (resolved.artifactAlias && existing.alias !== resolved.artifactAlias) {
|
|
416
416
|
svc.artifactsRepo.setAlias(resolved.artifactId, resolved.artifactAlias);
|
|
417
417
|
}
|
|
418
|
-
svc.metrics.recordArtifactCacheHit();
|
|
419
418
|
const touchedAt = new Date().toISOString();
|
|
419
|
+
if (!existing.version) {
|
|
420
|
+
backfillResolvedVersion(svc, resolved, touchedAt);
|
|
421
|
+
}
|
|
422
|
+
svc.metrics.recordArtifactCacheHit();
|
|
420
423
|
svc.artifactsRepo.touchArtifact(resolved.artifactId, touchedAt);
|
|
421
424
|
touchCacheMetrics(svc, resolved.artifactId, touchedAt);
|
|
422
425
|
return;
|
|
@@ -444,6 +447,71 @@ export async function ingestIfNeeded(svc, resolved) {
|
|
|
444
447
|
}
|
|
445
448
|
}
|
|
446
449
|
}
|
|
450
|
+
/**
|
|
451
|
+
* Writes the version a warm resolve derived onto a stored row that records none,
|
|
452
|
+
* with the two provenance fields that follow from it.
|
|
453
|
+
*
|
|
454
|
+
* The artifactId is a content hash, so a row indexed before its version could be
|
|
455
|
+
* derived - a Minecraft 26.1+ runtime jar indexed before the in-jar proof existed -
|
|
456
|
+
* is served warm by every later resolve and never rewritten. resolve-artifact then
|
|
457
|
+
* reports the version while the row lacks it, and every lookup by artifactId reads
|
|
458
|
+
* the row: get-class-members refuses mapping "mojang" for want of a version, and a
|
|
459
|
+
* miss still gets the legacy "use mapping=mojang" hint.
|
|
460
|
+
*
|
|
461
|
+
* Only gaps are filled. A stored version is never replaced, and the row keeps the
|
|
462
|
+
* label, chain and target it was indexed under. Only a version the fresh provenance
|
|
463
|
+
* records as resolved from is written: resolve-artifact records the version the
|
|
464
|
+
* target itself names (version target, coordinate or in-jar proof), which describes
|
|
465
|
+
* these bytes, while the binary fallback of get-class-source re-ingests with a
|
|
466
|
+
* caller version that may come from the project's gradle.properties, under the
|
|
467
|
+
* stored provenance, so it never qualifies. The row is re-read in the same
|
|
468
|
+
* synchronous turn as the write, so a concurrent writer since `existing` was read
|
|
469
|
+
* is not overwritten.
|
|
470
|
+
* `upsertArtifact` is the repo's only writer for these columns; on an existing row
|
|
471
|
+
* it updates in place and leaves `created_at` and the file index alone.
|
|
472
|
+
*/
|
|
473
|
+
function backfillResolvedVersion(svc, resolved, timestamp) {
|
|
474
|
+
const version = resolved.version;
|
|
475
|
+
if (!version || resolved.provenance?.resolvedFrom?.version !== version) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// Only a proven Minecraft 26.1+ runtime qualifies. A library version recorded by a
|
|
479
|
+
// dependency resolve must never land on a row whose stored provenance does not mark
|
|
480
|
+
// it as a dependency, or later lookups would read that version as Minecraft's.
|
|
481
|
+
if (resolved.provenance?.unobfuscatedRuntime !== true) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const current = svc.artifactsRepo.getArtifact(resolved.artifactId);
|
|
485
|
+
if (!current || current.version) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
svc.artifactsRepo.upsertArtifact({
|
|
489
|
+
artifactId: current.artifactId,
|
|
490
|
+
alias: current.alias,
|
|
491
|
+
origin: current.origin,
|
|
492
|
+
coordinate: current.coordinate,
|
|
493
|
+
version,
|
|
494
|
+
binaryJarPath: current.binaryJarPath,
|
|
495
|
+
sourceJarPath: current.sourceJarPath,
|
|
496
|
+
repoUrl: current.repoUrl,
|
|
497
|
+
requestedMapping: current.requestedMapping,
|
|
498
|
+
mappingApplied: current.mappingApplied,
|
|
499
|
+
provenance: current.provenance
|
|
500
|
+
? {
|
|
501
|
+
...current.provenance,
|
|
502
|
+
resolvedFrom: {
|
|
503
|
+
...current.provenance.resolvedFrom,
|
|
504
|
+
version: current.provenance.resolvedFrom?.version ?? version
|
|
505
|
+
},
|
|
506
|
+
unobfuscatedRuntime: true
|
|
507
|
+
}
|
|
508
|
+
: undefined,
|
|
509
|
+
qualityFlags: current.qualityFlags,
|
|
510
|
+
artifactSignature: current.artifactSignature,
|
|
511
|
+
isDecompiled: current.isDecompiled,
|
|
512
|
+
timestamp
|
|
513
|
+
});
|
|
514
|
+
}
|
|
447
515
|
async function rebuildMissingArtifactIndex(svc, resolved, reason) {
|
|
448
516
|
svc.metrics.recordArtifactCacheMiss();
|
|
449
517
|
svc.metrics.recordReindex();
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { SignatureMember } from "../../minecraft-explorer-service.js";
|
|
2
2
|
import type { SourceService } from "../../source-service.js";
|
|
3
3
|
import type { MappingSourcePriority, SourceMapping } from "../../types.js";
|
|
4
|
+
/**
|
|
5
|
+
* On unobfuscated versions (26.1+) the as-shipped ("obfuscated") names are the Mojang
|
|
6
|
+
* names, so obfuscated<->mojang is the identity. The mapping graph is empty there, so
|
|
7
|
+
* asking it would only report every member as unmapped.
|
|
8
|
+
*
|
|
9
|
+
* `minecraftVersion` must be a proven Minecraft version. A dependency
|
|
10
|
+
* artifact's version is its own release number (`annotations:26.0.2`), so callers
|
|
11
|
+
* serving one pass undefined and keep the mapping lookup it always had.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isUnobfuscatedIdentityPair(minecraftVersion: string | undefined, sourceMapping: SourceMapping, targetMapping: SourceMapping): boolean;
|
|
4
14
|
export declare function normalizeMapping(mapping: SourceMapping | undefined): SourceMapping;
|
|
5
15
|
export declare function rejectLifecycleClassLikeInput(svc: SourceService, input: {
|
|
6
16
|
symbol: string;
|
|
@@ -16,7 +26,16 @@ export declare function resolveToObfuscatedMemberName(svc: SourceService, name:
|
|
|
16
26
|
name: string;
|
|
17
27
|
descriptor?: string;
|
|
18
28
|
}>;
|
|
19
|
-
export declare function remapSignatureMembers(svc: SourceService, members: SignatureMember[], kind: "field" | "method", version: string, sourceMapping: SourceMapping, targetMapping: SourceMapping, sourcePriority: MappingSourcePriority | undefined, warnings: string[], projectPath?: string, gradleUserHome?: string
|
|
29
|
+
export declare function remapSignatureMembers(svc: SourceService, members: SignatureMember[], kind: "field" | "method", version: string, sourceMapping: SourceMapping, targetMapping: SourceMapping, sourcePriority: MappingSourcePriority | undefined, warnings: string[], projectPath?: string, gradleUserHome?: string,
|
|
30
|
+
/**
|
|
31
|
+
* The Minecraft version the obfuscated<->mojang identity shortcut may trust.
|
|
32
|
+
* Defaults to `version`, which the lifecycle, validate-mixin and access-widener
|
|
33
|
+
* callers pass as a Minecraft version; get-class-members passes undefined for a
|
|
34
|
+
* dependency artifact, whose `version` is the library's own.
|
|
35
|
+
*/
|
|
36
|
+
identity?: {
|
|
37
|
+
minecraftVersion: string | undefined;
|
|
38
|
+
}): Promise<{
|
|
20
39
|
members: SignatureMember[];
|
|
21
40
|
failedNames: Set<string>;
|
|
22
41
|
}>;
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
import { buildSuggestedCall } from "../../build-suggested-call.js";
|
|
2
2
|
import { ERROR_CODES, createError } from "../../errors.js";
|
|
3
|
+
import { isUnobfuscatedVersion } from "../../version-service.js";
|
|
3
4
|
import { rebuildJavaSignature, remapJvmDescriptor } from "../descriptor-utils.js";
|
|
5
|
+
/**
|
|
6
|
+
* On unobfuscated versions (26.1+) the as-shipped ("obfuscated") names are the Mojang
|
|
7
|
+
* names, so obfuscated<->mojang is the identity. The mapping graph is empty there, so
|
|
8
|
+
* asking it would only report every member as unmapped.
|
|
9
|
+
*
|
|
10
|
+
* `minecraftVersion` must be a proven Minecraft version. A dependency
|
|
11
|
+
* artifact's version is its own release number (`annotations:26.0.2`), so callers
|
|
12
|
+
* serving one pass undefined and keep the mapping lookup it always had.
|
|
13
|
+
*/
|
|
14
|
+
export function isUnobfuscatedIdentityPair(minecraftVersion, sourceMapping, targetMapping) {
|
|
15
|
+
const pair = new Set([sourceMapping, targetMapping]);
|
|
16
|
+
return (pair.size === 2 &&
|
|
17
|
+
pair.has("obfuscated") &&
|
|
18
|
+
pair.has("mojang") &&
|
|
19
|
+
minecraftVersion !== undefined &&
|
|
20
|
+
isUnobfuscatedVersion(minecraftVersion));
|
|
21
|
+
}
|
|
4
22
|
export function normalizeMapping(mapping) {
|
|
5
23
|
if (mapping == null) {
|
|
6
24
|
return "obfuscated";
|
|
@@ -87,7 +105,7 @@ export async function resolveToObfuscatedClassName(svc, className, version, mapp
|
|
|
87
105
|
});
|
|
88
106
|
}
|
|
89
107
|
export async function resolveToObfuscatedMemberName(svc, name, ownerInSourceMapping, descriptor, kind, version, mapping, sourcePriority, warnings, gradleUserHome) {
|
|
90
|
-
if (mapping === "obfuscated") {
|
|
108
|
+
if (mapping === "obfuscated" || isUnobfuscatedIdentityPair(version, mapping, "obfuscated")) {
|
|
91
109
|
return {
|
|
92
110
|
name,
|
|
93
111
|
descriptor: kind === "method" ? descriptor : undefined
|
|
@@ -167,9 +185,17 @@ export async function resolveToObfuscatedMemberName(svc, name, ownerInSourceMapp
|
|
|
167
185
|
descriptor: kind === "method" ? descriptor : undefined
|
|
168
186
|
};
|
|
169
187
|
}
|
|
170
|
-
export async function remapSignatureMembers(svc, members, kind, version, sourceMapping, targetMapping, sourcePriority, warnings, projectPath, gradleUserHome
|
|
188
|
+
export async function remapSignatureMembers(svc, members, kind, version, sourceMapping, targetMapping, sourcePriority, warnings, projectPath, gradleUserHome,
|
|
189
|
+
/**
|
|
190
|
+
* The Minecraft version the obfuscated<->mojang identity shortcut may trust.
|
|
191
|
+
* Defaults to `version`, which the lifecycle, validate-mixin and access-widener
|
|
192
|
+
* callers pass as a Minecraft version; get-class-members passes undefined for a
|
|
193
|
+
* dependency artifact, whose `version` is the library's own.
|
|
194
|
+
*/
|
|
195
|
+
identity = { minecraftVersion: version }) {
|
|
171
196
|
const failedNames = new Set();
|
|
172
|
-
if (sourceMapping === targetMapping
|
|
197
|
+
if (sourceMapping === targetMapping ||
|
|
198
|
+
isUnobfuscatedIdentityPair(identity.minecraftVersion, sourceMapping, targetMapping)) {
|
|
173
199
|
return { members, failedNames };
|
|
174
200
|
}
|
|
175
201
|
const memberKeyToRemapped = new Map();
|
|
@@ -1,2 +1,27 @@
|
|
|
1
1
|
import type { CheckSymbolExistsInput, CheckSymbolExistsOutput, SourceService } from "../../source-service.js";
|
|
2
|
+
/**
|
|
3
|
+
* What a runtime-bytecode existence check established.
|
|
4
|
+
*
|
|
5
|
+
* `verified` is true when the runtime jar answered: the class loaded and the member
|
|
6
|
+
* lookup ran to completion, or the class was confirmed missing (CLASS_NOT_FOUND). It
|
|
7
|
+
* is false when the check could not be performed - a short class name, a member query
|
|
8
|
+
* without an owner, or a class that failed to load for any other reason (unreadable
|
|
9
|
+
* jar, malformed class file) - and `result` is then the fallback base with the
|
|
10
|
+
* explanatory warning appended, so it still carries the caller's own status.
|
|
11
|
+
*/
|
|
12
|
+
export interface UnobfuscatedRuntimeCheck {
|
|
13
|
+
verified: boolean;
|
|
14
|
+
result: CheckSymbolExistsOutput;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* check-symbol-exists's runtime fallback: the result of `runUnobfuscatedRuntimeCheck`
|
|
18
|
+
* alone. Its callers keep the fallback base's status whenever the check could not be
|
|
19
|
+
* performed, so they need no `verified` flag.
|
|
20
|
+
*/
|
|
2
21
|
export declare function checkSymbolExistsInUnobfuscatedRuntime(svc: SourceService, input: CheckSymbolExistsInput, fallbackBase: CheckSymbolExistsOutput): Promise<CheckSymbolExistsOutput | undefined>;
|
|
22
|
+
/**
|
|
23
|
+
* Checks a symbol against the Minecraft runtime jar of an unobfuscated version, whose
|
|
24
|
+
* names are the Mojang names. Undefined when the version or name is empty or the
|
|
25
|
+
* runtime jar cannot be resolved.
|
|
26
|
+
*/
|
|
27
|
+
export declare function runUnobfuscatedRuntimeCheck(svc: SourceService, input: CheckSymbolExistsInput, fallbackBase: CheckSymbolExistsOutput): Promise<UnobfuscatedRuntimeCheck | undefined>;
|