@adhisang/minecraft-modding-mcp 3.1.1 → 3.2.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 +24 -0
- package/README.md +20 -8
- package/dist/access-transformer-parser.d.ts +17 -0
- package/dist/access-transformer-parser.js +97 -0
- package/dist/concurrency.d.ts +1 -0
- package/dist/concurrency.js +24 -0
- package/dist/decompiler/vineflower.js +22 -21
- package/dist/entry-tools/analyze-mod-service.d.ts +4 -4
- package/dist/entry-tools/analyze-symbol-service.d.ts +20 -20
- package/dist/entry-tools/inspect-minecraft-service.d.ts +148 -148
- package/dist/entry-tools/validate-project-service.d.ts +153 -16
- package/dist/entry-tools/validate-project-service.js +360 -23
- package/dist/gradle-paths.d.ts +4 -0
- package/dist/gradle-paths.js +57 -0
- package/dist/index.js +65 -13
- package/dist/mapping-pipeline-service.d.ts +3 -1
- package/dist/mapping-pipeline-service.js +16 -1
- package/dist/mapping-service.d.ts +4 -0
- package/dist/mapping-service.js +155 -60
- package/dist/minecraft-explorer-service.d.ts +13 -0
- package/dist/minecraft-explorer-service.js +8 -4
- package/dist/mixin-validator.d.ts +33 -2
- package/dist/mixin-validator.js +197 -11
- package/dist/mod-analyzer.d.ts +1 -0
- package/dist/mod-analyzer.js +17 -1
- package/dist/mod-decompile-service.js +4 -4
- package/dist/mod-remap-service.js +1 -54
- package/dist/mod-search-service.d.ts +1 -0
- package/dist/mod-search-service.js +84 -51
- package/dist/response-utils.d.ts +35 -0
- package/dist/response-utils.js +113 -0
- package/dist/source-jar-reader.d.ts +16 -0
- package/dist/source-jar-reader.js +103 -1
- package/dist/source-resolver.js +9 -10
- package/dist/source-service.d.ts +22 -2
- package/dist/source-service.js +914 -105
- package/dist/tool-contract-manifest.js +8 -6
- package/dist/types.d.ts +17 -0
- package/dist/workspace-mapping-service.d.ts +13 -0
- package/dist/workspace-mapping-service.js +146 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { ZodError, z } from "zod";
|
|
|
4
4
|
import { CompatStdioServerTransport } from "./compat-stdio-transport.js";
|
|
5
5
|
import { objectResult } from "./mcp-helpers.js";
|
|
6
6
|
import { prepareToolInput } from "./tool-input.js";
|
|
7
|
+
import { isCompactEnabled, COMPACT_MAPPING_TOOL_NAMES, compactResponse, compactArtifactResponse, compactMappingResponse } from "./response-utils.js";
|
|
7
8
|
import { loadConfig } from "./config.js";
|
|
8
9
|
import { createError, ERROR_CODES, isAppError } from "./errors.js";
|
|
9
10
|
import { log } from "./logger.js";
|
|
@@ -18,7 +19,7 @@ import { InspectMinecraftService, inspectMinecraftSchema, inspectMinecraftShape
|
|
|
18
19
|
import { AnalyzeSymbolService, analyzeSymbolSchema, analyzeSymbolShape } from "./entry-tools/analyze-symbol-service.js";
|
|
19
20
|
import { CompareMinecraftService, compareMinecraftSchema, compareMinecraftShape } from "./entry-tools/compare-minecraft-service.js";
|
|
20
21
|
import { AnalyzeModService, analyzeModSchema, analyzeModShape } from "./entry-tools/analyze-mod-service.js";
|
|
21
|
-
import { ValidateProjectService, validateProjectSchema, validateProjectShape, discoverWorkspaceAccessWideners, discoverWorkspaceMixins } from "./entry-tools/validate-project-service.js";
|
|
22
|
+
import { ValidateProjectService, validateProjectSchema, validateProjectShape, discoverWorkspaceAccessTransformers, discoverWorkspaceAccessWideners, discoverWorkspaceMixins } from "./entry-tools/validate-project-service.js";
|
|
22
23
|
import { ManageCacheService, manageCacheSchema, manageCacheShape } from "./entry-tools/manage-cache-service.js";
|
|
23
24
|
import { createCacheRegistry } from "./cache-registry.js";
|
|
24
25
|
import { buildEntryToolMeta } from "./entry-tools/response-contract.js";
|
|
@@ -89,7 +90,7 @@ const sourceLookupTargetSchema = z.discriminatedUnion("type", [
|
|
|
89
90
|
]);
|
|
90
91
|
const RESOLVE_ARTIFACT_TARGET_DESCRIPTION = "Object with kind and value. Example: {\"kind\":\"version\",\"value\":\"1.21.10\"}. Must be an object, not a string.";
|
|
91
92
|
const SOURCE_LOOKUP_TARGET_DESCRIPTION = "Object: {\"type\":\"resolve\",\"kind\":\"version\",\"value\":\"1.21.10\"} or {\"type\":\"artifact\",\"artifactId\":\"...\"}. Must be an object, not a string.";
|
|
92
|
-
const SOURCE_SCOPE_DESCRIPTION =
|
|
93
|
+
const SOURCE_SCOPE_DESCRIPTION = "vanilla = Mojang client jar only; merged = source-oriented merged runtime discovery; loader = loader/runtime artifact discovery when the workspace exposes transformed runtime jars.";
|
|
93
94
|
const SUGGESTED_CALL_DEFAULTS = {
|
|
94
95
|
allowDecompile: true,
|
|
95
96
|
preferProjectVersion: false,
|
|
@@ -129,7 +130,9 @@ const resolveArtifactShape = {
|
|
|
129
130
|
projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted source resolution"),
|
|
130
131
|
scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
|
|
131
132
|
preferProjectVersion: z.boolean().optional().describe("When true, detect MC version from gradle.properties and override target.value"),
|
|
132
|
-
strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false.")
|
|
133
|
+
strictVersion: z.boolean().optional().describe("When true, reject version-approximated results instead of returning them. Default false."),
|
|
134
|
+
compact: z.boolean().default(false).describe("When true, return minimal fields (artifactId, origin, isDecompiled, version, requestedMapping, mappingApplied, qualityFlags). "
|
|
135
|
+
+ "Omit provenance, artifactContents, sampleEntries, adjacentSourceCandidates, binaryJarPath, coordinate, repoUrl, resolvedSourceJarPath.")
|
|
133
136
|
};
|
|
134
137
|
const resolveArtifactSchema = z.object(resolveArtifactShape);
|
|
135
138
|
const getClassSourceShape = {
|
|
@@ -250,7 +253,9 @@ const findMappingShape = {
|
|
|
250
253
|
})
|
|
251
254
|
.partial()
|
|
252
255
|
.optional(),
|
|
253
|
-
maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)")
|
|
256
|
+
maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)"),
|
|
257
|
+
compact: z.boolean().default(false).describe("When true, omit top-level empty arrays, null/undefined values, and empty objects from the response. "
|
|
258
|
+
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions.")
|
|
254
259
|
};
|
|
255
260
|
const findMappingSchema = z.object(findMappingShape).superRefine((value, ctx) => {
|
|
256
261
|
if (value.kind === "class") {
|
|
@@ -317,7 +322,9 @@ const resolveMethodMappingExactShape = {
|
|
|
317
322
|
sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
318
323
|
targetMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
319
324
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
320
|
-
maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)")
|
|
325
|
+
maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)"),
|
|
326
|
+
compact: z.boolean().default(false).describe("When true, omit top-level empty arrays, null/undefined values, and empty objects from the response. "
|
|
327
|
+
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions.")
|
|
321
328
|
};
|
|
322
329
|
const resolveMethodMappingExactSchema = z
|
|
323
330
|
.object(resolveMethodMappingExactShape)
|
|
@@ -368,7 +375,9 @@ const resolveWorkspaceSymbolShape = {
|
|
|
368
375
|
descriptor: optionalNonEmptyString,
|
|
369
376
|
sourceMapping: sourceMappingSchema.describe("obfuscated | mojang | intermediary | yarn"),
|
|
370
377
|
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
371
|
-
maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates for field/method lookups (max 200)")
|
|
378
|
+
maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates for field/method lookups (max 200)"),
|
|
379
|
+
compact: z.boolean().default(false).describe("When true, omit top-level empty arrays, null/undefined values, and empty objects from the response. "
|
|
380
|
+
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions.")
|
|
372
381
|
};
|
|
373
382
|
const resolveWorkspaceSymbolSchema = z
|
|
374
383
|
.object(resolveWorkspaceSymbolShape)
|
|
@@ -440,7 +449,9 @@ const checkSymbolExistsShape = {
|
|
|
440
449
|
nameMode: classNameModeSchema.default("fqcn").describe("fqcn | auto"),
|
|
441
450
|
signatureMode: z.enum(["exact", "name-only"]).default("exact")
|
|
442
451
|
.describe("exact: require descriptor for methods; name-only: match by owner+name only"),
|
|
443
|
-
maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)")
|
|
452
|
+
maxCandidates: optionalPositiveInt.default(200).describe("Limit returned candidates (max 200)"),
|
|
453
|
+
compact: z.boolean().default(false).describe("When true, omit top-level empty arrays, null/undefined values, and empty objects from the response. "
|
|
454
|
+
+ "Also omit redundant candidates array for single full-confidence exact-match resolutions.")
|
|
444
455
|
};
|
|
445
456
|
const checkSymbolExistsSchema = z.object(checkSymbolExistsShape).superRefine((value, ctx) => {
|
|
446
457
|
if (value.kind === "class") {
|
|
@@ -581,9 +592,24 @@ const validateAccessWidenerShape = {
|
|
|
581
592
|
content: nonEmptyString.describe("Access Widener file content"),
|
|
582
593
|
version: nonEmptyString.describe("Minecraft version"),
|
|
583
594
|
mapping: sourceMappingSchema.optional().describe("obfuscated | mojang | intermediary | yarn"),
|
|
584
|
-
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first")
|
|
595
|
+
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
596
|
+
projectPath: optionalNonEmptyString.describe("Optional workspace root path for Loom cache-assisted runtime validation"),
|
|
597
|
+
scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
|
|
598
|
+
preferProjectVersion: z.boolean().default(false)
|
|
599
|
+
.describe("When true, detect MC version from gradle.properties and override version")
|
|
585
600
|
};
|
|
586
601
|
const validateAccessWidenerSchema = z.object(validateAccessWidenerShape);
|
|
602
|
+
const validateAccessTransformerShape = {
|
|
603
|
+
content: nonEmptyString.describe("Access Transformer file content"),
|
|
604
|
+
version: nonEmptyString.describe("Minecraft version"),
|
|
605
|
+
atNamespace: z.enum(["srg", "mojang", "obfuscated"]).optional().describe("srg | mojang | obfuscated"),
|
|
606
|
+
sourcePriority: mappingSourcePrioritySchema.optional().describe("loom-first | maven-first"),
|
|
607
|
+
projectPath: optionalNonEmptyString.describe("Optional workspace root path for Forge/NeoForge runtime validation"),
|
|
608
|
+
scope: artifactScopeSchema.optional().describe(SOURCE_SCOPE_DESCRIPTION),
|
|
609
|
+
preferProjectVersion: z.boolean().default(false)
|
|
610
|
+
.describe("When true, detect MC version from gradle.properties and override version")
|
|
611
|
+
};
|
|
612
|
+
const validateAccessTransformerSchema = z.object(validateAccessTransformerShape);
|
|
587
613
|
const analyzeModJarShape = {
|
|
588
614
|
jarPath: nonEmptyString.describe("Local path to the mod JAR file"),
|
|
589
615
|
includeClasses: z.boolean().default(false).describe("Include full class listing")
|
|
@@ -715,8 +741,11 @@ const analyzeModService = new AnalyzeModService({
|
|
|
715
741
|
const validateProjectService = new ValidateProjectService({
|
|
716
742
|
validateMixin: (input) => sourceService.validateMixin(input),
|
|
717
743
|
validateAccessWidener: (input) => sourceService.validateAccessWidener(input),
|
|
744
|
+
validateAccessTransformer: (input) => sourceService.validateAccessTransformer(input),
|
|
718
745
|
discoverMixins: discoverWorkspaceMixins,
|
|
719
|
-
discoverAccessWideners: discoverWorkspaceAccessWideners
|
|
746
|
+
discoverAccessWideners: discoverWorkspaceAccessWideners,
|
|
747
|
+
discoverAccessTransformers: discoverWorkspaceAccessTransformers,
|
|
748
|
+
detectProjectMinecraftVersion: (projectPath) => workspaceMappingService.detectProjectMinecraftVersion(projectPath)
|
|
720
749
|
});
|
|
721
750
|
const manageCacheService = new ManageCacheService({
|
|
722
751
|
registry: createCacheRegistry({
|
|
@@ -1379,7 +1408,7 @@ function buildInvalidInputGuidance(tool, normalizedInput) {
|
|
|
1379
1408
|
if (tool === "validate-project") {
|
|
1380
1409
|
return {
|
|
1381
1410
|
hints: [
|
|
1382
|
-
"validate-project.subject must be an object with subject.kind=workspace|mixin|access-widener.",
|
|
1411
|
+
"validate-project.subject must be an object with subject.kind=workspace|mixin|access-widener|access-transformer.",
|
|
1383
1412
|
"task=\"project-summary\" uses {\"subject\":{\"kind\":\"workspace\",\"projectPath\":\"/workspace\"}}.",
|
|
1384
1413
|
"Legacy include names like projectSummary/detectedConfig/validationSummary are not accepted; use include:[\"workspace\"] only when you need discovery details."
|
|
1385
1414
|
],
|
|
@@ -1502,6 +1531,17 @@ async function runTool(tool, rawInput, schema, action) {
|
|
|
1502
1531
|
? heavyToolExecutionGate.run(tool, () => action(parsedInput))
|
|
1503
1532
|
: action(parsedInput));
|
|
1504
1533
|
const { result, warnings, meta: resultMeta } = splitWarnings(payload);
|
|
1534
|
+
const isCompact = isCompactEnabled(tool, parsedInput);
|
|
1535
|
+
let projectedResult = result;
|
|
1536
|
+
if (isCompact) {
|
|
1537
|
+
if (tool === "resolve-artifact") {
|
|
1538
|
+
projectedResult = compactArtifactResponse(projectedResult);
|
|
1539
|
+
}
|
|
1540
|
+
if (COMPACT_MAPPING_TOOL_NAMES.has(tool)) {
|
|
1541
|
+
projectedResult = compactMappingResponse(projectedResult);
|
|
1542
|
+
}
|
|
1543
|
+
projectedResult = compactResponse(projectedResult);
|
|
1544
|
+
}
|
|
1505
1545
|
const entryMeta = ENTRY_TOOL_NAMES.has(tool)
|
|
1506
1546
|
? buildEntryToolMeta({
|
|
1507
1547
|
detail: normalizedInput &&
|
|
@@ -1519,7 +1559,7 @@ async function runTool(tool, rawInput, schema, action) {
|
|
|
1519
1559
|
})
|
|
1520
1560
|
: undefined;
|
|
1521
1561
|
return objectResult({
|
|
1522
|
-
result,
|
|
1562
|
+
result: projectedResult,
|
|
1523
1563
|
meta: {
|
|
1524
1564
|
...(entryMeta ?? {}),
|
|
1525
1565
|
...resultMeta,
|
|
@@ -1584,7 +1624,7 @@ server.tool("inspect-minecraft", "High-level v3 entry tool for version discovery
|
|
|
1584
1624
|
server.tool("analyze-symbol", "High-level v3 entry tool for symbol existence, mapping, lifecycle, workspace analysis, and API overview.", analyzeSymbolShape, { readOnlyHint: true }, async (args) => runTool("analyze-symbol", args, analyzeSymbolSchema, async (input) => analyzeSymbolService.execute(input)));
|
|
1585
1625
|
server.tool("compare-minecraft", "High-level v3 entry tool for version comparisons, class diffs, registry diffs, and migration overviews.", compareMinecraftShape, { readOnlyHint: true }, async (args) => runTool("compare-minecraft", args, compareMinecraftSchema, async (input) => compareMinecraftService.execute(input)));
|
|
1586
1626
|
server.tool("analyze-mod", "High-level v3 entry tool for mod metadata inspection, decompile/search flows, class source, and safe remap previews/applies.", analyzeModShape, { readOnlyHint: false }, async (args) => runTool("analyze-mod", args, analyzeModSchema, async (input) => analyzeModService.execute(input)));
|
|
1587
|
-
server.tool("validate-project", "High-level v3 entry tool for project summary, direct mixin validation, and access widener validation.", validateProjectShape, { readOnlyHint: true }, async (args) => runTool("validate-project", args, validateProjectSchema, async (input) => validateProjectService.execute(input)));
|
|
1627
|
+
server.tool("validate-project", "High-level v3 entry tool for project summary, direct mixin validation, and access widener/access transformer validation.", validateProjectShape, { readOnlyHint: true }, async (args) => runTool("validate-project", args, validateProjectSchema, async (input) => validateProjectService.execute(input)));
|
|
1588
1628
|
server.tool("manage-cache", "High-level v3 entry tool for cache summaries, listing, verification, previewed mutation, and explicit apply operations.", manageCacheShape, { readOnlyHint: false }, async (args) => runTool("manage-cache", args, manageCacheSchema, async (input) => manageCacheService.execute(input)));
|
|
1589
1629
|
server.tool("resolve-artifact", "Resolve source artifact from a target object ({ kind, value }) and return artifact metadata. For target.kind=jar, only <basename>-sources.jar is auto-adopted; other adjacent *-sources.jar files are informational.", resolveArtifactShape, { readOnlyHint: true }, async (args) => runTool("resolve-artifact", args, resolveArtifactSchema, async (input) => sourceService.resolveArtifact({
|
|
1590
1630
|
target: input.target,
|
|
@@ -1785,7 +1825,19 @@ server.tool("validate-access-widener", "Validate Access Widener file entries aga
|
|
|
1785
1825
|
content: input.content,
|
|
1786
1826
|
version: input.version,
|
|
1787
1827
|
mapping: input.mapping,
|
|
1788
|
-
sourcePriority: input.sourcePriority
|
|
1828
|
+
sourcePriority: input.sourcePriority,
|
|
1829
|
+
projectPath: input.projectPath,
|
|
1830
|
+
scope: input.scope,
|
|
1831
|
+
preferProjectVersion: input.preferProjectVersion
|
|
1832
|
+
})));
|
|
1833
|
+
server.tool("validate-access-transformer", "Validate Access Transformer file entries against Minecraft bytecode signatures for a given version.", validateAccessTransformerShape, { readOnlyHint: true }, async (args) => runTool("validate-access-transformer", args, validateAccessTransformerSchema, async (input) => sourceService.validateAccessTransformer({
|
|
1834
|
+
content: input.content,
|
|
1835
|
+
version: input.version,
|
|
1836
|
+
atNamespace: input.atNamespace,
|
|
1837
|
+
sourcePriority: input.sourcePriority,
|
|
1838
|
+
projectPath: input.projectPath,
|
|
1839
|
+
scope: input.scope,
|
|
1840
|
+
preferProjectVersion: input.preferProjectVersion
|
|
1789
1841
|
})));
|
|
1790
1842
|
server.tool("analyze-mod-jar", "Analyze a Minecraft mod JAR to extract loader type, metadata, entrypoints, mixins, and dependencies.", analyzeModJarShape, { readOnlyHint: true }, async (args) => runTool("analyze-mod-jar", args, analyzeModJarSchema, async (input) => {
|
|
1791
1843
|
const result = await analyzeModJar(input.jarPath, {
|
|
@@ -3,6 +3,7 @@ export interface MappingPipelineInput {
|
|
|
3
3
|
requestedMapping: SourceMapping;
|
|
4
4
|
target: SourceTargetInput;
|
|
5
5
|
resolved: ResolvedSourceArtifact;
|
|
6
|
+
runtimeNamesUnobfuscated?: boolean;
|
|
6
7
|
}
|
|
7
8
|
export interface MappingPipelineResult {
|
|
8
9
|
mappingApplied: SourceMapping;
|
|
@@ -13,6 +14,7 @@ export interface MappingPipelineResult {
|
|
|
13
14
|
* Mapping pipeline for v0.3.
|
|
14
15
|
* Current implementation enforces explicit guarantees:
|
|
15
16
|
* - obfuscated: always pass-through
|
|
16
|
-
* - mojang: requires source-backed
|
|
17
|
+
* - mojang: requires source-backed artifacts on legacy obfuscated versions,
|
|
18
|
+
* but unobfuscated runtime jars can pass through directly
|
|
17
19
|
*/
|
|
18
20
|
export declare function applyMappingPipeline(input: MappingPipelineInput): MappingPipelineResult;
|
|
@@ -3,7 +3,8 @@ import { createError, ERROR_CODES } from "./errors.js";
|
|
|
3
3
|
* Mapping pipeline for v0.3.
|
|
4
4
|
* Current implementation enforces explicit guarantees:
|
|
5
5
|
* - obfuscated: always pass-through
|
|
6
|
-
* - mojang: requires source-backed
|
|
6
|
+
* - mojang: requires source-backed artifacts on legacy obfuscated versions,
|
|
7
|
+
* but unobfuscated runtime jars can pass through directly
|
|
7
8
|
*/
|
|
8
9
|
export function applyMappingPipeline(input) {
|
|
9
10
|
const transformChain = [];
|
|
@@ -22,6 +23,20 @@ export function applyMappingPipeline(input) {
|
|
|
22
23
|
transformChain
|
|
23
24
|
};
|
|
24
25
|
}
|
|
26
|
+
if (input.requestedMapping === "mojang" && input.runtimeNamesUnobfuscated) {
|
|
27
|
+
transformChain.push("mapping:mojang-runtime-unobfuscated");
|
|
28
|
+
if (input.resolved.isDecompiled) {
|
|
29
|
+
qualityFlags.push("decompiled");
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
qualityFlags.push("source-backed");
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
mappingApplied: "mojang",
|
|
36
|
+
qualityFlags,
|
|
37
|
+
transformChain
|
|
38
|
+
};
|
|
39
|
+
}
|
|
25
40
|
if (input.requestedMapping !== "mojang" &&
|
|
26
41
|
input.requestedMapping !== "intermediary" &&
|
|
27
42
|
input.requestedMapping !== "yarn") {
|
|
@@ -62,6 +62,7 @@ export type FindMappingInput = {
|
|
|
62
62
|
sourceMapping: SourceMapping;
|
|
63
63
|
targetMapping: SourceMapping;
|
|
64
64
|
sourcePriority?: MappingSourcePriority;
|
|
65
|
+
projectPath?: string;
|
|
65
66
|
disambiguation?: {
|
|
66
67
|
ownerHint?: string;
|
|
67
68
|
descriptorHint?: string;
|
|
@@ -74,6 +75,7 @@ export type EnsureMappingAvailableInput = {
|
|
|
74
75
|
sourceMapping: SourceMapping;
|
|
75
76
|
targetMapping: SourceMapping;
|
|
76
77
|
sourcePriority?: MappingSourcePriority;
|
|
78
|
+
projectPath?: string;
|
|
77
79
|
};
|
|
78
80
|
export type EnsureMappingAvailableOutput = {
|
|
79
81
|
transformChain: string[];
|
|
@@ -88,6 +90,7 @@ export type ResolveMethodMappingExactInput = {
|
|
|
88
90
|
sourceMapping: SourceMapping;
|
|
89
91
|
targetMapping: SourceMapping;
|
|
90
92
|
sourcePriority?: MappingSourcePriority;
|
|
93
|
+
projectPath?: string;
|
|
91
94
|
maxCandidates?: number;
|
|
92
95
|
};
|
|
93
96
|
export type ResolveMethodMappingExactOutput = SymbolResolutionOutput;
|
|
@@ -153,6 +156,7 @@ export declare class MappingService {
|
|
|
153
156
|
checkSymbolExists(input: SymbolExistenceInput): Promise<SymbolExistenceOutput>;
|
|
154
157
|
private mapRecordBetweenMappings;
|
|
155
158
|
private mapCandidatesAlongPath;
|
|
159
|
+
private projectMethodDescriptorToTarget;
|
|
156
160
|
private provenanceForPath;
|
|
157
161
|
/**
|
|
158
162
|
* Probe the mapping graph health for a given version.
|
package/dist/mapping-service.js
CHANGED
|
@@ -3,8 +3,9 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import fastGlob from "fast-glob";
|
|
5
5
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
6
|
+
import { buildVersionSourceSearchRoots, normalizeOptionalProjectPath } from "./gradle-paths.js";
|
|
6
7
|
import { defaultDownloadPath, downloadToCache } from "./repo-downloader.js";
|
|
7
|
-
import {
|
|
8
|
+
import { collectMatchedJarEntriesAsUtf8 } from "./source-jar-reader.js";
|
|
8
9
|
import { VersionService, isUnobfuscatedVersion } from "./version-service.js";
|
|
9
10
|
const SUPPORTED_MAPPINGS = new Set([
|
|
10
11
|
"obfuscated",
|
|
@@ -19,6 +20,7 @@ const MATCH_RANK = {
|
|
|
19
20
|
};
|
|
20
21
|
const DESCRIPTOR_FALLBACK_CONFIDENCE = 0.85;
|
|
21
22
|
const MAX_CANDIDATES = 200;
|
|
23
|
+
const GLOB_SPECIAL_CHARS = /[\\!*+?()[\]{}@|]/g;
|
|
22
24
|
function createDirectionIndex() {
|
|
23
25
|
return {
|
|
24
26
|
exact: new Map(),
|
|
@@ -811,13 +813,32 @@ function applyDisambiguationHints(candidates, disambiguation) {
|
|
|
811
813
|
}
|
|
812
814
|
const descriptorHint = normalizeDescriptorHint(disambiguation.descriptorHint);
|
|
813
815
|
if (descriptorHint) {
|
|
814
|
-
const descriptorMatched = filtered.filter((candidate) => candidate.descriptor === descriptorHint);
|
|
816
|
+
const descriptorMatched = filtered.filter((candidate) => candidate.descriptor != null && candidate.descriptor === descriptorHint);
|
|
815
817
|
if (descriptorMatched.length > 0) {
|
|
816
818
|
filtered = descriptorMatched;
|
|
817
819
|
}
|
|
818
820
|
}
|
|
819
821
|
return filtered;
|
|
820
822
|
}
|
|
823
|
+
function projectLookupCandidateDescriptor(candidate, sourceDescriptor, targetDescriptor) {
|
|
824
|
+
// Tiny mappings preserve method descriptors verbatim, so single-hop tiny paths often
|
|
825
|
+
// return the source descriptor even though the final symbol is already in the target
|
|
826
|
+
// namespace. Multi-hop paths that already produced a target-side descriptor are left
|
|
827
|
+
// unchanged by design.
|
|
828
|
+
if (candidate.kind !== "method" ||
|
|
829
|
+
!candidate.descriptor ||
|
|
830
|
+
!targetDescriptor ||
|
|
831
|
+
candidate.descriptor !== sourceDescriptor) {
|
|
832
|
+
return candidate;
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
...candidate,
|
|
836
|
+
descriptor: targetDescriptor
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function effectiveLoomSearchProjectPath(projectPath) {
|
|
840
|
+
return normalizeOptionalProjectPath(projectPath) ?? normalizeOptionalProjectPath(process.cwd());
|
|
841
|
+
}
|
|
821
842
|
function collectTargetRecords(graph, targetMapping) {
|
|
822
843
|
return [...(graph.recordsByTarget.get(targetMapping) ?? [])];
|
|
823
844
|
}
|
|
@@ -953,7 +974,7 @@ export class MappingService {
|
|
|
953
974
|
warnings: []
|
|
954
975
|
};
|
|
955
976
|
}
|
|
956
|
-
const graph = await this.loadGraph(version, priority, requiresOnlyObfuscatedMojangGraph(sourceMapping, targetMapping) ? "obfuscated-mojang-only" : "full");
|
|
977
|
+
const graph = await this.loadGraph(version, priority, requiresOnlyObfuscatedMojangGraph(sourceMapping, targetMapping) ? "obfuscated-mojang-only" : "full", input.projectPath);
|
|
957
978
|
const path = namespacePath(graph, sourceMapping, targetMapping);
|
|
958
979
|
if (!path) {
|
|
959
980
|
return {
|
|
@@ -968,7 +989,15 @@ export class MappingService {
|
|
|
968
989
|
]
|
|
969
990
|
};
|
|
970
991
|
}
|
|
971
|
-
const
|
|
992
|
+
const descriptorProjection = queryRecord.kind === "method" && queryRecord.descriptor
|
|
993
|
+
? this.projectMethodDescriptorToTarget(graph, path, queryRecord.descriptor)
|
|
994
|
+
: undefined;
|
|
995
|
+
const projectedDescriptor = descriptorProjection?.complete ? descriptorProjection.descriptor : undefined;
|
|
996
|
+
const rawCandidates = this
|
|
997
|
+
.mapCandidatesAlongPath(graph, path, queryRecord)
|
|
998
|
+
.map((candidate) => queryRecord.kind === "method" && queryRecord.descriptor
|
|
999
|
+
? projectLookupCandidateDescriptor(candidate, queryRecord.descriptor, projectedDescriptor)
|
|
1000
|
+
: candidate);
|
|
972
1001
|
const warnings = [];
|
|
973
1002
|
const disambiguatedCandidates = applyDisambiguationHints(rawCandidates, input.disambiguation);
|
|
974
1003
|
if (rawCandidates.length > disambiguatedCandidates.length) {
|
|
@@ -1037,7 +1066,7 @@ export class MappingService {
|
|
|
1037
1066
|
warnings: []
|
|
1038
1067
|
};
|
|
1039
1068
|
}
|
|
1040
|
-
const graph = await this.loadGraph(version, priority, requiresOnlyObfuscatedMojangGraph(sourceMapping, targetMapping) ? "obfuscated-mojang-only" : "full");
|
|
1069
|
+
const graph = await this.loadGraph(version, priority, requiresOnlyObfuscatedMojangGraph(sourceMapping, targetMapping) ? "obfuscated-mojang-only" : "full", input.projectPath);
|
|
1041
1070
|
const path = namespacePath(graph, sourceMapping, targetMapping);
|
|
1042
1071
|
if (!path) {
|
|
1043
1072
|
throw createError({
|
|
@@ -1121,7 +1150,7 @@ export class MappingService {
|
|
|
1121
1150
|
warnings: []
|
|
1122
1151
|
};
|
|
1123
1152
|
}
|
|
1124
|
-
const graph = await this.loadGraph(version, priority, requiresOnlyObfuscatedMojangGraph(sourceMapping, targetMapping) ? "obfuscated-mojang-only" : "full");
|
|
1153
|
+
const graph = await this.loadGraph(version, priority, requiresOnlyObfuscatedMojangGraph(sourceMapping, targetMapping) ? "obfuscated-mojang-only" : "full", input.projectPath);
|
|
1125
1154
|
const path = namespacePath(graph, sourceMapping, targetMapping);
|
|
1126
1155
|
if (!path) {
|
|
1127
1156
|
return {
|
|
@@ -1137,12 +1166,16 @@ export class MappingService {
|
|
|
1137
1166
|
};
|
|
1138
1167
|
}
|
|
1139
1168
|
const warnings = [];
|
|
1169
|
+
const descriptorProjection = this.projectMethodDescriptorToTarget(graph, path, descriptor);
|
|
1170
|
+
const projectedDescriptor = descriptorProjection.complete ? descriptorProjection.descriptor : undefined;
|
|
1140
1171
|
const rawCandidates = this
|
|
1141
1172
|
.mapCandidatesAlongPath(graph, path, queryRecord)
|
|
1142
|
-
.filter((candidate) => candidate.kind === "method")
|
|
1173
|
+
.filter((candidate) => candidate.kind === "method")
|
|
1174
|
+
.map((candidate) => projectLookupCandidateDescriptor(candidate, descriptor, projectedDescriptor));
|
|
1143
1175
|
const candidates = rawCandidates.map(toResolutionCandidate);
|
|
1144
1176
|
const limitedCandidates = limitResolutionCandidates(candidates, input.maxCandidates);
|
|
1145
|
-
const
|
|
1177
|
+
const strictDescriptor = projectedDescriptor ?? descriptor;
|
|
1178
|
+
const strictCandidates = rawCandidates.filter((candidate) => candidate.descriptor === strictDescriptor);
|
|
1146
1179
|
if (strictCandidates.length === 1) {
|
|
1147
1180
|
const resolved = toResolutionCandidate(strictCandidates[0]);
|
|
1148
1181
|
return {
|
|
@@ -1172,8 +1205,10 @@ export class MappingService {
|
|
|
1172
1205
|
provenance: this.provenanceForPath(graph, path)
|
|
1173
1206
|
};
|
|
1174
1207
|
}
|
|
1175
|
-
if (
|
|
1176
|
-
warnings.push(
|
|
1208
|
+
if (descriptorProjection.hadClassReferences && !descriptorProjection.complete) {
|
|
1209
|
+
warnings.push(pathUsesSource(graph.pairs, path, "mojang-client-mappings")
|
|
1210
|
+
? "Method descriptor could not be preserved through mojang-client-mappings and exact resolution is unavailable."
|
|
1211
|
+
: "Method descriptor could not be fully remapped across the mapping path and exact resolution is unavailable.");
|
|
1177
1212
|
return {
|
|
1178
1213
|
querySymbol,
|
|
1179
1214
|
mappingContext,
|
|
@@ -1624,6 +1659,33 @@ export class MappingService {
|
|
|
1624
1659
|
descriptor: item.record.descriptor
|
|
1625
1660
|
}));
|
|
1626
1661
|
}
|
|
1662
|
+
projectMethodDescriptorToTarget(graph, path, descriptor) {
|
|
1663
|
+
let hadClassReferences = false;
|
|
1664
|
+
let complete = true;
|
|
1665
|
+
const classProjectionCache = new Map();
|
|
1666
|
+
const projectedDescriptor = descriptor.replace(/L([^;]+);/g, (fullMatch, internalName) => {
|
|
1667
|
+
hadClassReferences = true;
|
|
1668
|
+
const cached = classProjectionCache.get(internalName);
|
|
1669
|
+
if (cached) {
|
|
1670
|
+
return `L${cached};`;
|
|
1671
|
+
}
|
|
1672
|
+
const projectedClassCandidates = this
|
|
1673
|
+
.mapCandidatesAlongPath(graph, path, createClassSymbolRecord(internalName.replace(/\//g, ".")))
|
|
1674
|
+
.filter((candidate) => candidate.kind === "class");
|
|
1675
|
+
if (projectedClassCandidates.length !== 1) {
|
|
1676
|
+
complete = false;
|
|
1677
|
+
return fullMatch;
|
|
1678
|
+
}
|
|
1679
|
+
const projectedInternalName = projectedClassCandidates[0].symbol.replace(/\./g, "/");
|
|
1680
|
+
classProjectionCache.set(internalName, projectedInternalName);
|
|
1681
|
+
return `L${projectedInternalName};`;
|
|
1682
|
+
});
|
|
1683
|
+
return {
|
|
1684
|
+
descriptor: projectedDescriptor,
|
|
1685
|
+
hadClassReferences,
|
|
1686
|
+
complete
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1627
1689
|
provenanceForPath(graph, path) {
|
|
1628
1690
|
if (path.length <= 1) {
|
|
1629
1691
|
return undefined;
|
|
@@ -1646,6 +1708,18 @@ export class MappingService {
|
|
|
1646
1708
|
async checkMappingHealth(input) {
|
|
1647
1709
|
const priority = mappingPriorityFromInput(this.config.mappingSourcePriority, input.sourcePriority);
|
|
1648
1710
|
const degradations = [];
|
|
1711
|
+
if (isUnobfuscatedVersion(input.version)) {
|
|
1712
|
+
const requestFulfillable = input.requestedMapping === "obfuscated" || input.requestedMapping === "mojang";
|
|
1713
|
+
if (!requestFulfillable) {
|
|
1714
|
+
degradations.push(`Version ${input.version} is unobfuscated; ${input.requestedMapping} mappings are not applicable.`);
|
|
1715
|
+
}
|
|
1716
|
+
return {
|
|
1717
|
+
mojangMappingsAvailable: true,
|
|
1718
|
+
tinyMappingsAvailable: false,
|
|
1719
|
+
memberRemapAvailable: requestFulfillable,
|
|
1720
|
+
degradations
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1649
1723
|
let graph;
|
|
1650
1724
|
try {
|
|
1651
1725
|
graph = await this.loadGraph(input.version, priority, "full");
|
|
@@ -1692,8 +1766,9 @@ export class MappingService {
|
|
|
1692
1766
|
degradations
|
|
1693
1767
|
};
|
|
1694
1768
|
}
|
|
1695
|
-
async loadGraph(version, priority, mode) {
|
|
1696
|
-
const
|
|
1769
|
+
async loadGraph(version, priority, mode, projectPath) {
|
|
1770
|
+
const effectiveProjectPath = effectiveLoomSearchProjectPath(projectPath);
|
|
1771
|
+
const cacheKey = `${version}|${priority}|${mode}|${effectiveProjectPath ?? ""}`;
|
|
1697
1772
|
const cached = this.graphCache.get(cacheKey);
|
|
1698
1773
|
if (cached) {
|
|
1699
1774
|
this.graphCache.delete(cacheKey);
|
|
@@ -1704,7 +1779,7 @@ export class MappingService {
|
|
|
1704
1779
|
if (existingLock) {
|
|
1705
1780
|
return existingLock;
|
|
1706
1781
|
}
|
|
1707
|
-
const buildPromise = this.buildGraph(version, priority, mode);
|
|
1782
|
+
const buildPromise = this.buildGraph(version, priority, mode, effectiveProjectPath);
|
|
1708
1783
|
this.buildLocks.set(cacheKey, buildPromise);
|
|
1709
1784
|
try {
|
|
1710
1785
|
const built = await buildPromise;
|
|
@@ -1716,7 +1791,7 @@ export class MappingService {
|
|
|
1716
1791
|
this.buildLocks.delete(cacheKey);
|
|
1717
1792
|
}
|
|
1718
1793
|
}
|
|
1719
|
-
async buildGraph(version, priority, mode) {
|
|
1794
|
+
async buildGraph(version, priority, mode, projectPath) {
|
|
1720
1795
|
if (isUnobfuscatedVersion(version)) {
|
|
1721
1796
|
return {
|
|
1722
1797
|
version,
|
|
@@ -1749,7 +1824,7 @@ export class MappingService {
|
|
|
1749
1824
|
const deferredTinyWarnings = [];
|
|
1750
1825
|
for (const source of mappingSourceOrder(priority)) {
|
|
1751
1826
|
const tinyLoad = source === "loom-cache"
|
|
1752
|
-
? await this.loadTinyPairsFromLoom(version)
|
|
1827
|
+
? await this.loadTinyPairsFromLoom(version, projectPath)
|
|
1753
1828
|
: await this.loadTinyPairsFromMaven(version);
|
|
1754
1829
|
if (tinyLoad.pairs.size === 0) {
|
|
1755
1830
|
deferredTinyWarnings.push(...tinyLoad.warnings);
|
|
@@ -1847,46 +1922,67 @@ export class MappingService {
|
|
|
1847
1922
|
};
|
|
1848
1923
|
}
|
|
1849
1924
|
}
|
|
1850
|
-
async loadTinyPairsFromLoom(version) {
|
|
1851
|
-
const
|
|
1852
|
-
const candidates = fastGlob.sync(patterns, {
|
|
1853
|
-
cwd: process.cwd(),
|
|
1854
|
-
absolute: true,
|
|
1855
|
-
onlyFiles: true
|
|
1856
|
-
});
|
|
1857
|
-
const byVersion = candidates
|
|
1858
|
-
.filter((p) => p.replaceAll("\\", "/").includes(`/${version}/`))
|
|
1859
|
-
.sort((left, right) => left.localeCompare(right));
|
|
1860
|
-
if (byVersion.length === 0) {
|
|
1861
|
-
return {
|
|
1862
|
-
pairs: new Map(),
|
|
1863
|
-
warnings: [`No Loom tiny mapping files matched version "${version}".`],
|
|
1864
|
-
mappingArtifact: "loom-cache:none"
|
|
1865
|
-
};
|
|
1866
|
-
}
|
|
1925
|
+
async loadTinyPairsFromLoom(version, projectPath) {
|
|
1926
|
+
const searchRoots = buildVersionSourceSearchRoots(effectiveLoomSearchProjectPath(projectPath));
|
|
1867
1927
|
const merged = new Map();
|
|
1868
|
-
|
|
1928
|
+
const discoveredPaths = new Set();
|
|
1929
|
+
for (const root of searchRoots) {
|
|
1930
|
+
let discovered = [];
|
|
1931
|
+
const versionRoot = join(root, version);
|
|
1869
1932
|
try {
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1933
|
+
discovered = existsSync(versionRoot)
|
|
1934
|
+
? await fastGlob.glob(["**/*.tiny", "**/*.tinyv2"], {
|
|
1935
|
+
cwd: versionRoot,
|
|
1936
|
+
absolute: true,
|
|
1937
|
+
onlyFiles: true
|
|
1938
|
+
})
|
|
1939
|
+
: await fastGlob.glob([`${version.replace(GLOB_SPECIAL_CHARS, "\\$&")}/**/*.tiny`, `${version.replace(GLOB_SPECIAL_CHARS, "\\$&")}/**/*.tinyv2`], {
|
|
1940
|
+
cwd: root,
|
|
1941
|
+
absolute: true,
|
|
1942
|
+
onlyFiles: true
|
|
1943
|
+
});
|
|
1881
1944
|
}
|
|
1882
1945
|
catch {
|
|
1883
|
-
|
|
1946
|
+
continue;
|
|
1884
1947
|
}
|
|
1948
|
+
const byVersion = discovered
|
|
1949
|
+
.filter((path) => path.replaceAll("\\", "/").includes(`/${version}/`))
|
|
1950
|
+
.sort((left, right) => left.localeCompare(right));
|
|
1951
|
+
if (byVersion.length === 0) {
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1954
|
+
for (const path of byVersion) {
|
|
1955
|
+
discoveredPaths.add(path);
|
|
1956
|
+
try {
|
|
1957
|
+
const content = await readFile(path, "utf8");
|
|
1958
|
+
const parsed = parseTinyMappings(content);
|
|
1959
|
+
for (const [key, index] of parsed.entries()) {
|
|
1960
|
+
const existing = merged.get(key);
|
|
1961
|
+
if (!existing) {
|
|
1962
|
+
merged.set(key, index);
|
|
1963
|
+
}
|
|
1964
|
+
else {
|
|
1965
|
+
mergeDirectionIndexes(existing, index);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
catch {
|
|
1970
|
+
// best effort: skip unreadable or invalid files
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
const orderedPaths = [...discoveredPaths].sort((left, right) => left.localeCompare(right));
|
|
1975
|
+
if (orderedPaths.length > 0) {
|
|
1976
|
+
return {
|
|
1977
|
+
pairs: merged,
|
|
1978
|
+
warnings: [],
|
|
1979
|
+
mappingArtifact: orderedPaths[0]
|
|
1980
|
+
};
|
|
1885
1981
|
}
|
|
1886
1982
|
return {
|
|
1887
|
-
pairs:
|
|
1888
|
-
warnings: [],
|
|
1889
|
-
mappingArtifact:
|
|
1983
|
+
pairs: new Map(),
|
|
1984
|
+
warnings: [`No Loom tiny mapping files matched version "${version}".`],
|
|
1985
|
+
mappingArtifact: "loom-cache:none"
|
|
1890
1986
|
};
|
|
1891
1987
|
}
|
|
1892
1988
|
async loadTinyPairsFromMaven(version) {
|
|
@@ -1942,15 +2038,11 @@ export class MappingService {
|
|
|
1942
2038
|
};
|
|
1943
2039
|
}
|
|
1944
2040
|
async parseTinyFromJar(jarPath) {
|
|
1945
|
-
const
|
|
1946
|
-
const tinyEntries = entries
|
|
1947
|
-
.filter((entry) => entry.toLowerCase().endsWith(".tiny") || entry.toLowerCase().endsWith(".tinyv2"))
|
|
1948
|
-
.sort((left, right) => left.localeCompare(right));
|
|
2041
|
+
const tinyEntries = (await collectMatchedJarEntriesAsUtf8(jarPath, (entry) => entry.toLowerCase().endsWith(".tiny") || entry.toLowerCase().endsWith(".tinyv2"), { continueOnError: true })).sort((left, right) => left.filePath.localeCompare(right.filePath));
|
|
1949
2042
|
const merged = new Map();
|
|
1950
2043
|
for (const entry of tinyEntries) {
|
|
1951
2044
|
try {
|
|
1952
|
-
const
|
|
1953
|
-
const parsed = parseTinyMappings(text);
|
|
2045
|
+
const parsed = parseTinyMappings(entry.content);
|
|
1954
2046
|
for (const [key, index] of parsed.entries()) {
|
|
1955
2047
|
const existing = merged.get(key);
|
|
1956
2048
|
if (!existing) {
|
|
@@ -2008,8 +2100,12 @@ export class MappingService {
|
|
|
2008
2100
|
return;
|
|
2009
2101
|
}
|
|
2010
2102
|
const priority = mappingPriorityFromInput(this.config.mappingSourcePriority, sourcePriority);
|
|
2011
|
-
|
|
2012
|
-
this.graphCache.
|
|
2103
|
+
const prefix = `${normalizedVersion}|${priority}|`;
|
|
2104
|
+
for (const key of this.graphCache.keys()) {
|
|
2105
|
+
if (key.startsWith(prefix)) {
|
|
2106
|
+
this.graphCache.delete(key);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2013
2109
|
}
|
|
2014
2110
|
}
|
|
2015
2111
|
// ---------------------------------------------------------------------------
|
|
@@ -2039,14 +2135,13 @@ async function fetchYarnCoordinatesStandalone(version, fetchFn = globalThis.fetc
|
|
|
2039
2135
|
}
|
|
2040
2136
|
}
|
|
2041
2137
|
async function extractTinyFromJar(jarPath, outputPath) {
|
|
2042
|
-
const
|
|
2043
|
-
const tinyEntry =
|
|
2138
|
+
const matchedEntries = await collectMatchedJarEntriesAsUtf8(jarPath, (entry) => entry === "mappings/mappings.tiny" || entry.toLowerCase().endsWith(".tiny"), { maxEntries: 1 });
|
|
2139
|
+
const tinyEntry = matchedEntries[0];
|
|
2044
2140
|
if (!tinyEntry) {
|
|
2045
2141
|
return false;
|
|
2046
2142
|
}
|
|
2047
|
-
const content = await readJarEntryAsUtf8(jarPath, tinyEntry);
|
|
2048
2143
|
await mkdir(dirname(outputPath), { recursive: true });
|
|
2049
|
-
await writeFile(outputPath, content, "utf8");
|
|
2144
|
+
await writeFile(outputPath, tinyEntry.content, "utf8");
|
|
2050
2145
|
return true;
|
|
2051
2146
|
}
|
|
2052
2147
|
/**
|