@adhisang/minecraft-modding-mcp 3.1.1 → 4.0.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 +49 -0
- package/README.md +37 -18
- package/dist/access-transformer-parser.d.ts +17 -0
- package/dist/access-transformer-parser.js +97 -0
- package/dist/cache-registry.d.ts +1 -1
- package/dist/cache-registry.js +10 -2
- package/dist/concurrency.d.ts +1 -0
- package/dist/concurrency.js +24 -0
- package/dist/config.d.ts +10 -1
- package/dist/config.js +52 -1
- 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 +22 -22
- package/dist/entry-tools/analyze-symbol-service.js +13 -2
- package/dist/entry-tools/inspect-minecraft-service.d.ts +168 -168
- package/dist/entry-tools/inspect-minecraft-service.js +8 -2
- package/dist/entry-tools/manage-cache-service.d.ts +4 -4
- package/dist/entry-tools/validate-project-service.d.ts +153 -16
- package/dist/entry-tools/validate-project-service.js +442 -25
- package/dist/gradle-paths.d.ts +4 -0
- package/dist/gradle-paths.js +57 -0
- package/dist/index.js +148 -30
- package/dist/lru-list.d.ts +31 -0
- package/dist/lru-list.js +102 -0
- package/dist/mapping-pipeline-service.d.ts +12 -1
- package/dist/mapping-pipeline-service.js +28 -1
- package/dist/mapping-service.d.ts +16 -0
- package/dist/mapping-service.js +405 -68
- 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 +218 -17
- 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/observability.d.ts +18 -1
- package/dist/observability.js +44 -1
- package/dist/response-utils.d.ts +69 -0
- package/dist/response-utils.js +227 -0
- package/dist/source-jar-reader.d.ts +16 -0
- package/dist/source-jar-reader.js +103 -1
- package/dist/source-resolver.d.ts +9 -1
- package/dist/source-resolver.js +23 -16
- package/dist/source-service.d.ts +119 -3
- package/dist/source-service.js +1836 -218
- package/dist/storage/artifacts-repo.d.ts +4 -1
- package/dist/storage/artifacts-repo.js +33 -5
- package/dist/storage/files-repo.d.ts +0 -2
- package/dist/storage/files-repo.js +0 -11
- package/dist/storage/migrations.d.ts +1 -1
- package/dist/storage/migrations.js +10 -2
- package/dist/storage/schema.d.ts +2 -0
- package/dist/storage/schema.js +25 -0
- package/dist/tool-contract-manifest.js +8 -6
- package/dist/types.d.ts +20 -0
- package/dist/workspace-mapping-service.d.ts +13 -0
- package/dist/workspace-mapping-service.js +146 -14
- package/package.json +3 -1
|
@@ -25,12 +25,25 @@ export interface GetSignatureInput {
|
|
|
25
25
|
includeInherited?: boolean;
|
|
26
26
|
}
|
|
27
27
|
export interface GetSignatureOutput {
|
|
28
|
+
classAccessFlags?: number;
|
|
28
29
|
constructors: SignatureMember[];
|
|
29
30
|
methods: SignatureMember[];
|
|
30
31
|
fields: SignatureMember[];
|
|
31
32
|
warnings: string[];
|
|
32
33
|
context: ResponseContext;
|
|
33
34
|
}
|
|
35
|
+
export declare function modifierPrefix(flags: number, category: "method" | "field"): string;
|
|
36
|
+
export declare function parseFieldType(descriptor: string, position?: number, options?: {
|
|
37
|
+
allowVoid?: boolean;
|
|
38
|
+
invalidVoidMessage?: string;
|
|
39
|
+
}): {
|
|
40
|
+
type: string;
|
|
41
|
+
next: number;
|
|
42
|
+
};
|
|
43
|
+
export declare function parseMethodDescriptor(descriptor: string): {
|
|
44
|
+
args: string[];
|
|
45
|
+
returnType: string;
|
|
46
|
+
};
|
|
34
47
|
export declare class MinecraftExplorerService {
|
|
35
48
|
private readonly config;
|
|
36
49
|
private readonly signatureCache;
|
|
@@ -21,7 +21,7 @@ const ACC_SYNTHETIC = 0x1000;
|
|
|
21
21
|
function lower(value) {
|
|
22
22
|
return value.toLocaleLowerCase();
|
|
23
23
|
}
|
|
24
|
-
function modifierPrefix(flags, category) {
|
|
24
|
+
export function modifierPrefix(flags, category) {
|
|
25
25
|
const parts = [];
|
|
26
26
|
if ((flags & ACC_PUBLIC) !== 0) {
|
|
27
27
|
parts.push("public");
|
|
@@ -68,7 +68,7 @@ function modifierPrefix(flags, category) {
|
|
|
68
68
|
}
|
|
69
69
|
return parts.join(" ");
|
|
70
70
|
}
|
|
71
|
-
function parseFieldType(descriptor, position = 0, options = {}) {
|
|
71
|
+
export function parseFieldType(descriptor, position = 0, options = {}) {
|
|
72
72
|
if (position >= descriptor.length) {
|
|
73
73
|
throw createError({
|
|
74
74
|
code: ERROR_CODES.INVALID_INPUT,
|
|
@@ -127,7 +127,7 @@ function parseFieldType(descriptor, position = 0, options = {}) {
|
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
-
function parseMethodDescriptor(descriptor) {
|
|
130
|
+
export function parseMethodDescriptor(descriptor) {
|
|
131
131
|
if (!descriptor.startsWith("(")) {
|
|
132
132
|
throw createError({
|
|
133
133
|
code: ERROR_CODES.INVALID_INPUT,
|
|
@@ -372,7 +372,7 @@ function parseClassFile(buffer) {
|
|
|
372
372
|
});
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
|
-
reader.readU2();
|
|
375
|
+
const accessFlags = reader.readU2();
|
|
376
376
|
const thisClassIndex = reader.readU2();
|
|
377
377
|
const superClassIndex = reader.readU2();
|
|
378
378
|
const interfacesCount = reader.readU2();
|
|
@@ -404,6 +404,7 @@ function parseClassFile(buffer) {
|
|
|
404
404
|
const attributesCount = reader.readU2();
|
|
405
405
|
readAttributes(reader, cp, attributesCount);
|
|
406
406
|
return {
|
|
407
|
+
accessFlags,
|
|
407
408
|
internalName: readClassName(cp, thisClassIndex),
|
|
408
409
|
superInternalName: readOptionalClassName(cp, superClassIndex),
|
|
409
410
|
interfaceInternalNames,
|
|
@@ -442,6 +443,7 @@ export class MinecraftExplorerService {
|
|
|
442
443
|
const cached = this.signatureCache.get(cacheKey);
|
|
443
444
|
if (cached) {
|
|
444
445
|
return {
|
|
446
|
+
classAccessFlags: cached.classAccessFlags,
|
|
445
447
|
constructors: cached.constructors,
|
|
446
448
|
methods: cached.methods,
|
|
447
449
|
fields: cached.fields,
|
|
@@ -619,6 +621,7 @@ export class MinecraftExplorerService {
|
|
|
619
621
|
const fields = dedupeMembers(hierarchyClasses.flatMap((classFile) => toMembers(classFile, "field", (member) => shouldIncludeMember(member))));
|
|
620
622
|
const methods = dedupeMembers(hierarchyClasses.flatMap((classFile) => toMembers(classFile, "method", (member) => member.name !== "<init>" && shouldIncludeMember(member))));
|
|
621
623
|
const output = {
|
|
624
|
+
classAccessFlags: parsed.accessFlags,
|
|
622
625
|
constructors,
|
|
623
626
|
methods,
|
|
624
627
|
fields,
|
|
@@ -626,6 +629,7 @@ export class MinecraftExplorerService {
|
|
|
626
629
|
};
|
|
627
630
|
this.signatureCache.set(cacheKey, output);
|
|
628
631
|
return {
|
|
632
|
+
classAccessFlags: output.classAccessFlags,
|
|
629
633
|
constructors: output.constructors,
|
|
630
634
|
methods: output.methods,
|
|
631
635
|
fields: output.fields,
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
import type { SignatureMember } from "./minecraft-explorer-service.js";
|
|
6
6
|
import type { ParsedMixin } from "./mixin-parser.js";
|
|
7
7
|
import type { ParsedAccessWidener, AccessWidenerEntry } from "./access-widener-parser.js";
|
|
8
|
-
import type {
|
|
8
|
+
import type { ParsedAccessTransformer, AccessTransformerEntry } from "./access-transformer-parser.js";
|
|
9
|
+
import type { AccessTransformerNamespace, RuntimeValidationProvenance, SourceMapping } from "./types.js";
|
|
9
10
|
export type MappingHealthReport = {
|
|
10
11
|
jarAvailable: boolean;
|
|
11
12
|
jarPath: string;
|
|
@@ -131,6 +132,7 @@ export type MixinValidationResult = {
|
|
|
131
132
|
};
|
|
132
133
|
export type ResolvedTargetMembers = {
|
|
133
134
|
className: string;
|
|
135
|
+
classAccessFlags?: number;
|
|
134
136
|
constructors: SignatureMember[];
|
|
135
137
|
methods: SignatureMember[];
|
|
136
138
|
fields: SignatureMember[];
|
|
@@ -143,12 +145,36 @@ export type AccessWidenerValidationResult = {
|
|
|
143
145
|
valid: boolean;
|
|
144
146
|
issue?: string;
|
|
145
147
|
suggestions?: string[];
|
|
148
|
+
resolvedInRuntime?: boolean;
|
|
149
|
+
resolvedRuntimeAccess?: "public" | "protected" | "private" | "package-private";
|
|
150
|
+
resolvedRuntimeJvmDescriptor?: string;
|
|
151
|
+
resolvedRuntimeJavaSignature?: string;
|
|
146
152
|
}>;
|
|
147
153
|
summary: {
|
|
148
154
|
total: number;
|
|
149
155
|
valid: number;
|
|
150
156
|
invalid: number;
|
|
151
157
|
};
|
|
158
|
+
provenance?: RuntimeValidationProvenance<SourceMapping>;
|
|
159
|
+
warnings: string[];
|
|
160
|
+
};
|
|
161
|
+
export type AccessTransformerValidationResult = {
|
|
162
|
+
valid: boolean;
|
|
163
|
+
entries: Array<AccessTransformerEntry & {
|
|
164
|
+
valid: boolean;
|
|
165
|
+
issue?: string;
|
|
166
|
+
suggestions?: string[];
|
|
167
|
+
resolvedInRuntime?: boolean;
|
|
168
|
+
resolvedRuntimeAccess?: "public" | "protected" | "private" | "package-private";
|
|
169
|
+
resolvedRuntimeJvmDescriptor?: string;
|
|
170
|
+
resolvedRuntimeJavaSignature?: string;
|
|
171
|
+
}>;
|
|
172
|
+
summary: {
|
|
173
|
+
total: number;
|
|
174
|
+
valid: number;
|
|
175
|
+
invalid: number;
|
|
176
|
+
};
|
|
177
|
+
provenance?: RuntimeValidationProvenance<AccessTransformerNamespace>;
|
|
152
178
|
warnings: string[];
|
|
153
179
|
};
|
|
154
180
|
export declare function levenshteinDistance(a: string, b: string): number;
|
|
@@ -169,4 +195,9 @@ export declare function validateParsedMixin(parsed: ParsedMixin, targetMembers:
|
|
|
169
195
|
projectPath?: string;
|
|
170
196
|
mapping?: string;
|
|
171
197
|
}, warningMode?: "full" | "aggregated", healthReport?: MappingHealthReport, symbolExistsButSignatureFailed?: Set<string>): MixinValidationResult;
|
|
172
|
-
export declare function validateParsedAccessWidener(parsed: ParsedAccessWidener, membersByClass: Map<string, ResolvedTargetMembers>, warnings: string[]
|
|
198
|
+
export declare function validateParsedAccessWidener(parsed: ParsedAccessWidener, membersByClass: Map<string, ResolvedTargetMembers>, warnings: string[], options?: {
|
|
199
|
+
includeRuntimeEvidence?: boolean;
|
|
200
|
+
}): AccessWidenerValidationResult;
|
|
201
|
+
export declare function validateParsedAccessTransformer(parsed: ParsedAccessTransformer, membersByClass: Map<string, ResolvedTargetMembers>, warnings: string[], options?: {
|
|
202
|
+
includeRuntimeEvidence?: boolean;
|
|
203
|
+
}): AccessTransformerValidationResult;
|
package/dist/mixin-validator.js
CHANGED
|
@@ -133,6 +133,21 @@ function allMethodNames(members) {
|
|
|
133
133
|
function allFieldNames(members) {
|
|
134
134
|
return members.fields.map((m) => m.name);
|
|
135
135
|
}
|
|
136
|
+
function accessLevelFromFlags(accessFlags) {
|
|
137
|
+
if (accessFlags == null) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
if ((accessFlags & 0x0001) !== 0) {
|
|
141
|
+
return "public";
|
|
142
|
+
}
|
|
143
|
+
if ((accessFlags & 0x0004) !== 0) {
|
|
144
|
+
return "protected";
|
|
145
|
+
}
|
|
146
|
+
if ((accessFlags & 0x0002) !== 0) {
|
|
147
|
+
return "private";
|
|
148
|
+
}
|
|
149
|
+
return "package-private";
|
|
150
|
+
}
|
|
136
151
|
function computeFalsePositiveRisk(healthReport, resolutionPath, issueConfidence) {
|
|
137
152
|
if (!healthReport)
|
|
138
153
|
return undefined;
|
|
@@ -210,11 +225,23 @@ function computeValidationStatus(summary) {
|
|
|
210
225
|
}
|
|
211
226
|
return "full";
|
|
212
227
|
}
|
|
213
|
-
function buildQuickSummary(status, summary) {
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
function buildQuickSummary(status, summary, context) {
|
|
229
|
+
const base = status === "full"
|
|
230
|
+
? `${summary.membersValidated} member(s) validated successfully.`
|
|
231
|
+
: `${summary.definiteErrors} error(s), ${summary.uncertainErrors} uncertain, ${summary.warnings} warning(s). ${summary.membersValidated} validated, ${summary.membersSkipped} member(s) skipped, ${summary.membersMissing} member(s) missing.`;
|
|
232
|
+
const notes = [];
|
|
233
|
+
const scopeFallback = context?.provenance?.scopeFallback;
|
|
234
|
+
if (scopeFallback) {
|
|
235
|
+
notes.push(`Scope fell back from "${scopeFallback.requested}" to "${scopeFallback.applied}" (${scopeFallback.reason}).`);
|
|
216
236
|
}
|
|
217
|
-
|
|
237
|
+
const healthReport = context?.healthReport;
|
|
238
|
+
if (healthReport && !healthReport.overallHealthy) {
|
|
239
|
+
const degradations = healthReport.degradations.length > 0
|
|
240
|
+
? healthReport.degradations.join("; ")
|
|
241
|
+
: "mapping infrastructure degraded";
|
|
242
|
+
notes.push(`Mapping health degraded: ${degradations}.`);
|
|
243
|
+
}
|
|
244
|
+
return notes.length > 0 ? `${base} ${notes.join(" ")}` : base;
|
|
218
245
|
}
|
|
219
246
|
function addSkippedMembers(parsed, resolvedMembers) {
|
|
220
247
|
for (const inj of parsed.injections) {
|
|
@@ -256,7 +283,10 @@ export function refreshMixinValidationOutcome(result) {
|
|
|
256
283
|
};
|
|
257
284
|
result.validationStatus = computeValidationStatus(result.summary);
|
|
258
285
|
result.valid = result.summary.definiteErrors === 0;
|
|
259
|
-
result.quickSummary = buildQuickSummary(result.validationStatus, result.summary
|
|
286
|
+
result.quickSummary = buildQuickSummary(result.validationStatus, result.summary, {
|
|
287
|
+
provenance: result.provenance,
|
|
288
|
+
healthReport: result.toolHealth
|
|
289
|
+
});
|
|
260
290
|
return result;
|
|
261
291
|
}
|
|
262
292
|
function validateInjection(inj, targetMembers, targetNames, issues, resolvedMembers, confidence, confidenceReason, remapFailedMembers, signatureFailedTargets, healthReport) {
|
|
@@ -712,7 +742,7 @@ export function validateParsedMixin(parsed, targetMembers, warnings, provenance,
|
|
|
712
742
|
parseWarnings: parseWarningCount
|
|
713
743
|
};
|
|
714
744
|
const validationStatus = computeValidationStatus(summary);
|
|
715
|
-
const quickSummary = buildQuickSummary(validationStatus, summary);
|
|
745
|
+
const quickSummary = buildQuickSummary(validationStatus, summary, { provenance, healthReport });
|
|
716
746
|
return {
|
|
717
747
|
className: parsed.className,
|
|
718
748
|
targets: targetNames,
|
|
@@ -735,7 +765,7 @@ export function validateParsedMixin(parsed, targetMembers, warnings, provenance,
|
|
|
735
765
|
/* ------------------------------------------------------------------ */
|
|
736
766
|
/* Access Widener validation */
|
|
737
767
|
/* ------------------------------------------------------------------ */
|
|
738
|
-
export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
|
|
768
|
+
export function validateParsedAccessWidener(parsed, membersByClass, warnings, options) {
|
|
739
769
|
warnings.push(...parsed.parseWarnings);
|
|
740
770
|
const validatedEntries = [];
|
|
741
771
|
let validCount = 0;
|
|
@@ -743,15 +773,29 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
|
|
|
743
773
|
for (const entry of parsed.entries) {
|
|
744
774
|
const ownerFqn = entry.target.replace(/\//g, ".");
|
|
745
775
|
if (entry.targetKind === "class") {
|
|
746
|
-
|
|
747
|
-
|
|
776
|
+
const members = membersByClass.get(ownerFqn);
|
|
777
|
+
if (members) {
|
|
778
|
+
const runtimeAccess = accessLevelFromFlags(members.classAccessFlags);
|
|
779
|
+
validatedEntries.push({
|
|
780
|
+
...entry,
|
|
781
|
+
valid: true,
|
|
782
|
+
...(options?.includeRuntimeEvidence
|
|
783
|
+
? {
|
|
784
|
+
resolvedInRuntime: true,
|
|
785
|
+
...(runtimeAccess
|
|
786
|
+
? { resolvedRuntimeAccess: runtimeAccess }
|
|
787
|
+
: {})
|
|
788
|
+
}
|
|
789
|
+
: {})
|
|
790
|
+
});
|
|
748
791
|
validCount++;
|
|
749
792
|
}
|
|
750
793
|
else {
|
|
751
794
|
validatedEntries.push({
|
|
752
795
|
...entry,
|
|
753
796
|
valid: false,
|
|
754
|
-
issue: `Class "${ownerFqn}" not found in game jar
|
|
797
|
+
issue: `Class "${ownerFqn}" not found in game jar.`,
|
|
798
|
+
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
755
799
|
});
|
|
756
800
|
invalidCount++;
|
|
757
801
|
}
|
|
@@ -763,16 +807,37 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
|
|
|
763
807
|
validatedEntries.push({
|
|
764
808
|
...entry,
|
|
765
809
|
valid: false,
|
|
766
|
-
issue: `Owner class "${ownerFqn}" not found in game jar
|
|
810
|
+
issue: `Owner class "${ownerFqn}" not found in game jar.`,
|
|
811
|
+
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
767
812
|
});
|
|
768
813
|
invalidCount++;
|
|
769
814
|
continue;
|
|
770
815
|
}
|
|
771
816
|
if (entry.targetKind === "method") {
|
|
772
817
|
const methodNames = allMethodNames(members);
|
|
773
|
-
const
|
|
818
|
+
const matchedMember = members.methods.find((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor)) ?? members.constructors.find((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor));
|
|
819
|
+
const found = matchedMember != null;
|
|
774
820
|
if (found) {
|
|
775
|
-
|
|
821
|
+
const runtimeMember = matchedMember;
|
|
822
|
+
const runtimeAccess = accessLevelFromFlags(runtimeMember.accessFlags);
|
|
823
|
+
validatedEntries.push({
|
|
824
|
+
...entry,
|
|
825
|
+
valid: true,
|
|
826
|
+
...(options?.includeRuntimeEvidence
|
|
827
|
+
? {
|
|
828
|
+
resolvedInRuntime: true,
|
|
829
|
+
...(runtimeAccess
|
|
830
|
+
? { resolvedRuntimeAccess: runtimeAccess }
|
|
831
|
+
: {}),
|
|
832
|
+
...(runtimeMember.jvmDescriptor
|
|
833
|
+
? { resolvedRuntimeJvmDescriptor: runtimeMember.jvmDescriptor }
|
|
834
|
+
: {}),
|
|
835
|
+
...(runtimeMember.javaSignature
|
|
836
|
+
? { resolvedRuntimeJavaSignature: runtimeMember.javaSignature }
|
|
837
|
+
: {})
|
|
838
|
+
}
|
|
839
|
+
: {})
|
|
840
|
+
});
|
|
776
841
|
validCount++;
|
|
777
842
|
}
|
|
778
843
|
else {
|
|
@@ -781,7 +846,8 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
|
|
|
781
846
|
...entry,
|
|
782
847
|
valid: false,
|
|
783
848
|
issue: `Method "${entry.name}" not found in class "${ownerFqn}".`,
|
|
784
|
-
suggestions: suggestions.length > 0 ? suggestions : undefined
|
|
849
|
+
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
850
|
+
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
785
851
|
});
|
|
786
852
|
invalidCount++;
|
|
787
853
|
}
|
|
@@ -789,9 +855,29 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
|
|
|
789
855
|
else {
|
|
790
856
|
// field
|
|
791
857
|
const fieldNames = allFieldNames(members);
|
|
792
|
-
const
|
|
858
|
+
const matchedMember = members.fields.find((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor));
|
|
859
|
+
const found = matchedMember != null;
|
|
793
860
|
if (found) {
|
|
794
|
-
|
|
861
|
+
const runtimeMember = matchedMember;
|
|
862
|
+
const runtimeAccess = accessLevelFromFlags(runtimeMember.accessFlags);
|
|
863
|
+
validatedEntries.push({
|
|
864
|
+
...entry,
|
|
865
|
+
valid: true,
|
|
866
|
+
...(options?.includeRuntimeEvidence
|
|
867
|
+
? {
|
|
868
|
+
resolvedInRuntime: true,
|
|
869
|
+
...(runtimeAccess
|
|
870
|
+
? { resolvedRuntimeAccess: runtimeAccess }
|
|
871
|
+
: {}),
|
|
872
|
+
...(runtimeMember.jvmDescriptor
|
|
873
|
+
? { resolvedRuntimeJvmDescriptor: runtimeMember.jvmDescriptor }
|
|
874
|
+
: {}),
|
|
875
|
+
...(runtimeMember.javaSignature
|
|
876
|
+
? { resolvedRuntimeJavaSignature: runtimeMember.javaSignature }
|
|
877
|
+
: {})
|
|
878
|
+
}
|
|
879
|
+
: {})
|
|
880
|
+
});
|
|
795
881
|
validCount++;
|
|
796
882
|
}
|
|
797
883
|
else {
|
|
@@ -800,7 +886,8 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
|
|
|
800
886
|
...entry,
|
|
801
887
|
valid: false,
|
|
802
888
|
issue: `Field "${entry.name}" not found in class "${ownerFqn}".`,
|
|
803
|
-
suggestions: suggestions.length > 0 ? suggestions : undefined
|
|
889
|
+
suggestions: suggestions.length > 0 ? suggestions : undefined,
|
|
890
|
+
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
804
891
|
});
|
|
805
892
|
invalidCount++;
|
|
806
893
|
}
|
|
@@ -819,4 +906,118 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
|
|
|
819
906
|
warnings
|
|
820
907
|
};
|
|
821
908
|
}
|
|
909
|
+
export function validateParsedAccessTransformer(parsed, membersByClass, warnings, options) {
|
|
910
|
+
warnings.push(...parsed.parseWarnings);
|
|
911
|
+
const validatedEntries = [];
|
|
912
|
+
let validCount = 0;
|
|
913
|
+
let invalidCount = 0;
|
|
914
|
+
for (const entry of parsed.entries) {
|
|
915
|
+
const ownerFqn = entry.owner;
|
|
916
|
+
const members = membersByClass.get(ownerFqn);
|
|
917
|
+
if (entry.targetKind === "class") {
|
|
918
|
+
if (!members) {
|
|
919
|
+
validatedEntries.push({
|
|
920
|
+
...entry,
|
|
921
|
+
valid: false,
|
|
922
|
+
issue: `Class "${ownerFqn}" not found in runtime jar.`,
|
|
923
|
+
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
924
|
+
});
|
|
925
|
+
invalidCount++;
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
const runtimeAccess = accessLevelFromFlags(members.classAccessFlags);
|
|
929
|
+
validatedEntries.push({
|
|
930
|
+
...entry,
|
|
931
|
+
valid: true,
|
|
932
|
+
...(options?.includeRuntimeEvidence
|
|
933
|
+
? {
|
|
934
|
+
resolvedInRuntime: true,
|
|
935
|
+
...(runtimeAccess ? { resolvedRuntimeAccess: runtimeAccess } : {})
|
|
936
|
+
}
|
|
937
|
+
: {})
|
|
938
|
+
});
|
|
939
|
+
validCount++;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
if (!members) {
|
|
943
|
+
validatedEntries.push({
|
|
944
|
+
...entry,
|
|
945
|
+
valid: false,
|
|
946
|
+
issue: `Owner class "${ownerFqn}" not found in runtime jar.`,
|
|
947
|
+
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
948
|
+
});
|
|
949
|
+
invalidCount++;
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
if (entry.targetKind === "field") {
|
|
953
|
+
const fieldNames = allFieldNames(members);
|
|
954
|
+
const matchedField = members.fields.find((member) => member.name === entry.name);
|
|
955
|
+
if (!matchedField) {
|
|
956
|
+
const suggestions = entry.name ? suggestSimilar(entry.name, fieldNames) : [];
|
|
957
|
+
validatedEntries.push({
|
|
958
|
+
...entry,
|
|
959
|
+
valid: false,
|
|
960
|
+
issue: `Field "${entry.name}" not found in class "${ownerFqn}".`,
|
|
961
|
+
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
962
|
+
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
963
|
+
});
|
|
964
|
+
invalidCount++;
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
const runtimeAccess = accessLevelFromFlags(matchedField.accessFlags);
|
|
968
|
+
validatedEntries.push({
|
|
969
|
+
...entry,
|
|
970
|
+
valid: true,
|
|
971
|
+
...(options?.includeRuntimeEvidence
|
|
972
|
+
? {
|
|
973
|
+
resolvedInRuntime: true,
|
|
974
|
+
...(runtimeAccess ? { resolvedRuntimeAccess: runtimeAccess } : {}),
|
|
975
|
+
...(matchedField.jvmDescriptor ? { resolvedRuntimeJvmDescriptor: matchedField.jvmDescriptor } : {}),
|
|
976
|
+
...(matchedField.javaSignature ? { resolvedRuntimeJavaSignature: matchedField.javaSignature } : {})
|
|
977
|
+
}
|
|
978
|
+
: {})
|
|
979
|
+
});
|
|
980
|
+
validCount++;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
const methodNames = allMethodNames(members);
|
|
984
|
+
const matchedMethod = members.methods.find((member) => member.name === entry.name && member.jvmDescriptor === entry.descriptor) ?? members.constructors.find((member) => member.name === entry.name && member.jvmDescriptor === entry.descriptor);
|
|
985
|
+
if (!matchedMethod) {
|
|
986
|
+
const suggestions = entry.name ? suggestSimilar(entry.name, methodNames) : [];
|
|
987
|
+
validatedEntries.push({
|
|
988
|
+
...entry,
|
|
989
|
+
valid: false,
|
|
990
|
+
issue: `Method "${entry.name}" not found in class "${ownerFqn}".`,
|
|
991
|
+
...(suggestions.length > 0 ? { suggestions } : {}),
|
|
992
|
+
...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
|
|
993
|
+
});
|
|
994
|
+
invalidCount++;
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
const runtimeAccess = accessLevelFromFlags(matchedMethod.accessFlags);
|
|
998
|
+
validatedEntries.push({
|
|
999
|
+
...entry,
|
|
1000
|
+
valid: true,
|
|
1001
|
+
...(options?.includeRuntimeEvidence
|
|
1002
|
+
? {
|
|
1003
|
+
resolvedInRuntime: true,
|
|
1004
|
+
...(runtimeAccess ? { resolvedRuntimeAccess: runtimeAccess } : {}),
|
|
1005
|
+
...(matchedMethod.jvmDescriptor ? { resolvedRuntimeJvmDescriptor: matchedMethod.jvmDescriptor } : {}),
|
|
1006
|
+
...(matchedMethod.javaSignature ? { resolvedRuntimeJavaSignature: matchedMethod.javaSignature } : {})
|
|
1007
|
+
}
|
|
1008
|
+
: {})
|
|
1009
|
+
});
|
|
1010
|
+
validCount++;
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
valid: invalidCount === 0,
|
|
1014
|
+
entries: validatedEntries,
|
|
1015
|
+
summary: {
|
|
1016
|
+
total: parsed.entries.length,
|
|
1017
|
+
valid: validCount,
|
|
1018
|
+
invalid: invalidCount
|
|
1019
|
+
},
|
|
1020
|
+
warnings
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
822
1023
|
//# sourceMappingURL=mixin-validator.js.map
|
package/dist/mod-analyzer.d.ts
CHANGED
package/dist/mod-analyzer.js
CHANGED
|
@@ -72,7 +72,8 @@ const forgeModsTomlSchema = z
|
|
|
72
72
|
.passthrough())
|
|
73
73
|
.optional(),
|
|
74
74
|
dependencies: z.record(z.array(z.unknown())).optional(),
|
|
75
|
-
mixins: z.array(z.object({ config: z.string() }).passthrough()).optional()
|
|
75
|
+
mixins: z.array(z.object({ config: z.string() }).passthrough()).optional(),
|
|
76
|
+
accessTransformers: z.array(z.object({ file: z.string().optional() }).passthrough()).optional()
|
|
76
77
|
})
|
|
77
78
|
.passthrough();
|
|
78
79
|
const legacyForgeSchema = z.array(z
|
|
@@ -214,6 +215,9 @@ function parseForgeMod(content, entries) {
|
|
|
214
215
|
}
|
|
215
216
|
// Mixin configs
|
|
216
217
|
const mixinConfigs = toml.mixins?.map((m) => m.config);
|
|
218
|
+
const accessTransformers = toml.accessTransformers
|
|
219
|
+
?.map((entry) => entry.file)
|
|
220
|
+
.filter((file) => typeof file === "string" && file.trim().length > 0);
|
|
217
221
|
return {
|
|
218
222
|
detectedLoader,
|
|
219
223
|
modId: firstMod?.modId,
|
|
@@ -221,6 +225,7 @@ function parseForgeMod(content, entries) {
|
|
|
221
225
|
modVersion: firstMod?.version,
|
|
222
226
|
description: firstMod?.description,
|
|
223
227
|
mixinConfigs: mixinConfigs && mixinConfigs.length > 0 ? mixinConfigs : undefined,
|
|
228
|
+
accessTransformers: accessTransformers && accessTransformers.length > 0 ? accessTransformers : undefined,
|
|
224
229
|
dependencies: dependencies.length > 0 ? dependencies : undefined
|
|
225
230
|
};
|
|
226
231
|
}
|
|
@@ -288,6 +293,9 @@ export async function analyzeModJar(jarPath, options) {
|
|
|
288
293
|
// Detect loader and parse metadata
|
|
289
294
|
let loader = "unknown";
|
|
290
295
|
let metadata = {};
|
|
296
|
+
const packagedAccessTransformers = [...new Set(entries.filter((entry) => /(^|\/)META-INF\/accesstransformer\.cfg$/i.test(entry) ||
|
|
297
|
+
/(^|\/)[^/]+_at\.cfg$/i.test(entry) ||
|
|
298
|
+
/(^|\/)accesstransformer[^/]*\.cfg$/i.test(entry)))].sort((left, right) => left.localeCompare(right));
|
|
291
299
|
if (entries.includes("fabric.mod.json")) {
|
|
292
300
|
loader = "fabric";
|
|
293
301
|
try {
|
|
@@ -346,6 +354,14 @@ export async function analyzeModJar(jarPath, options) {
|
|
|
346
354
|
loader,
|
|
347
355
|
jarKind,
|
|
348
356
|
...metadata,
|
|
357
|
+
...(packagedAccessTransformers.length > 0 || metadata.accessTransformers
|
|
358
|
+
? {
|
|
359
|
+
accessTransformers: [...new Set([
|
|
360
|
+
...(metadata.accessTransformers ?? []),
|
|
361
|
+
...packagedAccessTransformers
|
|
362
|
+
])]
|
|
363
|
+
}
|
|
364
|
+
: {}),
|
|
349
365
|
classCount,
|
|
350
366
|
...(classes !== undefined ? { classes } : {})
|
|
351
367
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, join, resolve as resolvePath } from "node:path";
|
|
4
4
|
import { createError, ERROR_CODES } from "./errors.js";
|
|
5
5
|
import { log } from "./logger.js";
|
|
@@ -41,7 +41,7 @@ export class ModDecompileService {
|
|
|
41
41
|
const targetFile = classNameToFilePath(input.className);
|
|
42
42
|
const matched = files.find((f) => f === targetFile || f.endsWith(`/${targetFile}`) || f === input.className);
|
|
43
43
|
if (matched) {
|
|
44
|
-
const content =
|
|
44
|
+
const content = await readFile(join(outputDir, matched), "utf8");
|
|
45
45
|
sourceResult = {
|
|
46
46
|
className: filePathToClassName(matched),
|
|
47
47
|
content,
|
|
@@ -106,7 +106,7 @@ export class ModDecompileService {
|
|
|
106
106
|
details: { className, jarPath, availableCount: files.length }
|
|
107
107
|
});
|
|
108
108
|
}
|
|
109
|
-
const fullContent =
|
|
109
|
+
const fullContent = await readFile(join(outputDir, matched), "utf8");
|
|
110
110
|
const totalLines = fullContent.split("\n").length;
|
|
111
111
|
let content = fullContent;
|
|
112
112
|
let truncated;
|
|
@@ -131,7 +131,7 @@ export class ModDecompileService {
|
|
|
131
131
|
const outPath = isAbsolute(input.outputFile)
|
|
132
132
|
? input.outputFile
|
|
133
133
|
: resolvePath(input.outputFile);
|
|
134
|
-
|
|
134
|
+
await writeFile(outPath, content, "utf8");
|
|
135
135
|
outputFilePath = outPath;
|
|
136
136
|
content = `[Written to ${outPath}]`;
|
|
137
137
|
}
|
|
@@ -8,7 +8,7 @@ import { resolveTinyMappingFile } from "./mapping-service.js";
|
|
|
8
8
|
import { resolveMojangTinyFile } from "./mojang-tiny-mapping-service.js";
|
|
9
9
|
import { analyzeModJar } from "./mod-analyzer.js";
|
|
10
10
|
import { normalizePathForHost } from "./path-converter.js";
|
|
11
|
-
import {
|
|
11
|
+
import { detectFabricLikeInputNamespace } from "./source-jar-reader.js";
|
|
12
12
|
import { remapJar } from "./tiny-remapper-service.js";
|
|
13
13
|
import { resolveTinyRemapperJar } from "./tiny-remapper-resolver.js";
|
|
14
14
|
function normalizeTargetNamespace(target) {
|
|
@@ -36,59 +36,6 @@ function extractMinecraftVersion(dependencies) {
|
|
|
36
36
|
const match = mcDep.versionRange.match(/(\d+\.\d+(?:\.\d+)?)/);
|
|
37
37
|
return match?.[1];
|
|
38
38
|
}
|
|
39
|
-
function countMatches(input, pattern) {
|
|
40
|
-
const flags = pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`;
|
|
41
|
-
const globalPattern = new RegExp(pattern.source, flags);
|
|
42
|
-
let count = 0;
|
|
43
|
-
while (globalPattern.exec(input)) {
|
|
44
|
-
count += 1;
|
|
45
|
-
}
|
|
46
|
-
return count;
|
|
47
|
-
}
|
|
48
|
-
async function detectFabricLikeInputNamespace(inputJar) {
|
|
49
|
-
const warnings = [];
|
|
50
|
-
const classEntries = (await listJarEntries(inputJar))
|
|
51
|
-
.filter((entry) => entry.endsWith(".class"))
|
|
52
|
-
.slice(0, 24);
|
|
53
|
-
if (classEntries.length === 0) {
|
|
54
|
-
warnings.push("Could not inspect class entries to detect input mapping; assuming intermediary.");
|
|
55
|
-
return {
|
|
56
|
-
fromNamespace: "intermediary",
|
|
57
|
-
warnings
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
let mojangScore = 0;
|
|
61
|
-
let intermediaryScore = 0;
|
|
62
|
-
for (const entry of classEntries) {
|
|
63
|
-
let text = "";
|
|
64
|
-
try {
|
|
65
|
-
text = (await readJarEntryAsBuffer(inputJar, entry)).toString("latin1");
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
mojangScore += countMatches(text, /net\/minecraft\/(?:advancements|client|commands|core|data|gametest|nbt|network|recipe|resources|server|sounds|stats|tags|util|world)\//g) * 3;
|
|
71
|
-
intermediaryScore += countMatches(text, /net\/minecraft\/class_\d+/g) * 3;
|
|
72
|
-
intermediaryScore += countMatches(text, /\b(?:method|field)_\d+\b/g);
|
|
73
|
-
}
|
|
74
|
-
if (mojangScore > intermediaryScore && mojangScore > 0) {
|
|
75
|
-
return {
|
|
76
|
-
fromNamespace: "mojang",
|
|
77
|
-
warnings
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
if (intermediaryScore > mojangScore && intermediaryScore > 0) {
|
|
81
|
-
return {
|
|
82
|
-
fromNamespace: "intermediary",
|
|
83
|
-
warnings
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
warnings.push("Could not confidently detect whether the input jar uses intermediary or mojang names; assuming intermediary.");
|
|
87
|
-
return {
|
|
88
|
-
fromNamespace: "intermediary",
|
|
89
|
-
warnings
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
39
|
async function detectInputNamespaceForLoader(inputJar, loader) {
|
|
93
40
|
if (loader === "fabric" || loader === "quilt") {
|
|
94
41
|
return detectFabricLikeInputNamespace(inputJar);
|