@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.
Files changed (41) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +20 -8
  3. package/dist/access-transformer-parser.d.ts +17 -0
  4. package/dist/access-transformer-parser.js +97 -0
  5. package/dist/concurrency.d.ts +1 -0
  6. package/dist/concurrency.js +24 -0
  7. package/dist/decompiler/vineflower.js +22 -21
  8. package/dist/entry-tools/analyze-mod-service.d.ts +4 -4
  9. package/dist/entry-tools/analyze-symbol-service.d.ts +20 -20
  10. package/dist/entry-tools/inspect-minecraft-service.d.ts +148 -148
  11. package/dist/entry-tools/validate-project-service.d.ts +153 -16
  12. package/dist/entry-tools/validate-project-service.js +360 -23
  13. package/dist/gradle-paths.d.ts +4 -0
  14. package/dist/gradle-paths.js +57 -0
  15. package/dist/index.js +65 -13
  16. package/dist/mapping-pipeline-service.d.ts +3 -1
  17. package/dist/mapping-pipeline-service.js +16 -1
  18. package/dist/mapping-service.d.ts +4 -0
  19. package/dist/mapping-service.js +155 -60
  20. package/dist/minecraft-explorer-service.d.ts +13 -0
  21. package/dist/minecraft-explorer-service.js +8 -4
  22. package/dist/mixin-validator.d.ts +33 -2
  23. package/dist/mixin-validator.js +197 -11
  24. package/dist/mod-analyzer.d.ts +1 -0
  25. package/dist/mod-analyzer.js +17 -1
  26. package/dist/mod-decompile-service.js +4 -4
  27. package/dist/mod-remap-service.js +1 -54
  28. package/dist/mod-search-service.d.ts +1 -0
  29. package/dist/mod-search-service.js +84 -51
  30. package/dist/response-utils.d.ts +35 -0
  31. package/dist/response-utils.js +113 -0
  32. package/dist/source-jar-reader.d.ts +16 -0
  33. package/dist/source-jar-reader.js +103 -1
  34. package/dist/source-resolver.js +9 -10
  35. package/dist/source-service.d.ts +22 -2
  36. package/dist/source-service.js +914 -105
  37. package/dist/tool-contract-manifest.js +8 -6
  38. package/dist/types.d.ts +17 -0
  39. package/dist/workspace-mapping-service.d.ts +13 -0
  40. package/dist/workspace-mapping-service.js +146 -14
  41. package/package.json +1 -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 { SourceMapping } from "./types.js";
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[]): AccessWidenerValidationResult;
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;
@@ -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;
@@ -735,7 +750,7 @@ export function validateParsedMixin(parsed, targetMembers, warnings, provenance,
735
750
  /* ------------------------------------------------------------------ */
736
751
  /* Access Widener validation */
737
752
  /* ------------------------------------------------------------------ */
738
- export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
753
+ export function validateParsedAccessWidener(parsed, membersByClass, warnings, options) {
739
754
  warnings.push(...parsed.parseWarnings);
740
755
  const validatedEntries = [];
741
756
  let validCount = 0;
@@ -743,15 +758,29 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
743
758
  for (const entry of parsed.entries) {
744
759
  const ownerFqn = entry.target.replace(/\//g, ".");
745
760
  if (entry.targetKind === "class") {
746
- if (membersByClass.has(ownerFqn)) {
747
- validatedEntries.push({ ...entry, valid: true });
761
+ const members = membersByClass.get(ownerFqn);
762
+ if (members) {
763
+ const runtimeAccess = accessLevelFromFlags(members.classAccessFlags);
764
+ validatedEntries.push({
765
+ ...entry,
766
+ valid: true,
767
+ ...(options?.includeRuntimeEvidence
768
+ ? {
769
+ resolvedInRuntime: true,
770
+ ...(runtimeAccess
771
+ ? { resolvedRuntimeAccess: runtimeAccess }
772
+ : {})
773
+ }
774
+ : {})
775
+ });
748
776
  validCount++;
749
777
  }
750
778
  else {
751
779
  validatedEntries.push({
752
780
  ...entry,
753
781
  valid: false,
754
- issue: `Class "${ownerFqn}" not found in game jar.`
782
+ issue: `Class "${ownerFqn}" not found in game jar.`,
783
+ ...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
755
784
  });
756
785
  invalidCount++;
757
786
  }
@@ -763,16 +792,37 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
763
792
  validatedEntries.push({
764
793
  ...entry,
765
794
  valid: false,
766
- issue: `Owner class "${ownerFqn}" not found in game jar.`
795
+ issue: `Owner class "${ownerFqn}" not found in game jar.`,
796
+ ...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
767
797
  });
768
798
  invalidCount++;
769
799
  continue;
770
800
  }
771
801
  if (entry.targetKind === "method") {
772
802
  const methodNames = allMethodNames(members);
773
- const found = members.methods.some((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor)) || members.constructors.some((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor));
803
+ 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));
804
+ const found = matchedMember != null;
774
805
  if (found) {
775
- validatedEntries.push({ ...entry, valid: true });
806
+ const runtimeMember = matchedMember;
807
+ const runtimeAccess = accessLevelFromFlags(runtimeMember.accessFlags);
808
+ validatedEntries.push({
809
+ ...entry,
810
+ valid: true,
811
+ ...(options?.includeRuntimeEvidence
812
+ ? {
813
+ resolvedInRuntime: true,
814
+ ...(runtimeAccess
815
+ ? { resolvedRuntimeAccess: runtimeAccess }
816
+ : {}),
817
+ ...(runtimeMember.jvmDescriptor
818
+ ? { resolvedRuntimeJvmDescriptor: runtimeMember.jvmDescriptor }
819
+ : {}),
820
+ ...(runtimeMember.javaSignature
821
+ ? { resolvedRuntimeJavaSignature: runtimeMember.javaSignature }
822
+ : {})
823
+ }
824
+ : {})
825
+ });
776
826
  validCount++;
777
827
  }
778
828
  else {
@@ -781,7 +831,8 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
781
831
  ...entry,
782
832
  valid: false,
783
833
  issue: `Method "${entry.name}" not found in class "${ownerFqn}".`,
784
- suggestions: suggestions.length > 0 ? suggestions : undefined
834
+ suggestions: suggestions.length > 0 ? suggestions : undefined,
835
+ ...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
785
836
  });
786
837
  invalidCount++;
787
838
  }
@@ -789,9 +840,29 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
789
840
  else {
790
841
  // field
791
842
  const fieldNames = allFieldNames(members);
792
- const found = members.fields.some((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor));
843
+ const matchedMember = members.fields.find((m) => m.name === entry.name && (!entry.descriptor || m.jvmDescriptor === entry.descriptor));
844
+ const found = matchedMember != null;
793
845
  if (found) {
794
- validatedEntries.push({ ...entry, valid: true });
846
+ const runtimeMember = matchedMember;
847
+ const runtimeAccess = accessLevelFromFlags(runtimeMember.accessFlags);
848
+ validatedEntries.push({
849
+ ...entry,
850
+ valid: true,
851
+ ...(options?.includeRuntimeEvidence
852
+ ? {
853
+ resolvedInRuntime: true,
854
+ ...(runtimeAccess
855
+ ? { resolvedRuntimeAccess: runtimeAccess }
856
+ : {}),
857
+ ...(runtimeMember.jvmDescriptor
858
+ ? { resolvedRuntimeJvmDescriptor: runtimeMember.jvmDescriptor }
859
+ : {}),
860
+ ...(runtimeMember.javaSignature
861
+ ? { resolvedRuntimeJavaSignature: runtimeMember.javaSignature }
862
+ : {})
863
+ }
864
+ : {})
865
+ });
795
866
  validCount++;
796
867
  }
797
868
  else {
@@ -800,7 +871,8 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
800
871
  ...entry,
801
872
  valid: false,
802
873
  issue: `Field "${entry.name}" not found in class "${ownerFqn}".`,
803
- suggestions: suggestions.length > 0 ? suggestions : undefined
874
+ suggestions: suggestions.length > 0 ? suggestions : undefined,
875
+ ...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
804
876
  });
805
877
  invalidCount++;
806
878
  }
@@ -819,4 +891,118 @@ export function validateParsedAccessWidener(parsed, membersByClass, warnings) {
819
891
  warnings
820
892
  };
821
893
  }
