@adhisang/minecraft-modding-mcp 7.0.0 → 7.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 +72 -0
- package/README.md +4 -3
- package/dist/access-transformer-parser.d.ts +8 -0
- package/dist/access-transformer-parser.js +8 -1
- package/dist/access-widener-parser.d.ts +17 -0
- package/dist/access-widener-parser.js +12 -1
- package/dist/cache-registry.js +19 -6
- 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/handlers/versions.js +15 -9
- 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/manage-cache-service.js +10 -14
- package/dist/entry-tools/validate-project/cases/access-transformer.js +22 -3
- package/dist/entry-tools/validate-project/cases/access-widener.js +31 -4
- package/dist/entry-tools/validate-project/cases/mixin.js +11 -3
- package/dist/entry-tools/validate-project/cases/project-summary.js +94 -16
- package/dist/entry-tools/validate-project-service.d.ts +2 -2
- package/dist/entry-tools/verify-mixin-target-service.js +19 -8
- package/dist/index.js +38 -15
- package/dist/java-process.d.ts +1 -0
- package/dist/java-process.js +14 -0
- package/dist/mapping/lookup.js +16 -1
- package/dist/mapping-service.d.ts +14 -0
- package/dist/mapping-service.js +35 -15
- package/dist/minecraft-explorer-service.js +70 -8
- package/dist/mixin/access-validators.js +38 -2
- package/dist/mixin/annotation-validators.js +137 -43
- package/dist/mixin/parsed-validator.js +21 -7
- package/dist/mixin-parser.d.ts +52 -0
- package/dist/mixin-parser.js +709 -130
- package/dist/mod-decompile-service.js +11 -1
- package/dist/mod-remap-service.js +6 -6
- package/dist/nbt/java-nbt-codec.js +7 -1
- package/dist/source/access-validate.js +10 -0
- package/dist/source/artifact-resolver.d.ts +27 -3
- package/dist/source/artifact-resolver.js +235 -30
- 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 +186 -27
- 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/nested-jars.d.ts +15 -1
- package/dist/source/nested-jars.js +14 -5
- package/dist/source/search.d.ts +10 -2
- package/dist/source/search.js +60 -13
- package/dist/source/symbol-resolver.js +88 -0
- package/dist/source/validate-mixin/pipeline/mapping-health.js +20 -1
- package/dist/source/validate-mixin/pipeline/target-lookup.js +16 -7
- package/dist/source/validate-mixin.d.ts +5 -0
- package/dist/source/validate-mixin.js +136 -21
- package/dist/source/workspace-target.js +75 -7
- package/dist/source-jar-reader.d.ts +48 -1
- package/dist/source-jar-reader.js +93 -3
- package/dist/source-resolver.d.ts +7 -0
- package/dist/source-resolver.js +22 -14
- package/dist/source-service.d.ts +5 -0
- package/dist/source-service.js +7 -0
- package/dist/stdio-supervisor.d.ts +35 -1
- package/dist/stdio-supervisor.js +77 -2
- package/dist/storage/db.d.ts +62 -2
- package/dist/storage/db.js +181 -20
- package/dist/storage/files-repo.d.ts +7 -0
- package/dist/storage/files-repo.js +17 -4
- package/dist/storage/sqlite.d.ts +31 -1
- package/dist/storage/sqlite.js +125 -16
- package/dist/tool-contract-manifest.js +2 -2
- package/dist/tool-execution-gate.js +2 -1
- 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 +52 -3
- package/dist/workspace-context-cache.d.ts +25 -0
- package/dist/workspace-context-cache.js +52 -2
- package/dist/workspace-mapping-service.d.ts +8 -0
- package/dist/workspace-mapping-service.js +151 -21
- package/docs/README-ja.md +3 -1
- package/docs/tool-reference.md +69 -22
- 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;
|
|
@@ -508,7 +626,8 @@ export function findClass(svc, input) {
|
|
|
508
626
|
qualifiedName: isInnerMatch ? className : rowQualified,
|
|
509
627
|
filePath: row.filePath,
|
|
510
628
|
line: row.line,
|
|
511
|
-
symbolKind: row.symbolKind
|
|
629
|
+
symbolKind: row.symbolKind,
|
|
630
|
+
...(isInnerMatch ? { nested: true, enclosingClass: rowQualified } : {})
|
|
512
631
|
};
|
|
513
632
|
})
|
|
514
633
|
.slice(0, limit);
|
|
@@ -609,8 +728,23 @@ function finishFindClass(svc, input) {
|
|
|
609
728
|
}
|
|
610
729
|
}).suggestedCall;
|
|
611
730
|
}
|
|
612
|
-
if (matches.length === 0
|
|
613
|
-
|
|
731
|
+
if (matches.length === 0) {
|
|
732
|
+
const hintInput = describeArtifactForNamespaceHint(artifact, className);
|
|
733
|
+
const unobfuscatedVersion = unobfuscatedMinecraftVersion({
|
|
734
|
+
version: hintInput.minecraftVersion,
|
|
735
|
+
nativeDependency: hintInput.nativeDependency
|
|
736
|
+
});
|
|
737
|
+
if (isObfuscatedNamespaceHintTrue(hintInput)) {
|
|
738
|
+
warnings.push(`No exact class symbol matched "${className}". ${obfuscatedNamespaceHint(className)}`);
|
|
739
|
+
}
|
|
740
|
+
else if (unobfuscatedVersion && isObfuscatedLabelMiss(hintInput)) {
|
|
741
|
+
const nearest = collectDidYouMeanCandidates(svc, artifactId, className)
|
|
742
|
+
.slice(0, 3)
|
|
743
|
+
.map((candidate) => `"${candidate.className}"`);
|
|
744
|
+
warnings.push(`No exact class symbol matched "${className}".` +
|
|
745
|
+
(nearest.length > 0 ? ` Did you mean ${nearest.join(", ")}?` : "") +
|
|
746
|
+
` ${unobfuscatedRuntimeNamesNote(unobfuscatedVersion)}`);
|
|
747
|
+
}
|
|
614
748
|
}
|
|
615
749
|
return {
|
|
616
750
|
matches,
|
|
@@ -757,6 +891,12 @@ export async function getClassSource(svc, input) {
|
|
|
757
891
|
if (!artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })) {
|
|
758
892
|
mappingApplied = reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied);
|
|
759
893
|
}
|
|
894
|
+
// The same rule for the class-name lookup: its 26.1+ identity shortcut trusts only
|
|
895
|
+
// a Minecraft version, so a library's 26.x-shaped release number keeps the lookup
|
|
896
|
+
// (and its warnings) that any other library gets.
|
|
897
|
+
const minecraftVersion = artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })
|
|
898
|
+
? undefined
|
|
899
|
+
: version;
|
|
760
900
|
let activeArtifactId = artifactId;
|
|
761
901
|
let activeOrigin = origin;
|
|
762
902
|
let activeIsDecompiled = isDecompiled;
|
|
@@ -857,9 +997,10 @@ export async function getClassSource(svc, input) {
|
|
|
857
997
|
warnings.push(`Class "${className}" lives in nested jar "${match.entryName}" bundled by the shell jar; the lookup was redirected there automatically.`);
|
|
858
998
|
return true;
|
|
859
999
|
};
|
|
860
|
-
let activeLookupClassName = await svc
|
|
1000
|
+
let activeLookupClassName = await resolveArtifactClassNameForLookup(svc, {
|
|
861
1001
|
className,
|
|
862
1002
|
version,
|
|
1003
|
+
minecraftVersion,
|
|
863
1004
|
sourceMapping: requestedMapping,
|
|
864
1005
|
targetMapping: activeMappingApplied,
|
|
865
1006
|
sourcePriority: input.sourcePriority,
|
|
@@ -872,9 +1013,10 @@ export async function getClassSource(svc, input) {
|
|
|
872
1013
|
filePath = resolveClassFilePath(svc, activeArtifactId, activeLookupClassName);
|
|
873
1014
|
}
|
|
874
1015
|
if (!filePath && (await tryBinaryFallback())) {
|
|
875
|
-
activeLookupClassName = await svc
|
|
1016
|
+
activeLookupClassName = await resolveArtifactClassNameForLookup(svc, {
|
|
876
1017
|
className,
|
|
877
1018
|
version,
|
|
1019
|
+
minecraftVersion,
|
|
878
1020
|
sourceMapping: requestedMapping,
|
|
879
1021
|
targetMapping: activeMappingApplied,
|
|
880
1022
|
sourcePriority: input.sourcePriority,
|
|
@@ -916,9 +1058,10 @@ export async function getClassSource(svc, input) {
|
|
|
916
1058
|
}
|
|
917
1059
|
}
|
|
918
1060
|
if (!row && (await tryBinaryFallback())) {
|
|
919
|
-
activeLookupClassName = await svc
|
|
1061
|
+
activeLookupClassName = await resolveArtifactClassNameForLookup(svc, {
|
|
920
1062
|
className,
|
|
921
1063
|
version,
|
|
1064
|
+
minecraftVersion,
|
|
922
1065
|
sourceMapping: requestedMapping,
|
|
923
1066
|
targetMapping: activeMappingApplied,
|
|
924
1067
|
sourcePriority: input.sourcePriority,
|
|
@@ -973,17 +1116,27 @@ export async function getClassSource(svc, input) {
|
|
|
973
1116
|
const outputPath = isAbsolute(outputFile)
|
|
974
1117
|
? outputFile
|
|
975
1118
|
: resolvePath(outputFile);
|
|
976
|
-
|
|
1119
|
+
try {
|
|
1120
|
+
await writeFile(outputPath, sourceText, "utf8");
|
|
1121
|
+
}
|
|
1122
|
+
catch (writeError) {
|
|
1123
|
+
const errno = writeError?.code;
|
|
1124
|
+
throw createError({
|
|
1125
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
1126
|
+
message: `outputFile "${outputPath}" could not be written (${errno ?? "unknown error"}).`,
|
|
1127
|
+
details: { field: "outputFile", value: outputPath, osErrorCode: errno }
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
977
1130
|
resolvedOutputFile = outputPath;
|
|
978
1131
|
sourceText = `[Written to ${outputPath}]`;
|
|
979
1132
|
}
|
|
980
|
-
const normalizedProvenance = activeProvenance ??
|
|
1133
|
+
const normalizedProvenance = withUnobfuscatedRuntimeFlag(activeProvenance ??
|
|
981
1134
|
buildFallbackProvenance(svc, {
|
|
982
1135
|
artifactId: activeArtifactId,
|
|
983
1136
|
origin: activeOrigin,
|
|
984
1137
|
requestedMapping,
|
|
985
1138
|
mappingApplied: activeMappingApplied
|
|
986
|
-
});
|
|
1139
|
+
}));
|
|
987
1140
|
const nextStartLine = snippet.nextStartLine;
|
|
988
1141
|
// Continuation guidance: when output was truncated and was not redirected to
|
|
989
1142
|
// a file, hand the caller the next line to read plus a replayable call that
|
|
@@ -1152,10 +1305,14 @@ export async function getClassMembers(svc, input) {
|
|
|
1152
1305
|
});
|
|
1153
1306
|
// Gated on the same predicate as the source path above, and for the same
|
|
1154
1307
|
// reason: a dependency's `version` is its own coordinate version, so the
|
|
1155
|
-
// unobfuscated-runtime relabel must not reach it.
|
|
1308
|
+
// unobfuscated-runtime relabel must not reach it - nor the 26.1+ identity
|
|
1309
|
+
// shortcut of the class and member lookups below.
|
|
1156
1310
|
if (!artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })) {
|
|
1157
1311
|
mappingApplied = reconcileUnobfuscatedNamespace(version, requestedMapping, mappingApplied);
|
|
1158
1312
|
}
|
|
1313
|
+
const minecraftVersion = artifactResolver.isDependencyLikeArtifact({ provenance, coordinate })
|
|
1314
|
+
? undefined
|
|
1315
|
+
: version;
|
|
1159
1316
|
if (requestedMapping !== "obfuscated" && !version) {
|
|
1160
1317
|
throw createError({
|
|
1161
1318
|
code: ERROR_CODES.MAPPING_NOT_APPLIED,
|
|
@@ -1222,9 +1379,10 @@ export async function getClassMembers(svc, input) {
|
|
|
1222
1379
|
}
|
|
1223
1380
|
});
|
|
1224
1381
|
}
|
|
1225
|
-
const lookupClassName = await svc
|
|
1382
|
+
const lookupClassName = await resolveArtifactClassNameForLookup(svc, {
|
|
1226
1383
|
className,
|
|
1227
1384
|
version,
|
|
1385
|
+
minecraftVersion,
|
|
1228
1386
|
sourceMapping: requestedMapping,
|
|
1229
1387
|
targetMapping: mappingApplied,
|
|
1230
1388
|
sourcePriority: input.sourcePriority,
|
|
@@ -1347,6 +1505,7 @@ export async function getClassMembers(svc, input) {
|
|
|
1347
1505
|
signatureFields,
|
|
1348
1506
|
signatureMethods,
|
|
1349
1507
|
version,
|
|
1508
|
+
minecraftVersion,
|
|
1350
1509
|
mappingApplied,
|
|
1351
1510
|
requestedMapping,
|
|
1352
1511
|
sourcePriority: input.sourcePriority,
|
|
@@ -1379,13 +1538,13 @@ export async function getClassMembers(svc, input) {
|
|
|
1379
1538
|
const projectedMembers = projectMembersByLevel(projectMembersForWire({ constructors, fields, methods }, includeInherited, input.includeDescriptors ?? false), projection);
|
|
1380
1539
|
const truncated = sliced.truncated;
|
|
1381
1540
|
const nextCursor = sliced.nextOffset != null ? encodeOffsetCursor(sliced.nextOffset, memberCursorContext) : undefined;
|
|
1382
|
-
const baseProvenance = provenance ??
|
|
1541
|
+
const baseProvenance = withUnobfuscatedRuntimeFlag(provenance ??
|
|
1383
1542
|
buildFallbackProvenance(svc, {
|
|
1384
1543
|
artifactId,
|
|
1385
1544
|
origin,
|
|
1386
1545
|
requestedMapping,
|
|
1387
1546
|
mappingApplied
|
|
1388
|
-
});
|
|
1547
|
+
}));
|
|
1389
1548
|
const normalizedProvenance = nestedJarRedirect
|
|
1390
1549
|
? { ...baseProvenance, nestedJar: nestedJarRedirect }
|
|
1391
1550
|
: 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>;
|