@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
|
@@ -135,7 +135,17 @@ export class ModDecompileService {
|
|
|
135
135
|
const outPath = isAbsolute(input.outputFile)
|
|
136
136
|
? input.outputFile
|
|
137
137
|
: resolvePath(input.outputFile);
|
|
138
|
-
|
|
138
|
+
try {
|
|
139
|
+
await writeFile(outPath, content, "utf8");
|
|
140
|
+
}
|
|
141
|
+
catch (writeError) {
|
|
142
|
+
const errno = writeError?.code;
|
|
143
|
+
throw createError({
|
|
144
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
145
|
+
message: `outputFile "${outPath}" could not be written (${errno ?? "unknown error"}).`,
|
|
146
|
+
details: { field: "outputFile", value: outPath, osErrorCode: errno }
|
|
147
|
+
});
|
|
148
|
+
}
|
|
139
149
|
outputFilePath = outPath;
|
|
140
150
|
content = `[Written to ${outPath}]`;
|
|
141
151
|
}
|
|
@@ -149,13 +149,13 @@ export async function remapModJar(input, config, deps = {}) {
|
|
|
149
149
|
mkdirSync(cacheDir, { recursive: true });
|
|
150
150
|
const cachedOutput = join(cacheDir, `${cacheKey}.jar`);
|
|
151
151
|
if (!input.forceRemap && existsSync(cachedOutput)) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
copyJarToDestination(cachedOutput,
|
|
156
|
-
log("info", "remap.cache-hit", { inputJar: normalizedInput, outputJar
|
|
152
|
+
// Always copy to the same `outputJar` location a cold run would use (the
|
|
153
|
+
// caller-supplied path, or resolveOutputJarPath's "next to input" default),
|
|
154
|
+
// so a cache hit and a cold run return the same path convention.
|
|
155
|
+
copyJarToDestination(cachedOutput, outputJar);
|
|
156
|
+
log("info", "remap.cache-hit", { inputJar: normalizedInput, outputJar });
|
|
157
157
|
return {
|
|
158
|
-
outputJar
|
|
158
|
+
outputJar,
|
|
159
159
|
mcVersion,
|
|
160
160
|
fromMapping: fromNamespace,
|
|
161
161
|
targetMapping: input.targetMapping,
|
|
@@ -480,7 +480,13 @@ export function decodeJavaNbt(buffer) {
|
|
|
480
480
|
throw error;
|
|
481
481
|
}
|
|
482
482
|
}
|
|
483
|
-
|
|
483
|
+
// Unlike every other parseError() call site in this module, nothing here
|
|
484
|
+
// has an offset to report - do not inherit PARSE_NEXT_ACTION's "at the
|
|
485
|
+
// reported offset" promise.
|
|
486
|
+
throw parseError("Failed to decode Java NBT payload.", {
|
|
487
|
+
nextAction: "The payload is not a well-formed Java NBT stream. Check that nbtBase64 holds the whole " +
|
|
488
|
+
'file and that compression matches how it was written (use compression "auto" to detect gzip).'
|
|
489
|
+
});
|
|
484
490
|
}
|
|
485
491
|
}
|
|
486
492
|
export function encodeJavaNbt(document) {
|
|
@@ -80,6 +80,16 @@ export async function validateAccessWidener(svc, input) {
|
|
|
80
80
|
if (!content.trim()) {
|
|
81
81
|
throw createError({ code: ERROR_CODES.INVALID_INPUT, message: "content must be non-empty." });
|
|
82
82
|
}
|
|
83
|
+
// Fabric Loader also reads the classTweaker format; it is out of scope here rather than an
|
|
84
|
+
// accessWidener file with a bad header, so it gets no verdict.
|
|
85
|
+
const header = content.split(/\r?\n/).map((line) => line.trim()).find((line) => line && !line.startsWith("#"));
|
|
86
|
+
if (header && /^classTweaker(\s|$)/.test(header)) {
|
|
87
|
+
throw createError({
|
|
88
|
+
code: ERROR_CODES.INVALID_INPUT,
|
|
89
|
+
message: `The classTweaker format is not supported (header "${header}"); only accessWidener v1/v2 files can be validated.`,
|
|
90
|
+
details: { unsupportedFormat: "classTweaker", header }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
83
93
|
const warnings = [];
|
|
84
94
|
const parsed = parseAccessWidener(content);
|
|
85
95
|
const headerNamespaceRaw = normalizeOptionalString(parsed.namespace);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type JavaSourceScan } from "../source-jar-reader.js";
|
|
1
2
|
import type { SourceService } from "../source-service.js";
|
|
2
3
|
import type { ArtifactContentsSummary, ProbeMinecraftArtifactInput, ProbeMinecraftArtifactOutput, ResolveArtifactInput, ResolveArtifactOutput } from "../source-service.js";
|
|
3
4
|
import type { AccessTransformerNamespace, ArtifactProvenance, ArtifactScope, ArtifactTargetKind, ResolvedSourceArtifact, RuntimeLoader, RuntimeValidationProvenance, SourceMapping } from "../types.js";
|
|
@@ -139,10 +140,31 @@ export declare function hasLoaderRuntimeVersionToken(path: string, mcVersion: st
|
|
|
139
140
|
/**
|
|
140
141
|
* Minecraft version a runtime jar path carries, or undefined when the path
|
|
141
142
|
* names none. Loom lays its cache out as
|
|
142
|
-
* `<gradle>/caches/fabric-loom/<mcVersion>/...`, so the first
|
|
143
|
-
* of the path
|
|
143
|
+
* `<gradle>/caches/fabric-loom/<mcVersion>/...`, so the first token of the
|
|
144
|
+
* tool-written part of the path ({@link runtimeJarLayoutTail}; the whole path
|
|
145
|
+
* when it enters no layout root) that names a Minecraft version is the version the jar was built for — in
|
|
146
|
+
* either id family, and a pre-release names itself and not the release it
|
|
147
|
+
* precedes. Reading only `1.x[.y]` left every 26.x jar versionless, so no
|
|
148
|
+
* approximation was ever reported for one.
|
|
149
|
+
*
|
|
150
|
+
* A legacy `1.x` token is self-identifying; a 26.1+ token shares its shape with
|
|
151
|
+
* every loader's own version number, so it additionally has to sit where
|
|
152
|
+
* {@link tokenPositionNamesMinecraft} says Minecraft's version sits.
|
|
144
153
|
*/
|
|
145
154
|
export declare function inferRuntimeJarMinecraftVersion(path: string): string | undefined;
|
|
155
|
+
/**
|
|
156
|
+
* The Minecraft version a `kind: "jar"` target PROVES it is the unobfuscated
|
|
157
|
+
* runtime of, or undefined. The file path is never evidence: Loom and other
|
|
158
|
+
* tools lay jars out in ways that path heuristics have misread before.
|
|
159
|
+
*
|
|
160
|
+
* All three must hold: the walk met no `.java` entry, the jar ships
|
|
161
|
+
* `net/minecraft/SharedConstants.class`, and its root `version.json` parses with a
|
|
162
|
+
* string `id` that `isUnobfuscatedVersion` accepts. The id is put to that test
|
|
163
|
+
* only once the other two hold, so it is the id a Minecraft `version.json`
|
|
164
|
+
* declares, never a library's own release number. A Loom-mapped 1.x jar carries
|
|
165
|
+
* both signals too; its `1.x` id is what keeps it out. The path is never read.
|
|
166
|
+
*/
|
|
167
|
+
export declare function provenUnobfuscatedRuntimeJarVersion(scan: JavaSourceScan | undefined): string | undefined;
|
|
146
168
|
/**
|
|
147
169
|
* Loader a runtime jar belongs to, read from the tool-written part of its path.
|
|
148
170
|
*
|
|
@@ -163,7 +185,9 @@ export declare function inferRuntimeJarLoader(path: string): RuntimeLoader;
|
|
|
163
185
|
* `version` becomes the version the SERVED jar carries; the caller's original
|
|
164
186
|
* request is preserved under `requestedVersion` and flagged. A served loader
|
|
165
187
|
* that contradicts a KNOWN expected loader is flagged too — never silently
|
|
166
|
-
* dropped.
|
|
188
|
+
* dropped. When the jar's path names no version at all, the request stands as
|
|
189
|
+
* the answer and nothing is flagged: there is no served version to contradict
|
|
190
|
+
* it with.
|
|
167
191
|
*/
|
|
168
192
|
export declare function describeServedRuntimeJar(input: {
|
|
169
193
|
jarPath: string;
|
|
@@ -170,16 +170,30 @@ export function looksLikeMinecraftArtifactPath(path) {
|
|
|
170
170
|
function looksLikeMinecraftSourceArtifact(path, hasMinecraftNamespace) {
|
|
171
171
|
return hasMinecraftNamespace || looksLikeMinecraftArtifactPath(path);
|
|
172
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* What a Minecraft version id may be FOLLOWED by and still be the same id:
|
|
175
|
+
* nothing. A pre-release suffix extends the id rather than decorating it —
|
|
176
|
+
* `26.1-pre-1` is its own version and not `26.1` — so the hyphen forms Mojang
|
|
177
|
+
* publishes (`-pre1`, `-rc1`, `-pre-6`, `-rc-2`, `-snapshot-6`) have to close
|
|
178
|
+
* the token as firmly as a trailing digit does. Any OTHER hyphen is ordinary
|
|
179
|
+
* path decoration (`minecraft-1.21.1-merged.jar`) and must keep matching.
|
|
180
|
+
*
|
|
181
|
+
* The suffix grammar is `isUnobfuscatedVersion`'s in version-service.ts; the
|
|
182
|
+
* two are read together whenever either changes.
|
|
183
|
+
*/
|
|
184
|
+
const VERSION_ID_SUFFIX_LOOKAHEAD = "-(?:pre|rc)[0-9]|-(?:pre|rc|snapshot)-[0-9]";
|
|
173
185
|
export function hasExactVersionToken(path, version) {
|
|
174
186
|
const normalizedPath = normalizePathStyle(path).toLowerCase();
|
|
175
187
|
const normalizedVersion = version.trim().toLowerCase();
|
|
176
188
|
if (!normalizedVersion) {
|
|
177
189
|
return false;
|
|
178
190
|
}
|
|
179
|
-
// Avoid prefix false-positives like "1.21.1" matching "1.21.10"
|
|
180
|
-
|
|
191
|
+
// Avoid prefix false-positives like "1.21.1" matching "1.21.10", and
|
|
192
|
+
// pre-release false-positives like "26.1" matching "26.1-pre-1".
|
|
193
|
+
const cacheKey = normalizedVersion;
|
|
194
|
+
const cached = VERSION_TOKEN_REGEX_CACHE.get(cacheKey);
|
|
181
195
|
const pattern = cached
|
|
182
|
-
?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE,
|
|
196
|
+
?? rememberCachedRegex(VERSION_TOKEN_REGEX_CACHE, cacheKey, new RegExp(`(^|[^0-9a-z])${escapeRegexLiteral(normalizedVersion)}(?![0-9a-z]|\\.[0-9]|${VERSION_ID_SUFFIX_LOOKAHEAD})`, "i"));
|
|
183
197
|
return pattern.test(normalizedPath);
|
|
184
198
|
}
|
|
185
199
|
/**
|
|
@@ -209,15 +223,119 @@ export function hasLoaderRuntimeVersionToken(path, mcVersion) {
|
|
|
209
223
|
?? rememberCachedRegex(LOADER_VERSION_TOKEN_REGEX_CACHE, cacheKey, new RegExp(`(^|[^0-9a-z.])${minor}\\.${patch}\\.\\d`, "i"));
|
|
210
224
|
return pattern.test(normalizedPath);
|
|
211
225
|
}
|
|
212
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Every version-SHAPED token a path carries, in both id families: `<a>.<b>[.<c>]`
|
|
228
|
+
* with an optional pre-release suffix, and the `YYwNNa` snapshot form. Shape
|
|
229
|
+
* only — `inferRuntimeJarMinecraftVersion` decides which of them is Minecraft's.
|
|
230
|
+
*
|
|
231
|
+
* The alternatives are greedy, so the LONGEST token at a position is the one
|
|
232
|
+
* offered, suffix included. What closes it is another version component — a
|
|
233
|
+
* digit, or a dot that starts one — and nothing else: a dot that starts a file
|
|
234
|
+
* extension ends the token instead. Refusing every following dot made
|
|
235
|
+
* `minecraft-merged-26.1.jar` name no version and made
|
|
236
|
+
* `minecraft-merged-26.2-pre-6.jar` backtrack off its own suffix down to
|
|
237
|
+
* "26.2", the release that jar is not.
|
|
238
|
+
*/
|
|
239
|
+
const RUNTIME_JAR_VERSION_TOKEN_REGEX = /(?:^|[^0-9.])(\d+\.\d+(?:\.\d+)?(?:-(?:pre|rc)\d+|-(?:pre|rc|snapshot)-\d+)?|\d{2}w\d{2}[a-z])(?![0-9]|\.[0-9])/g;
|
|
240
|
+
/** The legacy obfuscated line, `1.21` / `1.21.10` / `1.14.4-pre7`. */
|
|
241
|
+
const LEGACY_MINECRAFT_VERSION_REGEX = /^1\.\d+(?:\.\d+)?(?:-(?:pre|rc)\d+|-(?:pre|rc|snapshot)-\d+)?$/;
|
|
242
|
+
/** Loom's cache root: the directory whose immediate children are Minecraft versions. */
|
|
243
|
+
const LOOM_CACHE_ROOT_SEGMENT_REGEX = /^(?:fabric-loom|loom-cache)$/;
|
|
244
|
+
/**
|
|
245
|
+
* Does the path POSITION a 26.1+ token sits at vouch for it being Minecraft's
|
|
246
|
+
* version?
|
|
247
|
+
*
|
|
248
|
+
* The new-format grammar is only a shape: `isUnobfuscatedVersion` accepts any
|
|
249
|
+
* id from year 26 up, and Forge's own versions have long since passed it
|
|
250
|
+
* (`forge-47.3.0-srg.jar`), so the token needs the same kind of corroboration
|
|
251
|
+
* the loader-version exclusions elsewhere in this file rely on. Three positions
|
|
252
|
+
* carry it, and they are the layouts the discovery helpers already walk: the
|
|
253
|
+
* artifact itself names Minecraft (`minecraft-merged-26.1.jar`), the entry
|
|
254
|
+
* directly inside the version directory does (neoformruntime's
|
|
255
|
+
* `<version>/minecraft-joined.jar`), or the version directory is a child of
|
|
256
|
+
* Loom's cache root (`<...>/fabric-loom/<mcVersion>/`).
|
|
257
|
+
*
|
|
258
|
+
* Only the token's own neighbourhood counts, never an ancestor — for the reason
|
|
259
|
+
* `runtimeJarLayoutRelativePath` exists: a checkout under `~/dev/minecraft/`
|
|
260
|
+
* names the user's directory, not the jar.
|
|
261
|
+
*/
|
|
262
|
+
function tokenPositionNamesMinecraft(segments, index) {
|
|
263
|
+
return ((segments[index]?.includes("minecraft") ?? false) ||
|
|
264
|
+
(segments[index + 1]?.includes("minecraft") ?? false) ||
|
|
265
|
+
LOOM_CACHE_ROOT_SEGMENT_REGEX.test(segments[index - 1] ?? ""));
|
|
266
|
+
}
|
|
213
267
|
/**
|
|
214
268
|
* Minecraft version a runtime jar path carries, or undefined when the path
|
|
215
269
|
* names none. Loom lays its cache out as
|
|
216
|
-
* `<gradle>/caches/fabric-loom/<mcVersion>/...`, so the first
|
|
217
|
-
* of the path
|
|
270
|
+
* `<gradle>/caches/fabric-loom/<mcVersion>/...`, so the first token of the
|
|
271
|
+
* tool-written part of the path ({@link runtimeJarLayoutTail}; the whole path
|
|
272
|
+
* when it enters no layout root) that names a Minecraft version is the version the jar was built for — in
|
|
273
|
+
* either id family, and a pre-release names itself and not the release it
|
|
274
|
+
* precedes. Reading only `1.x[.y]` left every 26.x jar versionless, so no
|
|
275
|
+
* approximation was ever reported for one.
|
|
276
|
+
*
|
|
277
|
+
* A legacy `1.x` token is self-identifying; a 26.1+ token shares its shape with
|
|
278
|
+
* every loader's own version number, so it additionally has to sit where
|
|
279
|
+
* {@link tokenPositionNamesMinecraft} says Minecraft's version sits.
|
|
218
280
|
*/
|
|
219
281
|
export function inferRuntimeJarMinecraftVersion(path) {
|
|
220
|
-
|
|
282
|
+
// A path that enters no layout root, such as a custom Gradle home's `loom-cache`, is read whole.
|
|
283
|
+
const normalizedPath = runtimeJarLayoutTail(path) ?? normalizePathStyle(path).toLowerCase();
|
|
284
|
+
const segments = normalizedPath.split("/");
|
|
285
|
+
for (const match of normalizedPath.matchAll(RUNTIME_JAR_VERSION_TOKEN_REGEX)) {
|
|
286
|
+
const token = match[1];
|
|
287
|
+
if (!token) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (LEGACY_MINECRAFT_VERSION_REGEX.test(token)) {
|
|
291
|
+
return token;
|
|
292
|
+
}
|
|
293
|
+
if (!isUnobfuscatedVersion(token)) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
// `match[0]` carries the leading boundary character, so the token starts at
|
|
297
|
+
// its end minus the token's own length.
|
|
298
|
+
const tokenStart = (match.index ?? 0) + match[0].length - token.length;
|
|
299
|
+
const segmentIndex = normalizedPath.slice(0, tokenStart).split("/").length - 1;
|
|
300
|
+
if (tokenPositionNamesMinecraft(segments, segmentIndex)) {
|
|
301
|
+
return token;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The Minecraft version a `kind: "jar"` target PROVES it is the unobfuscated
|
|
308
|
+
* runtime of, or undefined. The file path is never evidence: Loom and other
|
|
309
|
+
* tools lay jars out in ways that path heuristics have misread before.
|
|
310
|
+
*
|
|
311
|
+
* All three must hold: the walk met no `.java` entry, the jar ships
|
|
312
|
+
* `net/minecraft/SharedConstants.class`, and its root `version.json` parses with a
|
|
313
|
+
* string `id` that `isUnobfuscatedVersion` accepts. The id is put to that test
|
|
314
|
+
* only once the other two hold, so it is the id a Minecraft `version.json`
|
|
315
|
+
* declares, never a library's own release number. A Loom-mapped 1.x jar carries
|
|
316
|
+
* both signals too; its `1.x` id is what keeps it out. The path is never read.
|
|
317
|
+
*/
|
|
318
|
+
export function provenUnobfuscatedRuntimeJarVersion(scan) {
|
|
319
|
+
const signals = scan?.minecraftRuntimeSignals;
|
|
320
|
+
if (!scan || scan.hasJavaSources || !signals?.hasSharedConstantsClass || signals.versionJsonText === undefined) {
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
let parsed;
|
|
324
|
+
try {
|
|
325
|
+
parsed = JSON.parse(signals.versionJsonText);
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
const id = parsed.id;
|
|
334
|
+
if (typeof id !== "string") {
|
|
335
|
+
return undefined;
|
|
336
|
+
}
|
|
337
|
+
const version = id.trim();
|
|
338
|
+
return isUnobfuscatedVersion(version) ? version : undefined;
|
|
221
339
|
}
|
|
222
340
|
/**
|
|
223
341
|
* Segment that opens a directory tree a BUILD TOOL wrote: a dot-directory
|
|
@@ -237,6 +355,10 @@ const RUNTIME_JAR_LAYOUT_ROOT_RE = /^(?:\.[^/]*|caches|build)$/;
|
|
|
237
355
|
* loader for an artifact that names none.
|
|
238
356
|
*/
|
|
239
357
|
function runtimeJarLayoutRelativePath(path) {
|
|
358
|
+
return runtimeJarLayoutTail(path) ?? normalizePathStyle(path).toLowerCase().split("/").filter(Boolean).at(-1) ?? "";
|
|
359
|
+
}
|
|
360
|
+
/** Everything below the deepest layout root, or undefined when the path enters none. */
|
|
361
|
+
function runtimeJarLayoutTail(path) {
|
|
240
362
|
const segments = normalizePathStyle(path)
|
|
241
363
|
.toLowerCase()
|
|
242
364
|
.split("/")
|
|
@@ -246,7 +368,7 @@ function runtimeJarLayoutRelativePath(path) {
|
|
|
246
368
|
return segments.slice(index + 1).join("/");
|
|
247
369
|
}
|
|
248
370
|
}
|
|
249
|
-
return
|
|
371
|
+
return undefined;
|
|
250
372
|
}
|
|
251
373
|
/**
|
|
252
374
|
* Loader a runtime jar belongs to, read from the tool-written part of its path.
|
|
@@ -286,7 +408,9 @@ export function inferRuntimeJarLoader(path) {
|
|
|
286
408
|
* `version` becomes the version the SERVED jar carries; the caller's original
|
|
287
409
|
* request is preserved under `requestedVersion` and flagged. A served loader
|
|
288
410
|
* that contradicts a KNOWN expected loader is flagged too — never silently
|
|
289
|
-
* dropped.
|
|
411
|
+
* dropped. When the jar's path names no version at all, the request stands as
|
|
412
|
+
* the answer and nothing is flagged: there is no served version to contradict
|
|
413
|
+
* it with.
|
|
290
414
|
*/
|
|
291
415
|
export function describeServedRuntimeJar(input) {
|
|
292
416
|
const notes = [];
|
|
@@ -378,7 +502,8 @@ function buildProvenance(input) {
|
|
|
378
502
|
version: input.resolved.version,
|
|
379
503
|
repoUrl: input.resolved.repoUrl
|
|
380
504
|
},
|
|
381
|
-
transformChain: [...input.transformChain]
|
|
505
|
+
transformChain: [...input.transformChain],
|
|
506
|
+
...(input.unobfuscatedRuntime ? { unobfuscatedRuntime: true } : {})
|
|
382
507
|
};
|
|
383
508
|
if (!provenance.resolvedAt || !provenance.target.kind || !provenance.target.value) {
|
|
384
509
|
throw createError({
|
|
@@ -492,8 +617,9 @@ export async function probeMinecraftArtifact(svc, input) {
|
|
|
492
617
|
let value = input.target.value.trim();
|
|
493
618
|
const warnings = [];
|
|
494
619
|
const requestedMapping = normalizeMapping(input.mapping);
|
|
495
|
-
|
|
496
|
-
|
|
620
|
+
const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
|
|
621
|
+
if (input.preferProjectVersion && normalizedProjectPath) {
|
|
622
|
+
const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(normalizedProjectPath);
|
|
497
623
|
if (detected && detected !== value) {
|
|
498
624
|
warnings.push(`Overriding version "${value}" with project version "${detected}" from gradle.properties.`);
|
|
499
625
|
}
|
|
@@ -545,7 +671,7 @@ export async function probeMinecraftArtifact(svc, input) {
|
|
|
545
671
|
}
|
|
546
672
|
const versionSourceDiscovery = await svc.discoverVersionSourceJar({
|
|
547
673
|
version: resolvedVersion,
|
|
548
|
-
projectPath:
|
|
674
|
+
projectPath: normalizedProjectPath,
|
|
549
675
|
gradleUserHome: input.gradleUserHome
|
|
550
676
|
});
|
|
551
677
|
if (!versionSourceDiscovery.selectedSourceJarPath) {
|
|
@@ -1093,10 +1219,11 @@ export async function resolveVersionContext(svc, input) {
|
|
|
1093
1219
|
if (inferredVersion) {
|
|
1094
1220
|
return inferredVersion;
|
|
1095
1221
|
}
|
|
1096
|
-
|
|
1222
|
+
const normalizedProjectPath = normalizeOptionalProjectPath(input.projectPath);
|
|
1223
|
+
if (!input.preferProjectVersion || !normalizedProjectPath) {
|
|
1097
1224
|
return undefined;
|
|
1098
1225
|
}
|
|
1099
|
-
const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(
|
|
1226
|
+
const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(normalizedProjectPath);
|
|
1100
1227
|
if (detected) {
|
|
1101
1228
|
input.warnings.push(`Using project version "${detected}" from gradle.properties because the artifact metadata did not include a version.`);
|
|
1102
1229
|
}
|
|
@@ -1233,12 +1360,19 @@ export async function buildMappingFallbackSuggestedCall(svc, args) {
|
|
|
1233
1360
|
if (process.env.WORKSPACE_FALLBACK_LEGACY === "1") {
|
|
1234
1361
|
return buildLegacyMappingFallback({ kind, value, scope, isVanillaMojang, projectPath: input.projectPath });
|
|
1235
1362
|
}
|
|
1236
|
-
const projectPath = input.projectPath
|
|
1363
|
+
const projectPath = normalizeOptionalProjectPath(input.projectPath);
|
|
1237
1364
|
if (!projectPath) {
|
|
1238
1365
|
return buildLegacyMappingFallback({ kind, value, scope, isVanillaMojang, projectPath: undefined });
|
|
1239
1366
|
}
|
|
1240
1367
|
if (kind !== "version") {
|
|
1241
|
-
return buildLegacyMappingFallback({
|
|
1368
|
+
return buildLegacyMappingFallback({
|
|
1369
|
+
kind,
|
|
1370
|
+
value,
|
|
1371
|
+
scope,
|
|
1372
|
+
isVanillaMojang,
|
|
1373
|
+
projectPath,
|
|
1374
|
+
projectRuntimeUnobfuscated: isVanillaMojang && (await projectNamesUnobfuscatedMinecraft(svc, projectPath))
|
|
1375
|
+
});
|
|
1242
1376
|
}
|
|
1243
1377
|
const cached = svc.workspaceContextCache.read(projectPath);
|
|
1244
1378
|
if (cached &&
|
|
@@ -1299,9 +1433,28 @@ export async function buildMappingFallbackSuggestedCall(svc, args) {
|
|
|
1299
1433
|
}
|
|
1300
1434
|
return buildLegacyMappingFallback({ kind, value, scope, isVanillaMojang, projectPath });
|
|
1301
1435
|
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Whether the project's gradle.properties names a Minecraft 26.1+ release. The
|
|
1438
|
+
* property is a Minecraft version by name (`minecraft_version` and its aliases), so
|
|
1439
|
+
* it may be put to `isUnobfuscatedVersion`. Unreadable or absent answers false.
|
|
1440
|
+
*/
|
|
1441
|
+
async function projectNamesUnobfuscatedMinecraft(svc, projectPath) {
|
|
1442
|
+
try {
|
|
1443
|
+
const version = await svc.workspaceMappingService.detectProjectMinecraftVersion(projectPath);
|
|
1444
|
+
return version !== undefined && isUnobfuscatedVersion(version);
|
|
1445
|
+
}
|
|
1446
|
+
catch {
|
|
1447
|
+
return false;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1302
1450
|
function buildLegacyMappingFallback(args) {
|
|
1303
1451
|
const { kind, value, scope, isVanillaMojang, projectPath } = args;
|
|
1304
|
-
|
|
1452
|
+
// scope=merged reaches Loom source discovery only for a version target. For a
|
|
1453
|
+
// jar or coordinate in a 26.1+ project, re-suggesting mojang+merged on the same
|
|
1454
|
+
// target repeats the request that was just refused, so that case takes the
|
|
1455
|
+
// generic retry below instead.
|
|
1456
|
+
const mergedRetryWouldRepeat = kind !== "version" && args.projectRuntimeUnobfuscated === true;
|
|
1457
|
+
if (isVanillaMojang && projectPath && !mergedRetryWouldRepeat) {
|
|
1305
1458
|
return {
|
|
1306
1459
|
...buildSuggestedCall({
|
|
1307
1460
|
tool: "resolve-artifact",
|
|
@@ -1311,7 +1464,7 @@ function buildLegacyMappingFallback(args) {
|
|
|
1311
1464
|
"Retry with scope=merged to allow source-jar resolution from the project cache."
|
|
1312
1465
|
};
|
|
1313
1466
|
}
|
|
1314
|
-
if (isVanillaMojang) {
|
|
1467
|
+
if (isVanillaMojang && !projectPath) {
|
|
1315
1468
|
return {
|
|
1316
1469
|
...buildSuggestedCall({
|
|
1317
1470
|
tool: "resolve-artifact",
|
|
@@ -1326,7 +1479,14 @@ function buildLegacyMappingFallback(args) {
|
|
|
1326
1479
|
tool: "resolve-artifact",
|
|
1327
1480
|
params: buildResolveArtifactParams({ kind, value }, { mapping: "obfuscated", ...(scope ? { scope } : {}) })
|
|
1328
1481
|
}),
|
|
1329
|
-
|
|
1482
|
+
// A jar reaches here only when it was NOT proven to be a Minecraft runtime jar,
|
|
1483
|
+
// so its version is unknown and "obfuscated" must not be described as
|
|
1484
|
+
// obfuscated names: on 26.1+ the as-shipped names are Mojang names. Version and
|
|
1485
|
+
// coordinate refusals keep their established wording.
|
|
1486
|
+
nextAction: kind === "jar"
|
|
1487
|
+
? "Retry with mapping=obfuscated to read this jar's names as shipped (already Mojang names on Minecraft 26.1+). " +
|
|
1488
|
+
"For a vanilla Minecraft release, use target kind \"version\" instead."
|
|
1489
|
+
: "Retry with mapping=obfuscated to use the runtime obfuscated namespace."
|
|
1330
1490
|
};
|
|
1331
1491
|
}
|
|
1332
1492
|
export async function resolveArtifact(svc, input) {
|
|
@@ -1360,8 +1520,15 @@ export async function resolveArtifact(svc, input) {
|
|
|
1360
1520
|
const mapping = normalizeMapping(input.mapping);
|
|
1361
1521
|
const scope = input.scope;
|
|
1362
1522
|
const warnings = [...synthesisWarnings];
|
|
1363
|
-
|
|
1364
|
-
|
|
1523
|
+
// Normalized only for the readers that need it: every other use of
|
|
1524
|
+
// projectPath below reaches a helper that normalizes on its own, and doing it
|
|
1525
|
+
// here unconditionally would newly refuse a malformed path on calls that
|
|
1526
|
+
// never read one.
|
|
1527
|
+
const preferProjectVersionPath = input.preferProjectVersion
|
|
1528
|
+
? normalizeOptionalProjectPath(input.projectPath)
|
|
1529
|
+
: undefined;
|
|
1530
|
+
if (preferProjectVersionPath && kind === "version") {
|
|
1531
|
+
const detected = await svc.workspaceMappingService.detectProjectMinecraftVersion(preferProjectVersionPath);
|
|
1365
1532
|
if (detected && detected !== value) {
|
|
1366
1533
|
warnings.push(`Overriding version "${value}" with project version "${detected}" from gradle.properties.`);
|
|
1367
1534
|
}
|
|
@@ -1431,13 +1598,19 @@ export async function resolveArtifact(svc, input) {
|
|
|
1431
1598
|
// below), so its mapping is never actually enforced either way.
|
|
1432
1599
|
const versionNamesMinecraft = kind === "version" ||
|
|
1433
1600
|
(kind === "coordinate" && !dependencyOrigin && coordinateIsMinecraftRuntime);
|
|
1434
|
-
|
|
1435
|
-
|
|
1601
|
+
// A kind="jar" target names no version at all. It joins this gate only AFTER its
|
|
1602
|
+
// archive has been walked and proved to be a Minecraft 26.1+ runtime jar (see
|
|
1603
|
+
// `provenUnobfuscatedRuntimeJarVersion` below); until then it stays out.
|
|
1604
|
+
let minecraftVersion = versionNamesMinecraft ? resolvedVersion : undefined;
|
|
1605
|
+
let runtimeNamesUnobfuscated = minecraftVersion !== undefined && isUnobfuscatedVersion(minecraftVersion);
|
|
1436
1606
|
let effectiveMapping = mapping;
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1607
|
+
const dropInapplicableUnobfuscatedMapping = () => {
|
|
1608
|
+
if ((effectiveMapping === "intermediary" || effectiveMapping === "yarn") && runtimeNamesUnobfuscated) {
|
|
1609
|
+
warnings.push(`Version ${minecraftVersion} is unobfuscated; ${effectiveMapping} mappings are not applicable. Using the obfuscated namespace label for the deobfuscated runtime names.`);
|
|
1610
|
+
effectiveMapping = "obfuscated";
|
|
1611
|
+
}
|
|
1612
|
+
};
|
|
1613
|
+
dropInapplicableUnobfuscatedMapping();
|
|
1441
1614
|
if (kind === "version" &&
|
|
1442
1615
|
resolvedVersion &&
|
|
1443
1616
|
effectiveMapping === "mojang" &&
|
|
@@ -1471,9 +1644,21 @@ export async function resolveArtifact(svc, input) {
|
|
|
1471
1644
|
if (binaryRemapGate.warnings.length > 0) {
|
|
1472
1645
|
warnings.push(...binaryRemapGate.warnings);
|
|
1473
1646
|
}
|
|
1647
|
+
// Only a jar the caller named directly can be proved from its contents. A
|
|
1648
|
+
// dependency-origin jar is excluded for the reason given above, and a version
|
|
1649
|
+
// target is rewritten into a jar but already carries its version.
|
|
1650
|
+
const proveRuntimeJar = kind === "jar" && !dependencyOrigin;
|
|
1651
|
+
let subjectJarScan = undefined;
|
|
1474
1652
|
const resolved = await resolveSourceTargetInternal(resolvedTarget, {
|
|
1475
1653
|
allowDecompile: effectiveMapping === "mojang" ? true : input.allowDecompile ?? true,
|
|
1476
1654
|
mappingVariant: binaryRemapGate.mappingVariant,
|
|
1655
|
+
...(proveRuntimeJar
|
|
1656
|
+
? {
|
|
1657
|
+
onSubjectJarScanned: (scan) => {
|
|
1658
|
+
subjectJarScan = scan;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
: {}),
|
|
1477
1662
|
onRepoFailover: (event) => {
|
|
1478
1663
|
svc.metrics.recordRepoFailover();
|
|
1479
1664
|
log("warn", "repo.failover", {
|
|
@@ -1486,6 +1671,13 @@ export async function resolveArtifact(svc, input) {
|
|
|
1486
1671
|
});
|
|
1487
1672
|
}
|
|
1488
1673
|
}, svc.config);
|
|
1674
|
+
const provenJarVersion = proveRuntimeJar ? provenUnobfuscatedRuntimeJarVersion(subjectJarScan) : undefined;
|
|
1675
|
+
if (provenJarVersion) {
|
|
1676
|
+
resolvedVersion = provenJarVersion;
|
|
1677
|
+
minecraftVersion = provenJarVersion;
|
|
1678
|
+
runtimeNamesUnobfuscated = true;
|
|
1679
|
+
dropInapplicableUnobfuscatedMapping();
|
|
1680
|
+
}
|
|
1489
1681
|
resolved.version = resolvedVersion;
|
|
1490
1682
|
let mappingDecision;
|
|
1491
1683
|
try {
|
|
@@ -1547,9 +1739,21 @@ export async function resolveArtifact(svc, input) {
|
|
|
1547
1739
|
mapping: effectiveMapping,
|
|
1548
1740
|
target: { kind, value },
|
|
1549
1741
|
nextAction: "Use target: { kind: \"version\", value } or a versioned Maven coordinate so mapping artifacts can be resolved.",
|
|
1742
|
+
// This branch is reached precisely because no version is known, and
|
|
1743
|
+
// `value` is whatever the caller's own target carried - a jar path
|
|
1744
|
+
// for the kind that gets here. Replaying it as a version target
|
|
1745
|
+
// produced a `suggestedCall` that could never succeed, so the
|
|
1746
|
+
// version goes out as a placeholder the caller must fill in;
|
|
1747
|
+
// buildSuggestedCall routes such a template to exampleCalls.
|
|
1550
1748
|
...buildSuggestedCall({
|
|
1551
1749
|
tool: "resolve-artifact",
|
|
1552
|
-
params:
|
|
1750
|
+
params: undefined,
|
|
1751
|
+
examples: [
|
|
1752
|
+
{
|
|
1753
|
+
params: buildResolveArtifactParams({ kind: "version", value: "<your-mc-version>" }, { ...(scope ? { scope } : {}) }),
|
|
1754
|
+
reason: `Replace <your-mc-version> with the Minecraft version this artifact belongs to; ${effectiveMapping} mappings are resolved per version.`
|
|
1755
|
+
}
|
|
1756
|
+
]
|
|
1553
1757
|
})
|
|
1554
1758
|
}
|
|
1555
1759
|
});
|
|
@@ -1569,7 +1773,8 @@ export async function resolveArtifact(svc, input) {
|
|
|
1569
1773
|
const provenance = buildProvenance({
|
|
1570
1774
|
requestedTarget: { kind, value },
|
|
1571
1775
|
resolved,
|
|
1572
|
-
transformChain: [...mappingDecision.transformChain, ...additionalTransformChain]
|
|
1776
|
+
transformChain: [...mappingDecision.transformChain, ...additionalTransformChain],
|
|
1777
|
+
unobfuscatedRuntime: runtimeNamesUnobfuscated
|
|
1573
1778
|
});
|
|
1574
1779
|
if (workspaceProvenance) {
|
|
1575
1780
|
provenance.workspaceResolution = workspaceProvenance;
|
|
@@ -17,6 +17,13 @@ export type RemapMembersInput = {
|
|
|
17
17
|
signatureFields: SignatureMember[];
|
|
18
18
|
signatureMethods: SignatureMember[];
|
|
19
19
|
version: string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* `version` when it is a Minecraft version, else undefined: a dependency
|
|
22
|
+
* artifact's `version` is its own release number, which must not reach the
|
|
23
|
+
* 26.1+ obfuscated<->mojang identity shortcut, which may only be asked about a
|
|
24
|
+
* string proven to be a Minecraft version.
|
|
25
|
+
*/
|
|
26
|
+
minecraftVersion: string | undefined;
|
|
20
27
|
mappingApplied: SourceMapping;
|
|
21
28
|
requestedMapping: SourceMapping;
|
|
22
29
|
sourcePriority: MappingSourcePriority | undefined;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { remapSignatureMembers } from "../lifecycle/mapping-helpers.js";
|
|
1
2
|
import { matchesMemberPattern } from "../member-pattern.js";
|
|
2
3
|
export async function remapAndCountMembers(svc, input) {
|
|
3
4
|
const remap = async (members, kind) => {
|
|
4
5
|
if (input.version == null) {
|
|
5
6
|
return members;
|
|
6
7
|
}
|
|
7
|
-
|
|
8
|
+
// Called directly rather than through svc.remapSignatureMembers, whose fixed
|
|
9
|
+
// parameter list cannot carry minecraftVersion.
|
|
10
|
+
const result = await remapSignatureMembers(svc, members, kind, input.version, input.mappingApplied, input.requestedMapping, input.sourcePriority, input.warnings, undefined, input.gradleUserHome, { minecraftVersion: input.minecraftVersion });
|
|
8
11
|
return result.members;
|
|
9
12
|
};
|
|
10
13
|
let constructors = await remap(input.signatureConstructors, "method");
|
|
@@ -21,7 +21,7 @@ import type { ArtifactProvenance, ArtifactScope, MappingSourcePriority, Resolved
|
|
|
21
21
|
*/
|
|
22
22
|
export declare function reconcileUnobfuscatedNamespace(version: string | undefined, requestedMapping: SourceMapping, mappingApplied: SourceMapping): SourceMapping;
|
|
23
23
|
export declare function resolveClassFilePath(svc: SourceService, artifactId: string, className: string): string | undefined;
|
|
24
|
-
|
|
24
|
+
type ClassNameLookupInput = {
|
|
25
25
|
className: string;
|
|
26
26
|
version?: string;
|
|
27
27
|
sourceMapping: SourceMapping;
|
|
@@ -30,7 +30,13 @@ export declare function resolveClassNameForLookup(svc: SourceService, input: {
|
|
|
30
30
|
gradleUserHome?: string;
|
|
31
31
|
warnings: string[];
|
|
32
32
|
context: string;
|
|
33
|
-
}
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The class-name translation behind `svc.resolveClassNameForLookup`, whose callers
|
|
36
|
+
* (the lifecycle tools) pass a Minecraft version, so `version` also keys the 26.1+
|
|
37
|
+
* identity shortcut.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveClassNameForLookup(svc: SourceService, input: ClassNameLookupInput): Promise<string>;
|
|
34
40
|
export declare function buildFallbackProvenance(svc: SourceService, input: {
|
|
35
41
|
artifactId: string;
|
|
36
42
|
origin: ResolvedSourceArtifact["origin"];
|
|
@@ -98,3 +104,4 @@ export declare function findClass(svc: SourceService, input: FindClassInput): Fi
|
|
|
98
104
|
export declare function findClassIncludingNested(svc: SourceService, input: FindClassInput): Promise<FindClassOutput>;
|
|
99
105
|
export declare function getClassSource(svc: SourceService, input: GetClassSourceInput): Promise<GetClassSourceOutput>;
|
|
100
106
|
export declare function getClassMembers(svc: SourceService, input: GetClassMembersInput): Promise<GetClassMembersOutput>;
|
|
107
|
+
export {};
|