894
+ export function validateParsedAccessTransformer(parsed, membersByClass, warnings, options) {
895
+ warnings.push(...parsed.parseWarnings);
896
+ const validatedEntries = [];
897
+ let validCount = 0;
898
+ let invalidCount = 0;
899
+ for (const entry of parsed.entries) {
900
+ const ownerFqn = entry.owner;
901
+ const members = membersByClass.get(ownerFqn);
902
+ if (entry.targetKind === "class") {
903
+ if (!members) {
904
+ validatedEntries.push({
905
+ ...entry,
906
+ valid: false,
907
+ issue: `Class "${ownerFqn}" not found in runtime jar.`,
908
+ ...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
909
+ });
910
+ invalidCount++;
911
+ continue;
912
+ }
913
+ const runtimeAccess = accessLevelFromFlags(members.classAccessFlags);
914
+ validatedEntries.push({
915
+ ...entry,
916
+ valid: true,
917
+ ...(options?.includeRuntimeEvidence
918
+ ? {
919
+ resolvedInRuntime: true,
920
+ ...(runtimeAccess ? { resolvedRuntimeAccess: runtimeAccess } : {})
921
+ }
922
+ : {})
923
+ });
924
+ validCount++;
925
+ continue;
926
+ }
927
+ if (!members) {
928
+ validatedEntries.push({
929
+ ...entry,
930
+ valid: false,
931
+ issue: `Owner class "${ownerFqn}" not found in runtime jar.`,
932
+ ...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
933
+ });
934
+ invalidCount++;
935
+ continue;
936
+ }
937
+ if (entry.targetKind === "field") {
938
+ const fieldNames = allFieldNames(members);
939
+ const matchedField = members.fields.find((member) => member.name === entry.name);
940
+ if (!matchedField) {
941
+ const suggestions = entry.name ? suggestSimilar(entry.name, fieldNames) : [];
942
+ validatedEntries.push({
943
+ ...entry,
944
+ valid: false,
945
+ issue: `Field "${entry.name}" not found in class "${ownerFqn}".`,
946
+ ...(suggestions.length > 0 ? { suggestions } : {}),
947
+ ...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
948
+ });
949
+ invalidCount++;
950
+ continue;
951
+ }
952
+ const runtimeAccess = accessLevelFromFlags(matchedField.accessFlags);
953
+ validatedEntries.push({
954
+ ...entry,
955
+ valid: true,
956
+ ...(options?.includeRuntimeEvidence
957
+ ? {
958
+ resolvedInRuntime: true,
959
+ ...(runtimeAccess ? { resolvedRuntimeAccess: runtimeAccess } : {}),
960
+ ...(matchedField.jvmDescriptor ? { resolvedRuntimeJvmDescriptor: matchedField.jvmDescriptor } : {}),
961
+ ...(matchedField.javaSignature ? { resolvedRuntimeJavaSignature: matchedField.javaSignature } : {})
962
+ }
963
+ : {})
964
+ });
965
+ validCount++;
966
+ continue;
967
+ }
968
+ const methodNames = allMethodNames(members);
969
+ 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);
970
+ if (!matchedMethod) {
971
+ const suggestions = entry.name ? suggestSimilar(entry.name, methodNames) : [];
972
+ validatedEntries.push({
973
+ ...entry,
974
+ valid: false,
975
+ issue: `Method "${entry.name}" not found in class "${ownerFqn}".`,
976
+ ...(suggestions.length > 0 ? { suggestions } : {}),
977
+ ...(options?.includeRuntimeEvidence ? { resolvedInRuntime: false } : {})
978
+ });
979
+ invalidCount++;
980
+ continue;
981
+ }
982
+ const runtimeAccess = accessLevelFromFlags(matchedMethod.accessFlags);
983
+ validatedEntries.push({
984
+ ...entry,
985
+ valid: true,
986
+ ...(options?.includeRuntimeEvidence
987
+ ? {
988
+ resolvedInRuntime: true,
989
+ ...(runtimeAccess ? { resolvedRuntimeAccess: runtimeAccess } : {}),
990
+ ...(matchedMethod.jvmDescriptor ? { resolvedRuntimeJvmDescriptor: matchedMethod.jvmDescriptor } : {}),
991
+ ...(matchedMethod.javaSignature ? { resolvedRuntimeJavaSignature: matchedMethod.javaSignature } : {})
992
+ }
993
+ : {})
994
+ });
995
+ validCount++;
996
+ }
997
+ return {
998
+ valid: invalidCount === 0,
999
+ entries: validatedEntries,
1000
+ summary: {
1001
+ total: parsed.entries.length,
1002
+ valid: validCount,
1003
+ invalid: invalidCount
1004
+ },
1005
+ warnings
1006
+ };
1007
+ }
822
1008
  //# sourceMappingURL=mixin-validator.js.map
@@ -15,6 +15,7 @@ export interface ModAnalysisResult {
15
15
  entrypoints?: Record<string, string[]>;
16
16
  mixinConfigs?: string[];
17
17
  accessWidener?: string;
18
+ accessTransformers?: string[];
18
19
  dependencies?: ModDependency[];
19
20
  classCount: number;
20
21
  classes?: string[];
@@ -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 { readFileSync, writeFileSync } from "node:fs";
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 = readFileSync(join(outputDir, matched), "utf8");
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 = readFileSync(join(outputDir, matched), "utf8");
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
- writeFileSync(outPath, content, "utf8");
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 { listJarEntries, readJarEntryAsBuffer } from "./source-jar-reader.js";
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);
@@ -26,4 +26,5 @@ export declare class ModSearchService {
26
26
  constructor(modDecompileService: ModDecompileService);
27
27
  searchModSource(input: SearchModSourceInput): Promise<SearchModSourceOutput>;
28
28
  private searchSourceJarEntriesDirect;
29
+ private searchDecompiledClassFile;
29
30
  }