Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Enforce LF line endings for text files on every platform. Without this, Windows
# checkouts convert to CRLF (via core.autocrlf), which the Biome formatter flags
# as errors even though the committed blobs are LF.
* text=auto eol=lf

# Binary assets — never line-ending normalize.
*.jar binary
*.png binary
*.jpg binary
*.gif binary
*.ico binary
*.gz binary
*.zip binary
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- '__tests__/**'
- '*.ts'
- '*.js'
- 'biome.json'
- 'package.json'
- 'package-lock.json'
- 'tsconfig.json'
Expand All @@ -20,6 +21,7 @@ on:
- '__tests__/**'
- '*.ts'
- '*.js'
- 'biome.json'
- 'package.json'
- 'package-lock.json'
- 'tsconfig.json'
Expand Down Expand Up @@ -75,6 +77,9 @@ jobs:
- name: Build
run: npm run build

- name: Run linter
run: npm run lint

# Isolate the heavy decompile pipeline into its own step so it is not racing
# the vitest test timeout. Idempotent: a fast no-op when the cache above was
# restored (hasDecompiledSource -> true); runs the full pipeline only on a
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ Deterministic central-directory scan: any `.class` entry → decompile via VineF
8. **`compare_versions`** - Compare classes/registries between versions
9. **`analyze_mixin`** - Analyze and validate Mixin code
10. **`validate_access_widener`** - Validate access widener files
10a. **`validate_access_transformer`** - Validate Forge/NeoForge access transformer files (parses `.cfg`, checks signatures, detects record canonical-constructor crashes, inner-class accessibility, and conflicting modifiers; mojmap-only)
10a. **`validate_access_transformer`** - Validate Forge/NeoForge access transformer files (parses `.cfg`, checks signatures, flags record canonical-constructor widening, inner-class accessibility, and conflicting modifiers; mojmap-only). Validates against the **remapped JAR's bytecode** (via the bundled ASM `bytecode-dumper`), not decompiled `.java` — decompiled source omits compiler-generated record members (canonical constructors, component accessors), so a source-based check false-flags them as missing (issue #12). Bytecode carries every member with its true access flags and erased descriptors (the same facts `javap` shows). Only needs the remapped JAR, which `decompile_minecraft_version` produces; per-class results are cached in a `remapped/{version}-{mapping}.bytecode.json` sidecar keyed by JAR signature. **Patched keys work too**: the patched flow does no remapping, so `decompileLocalJar` copies a *compiled* input JAR to `remapped/{version}-{mapping}.jar` — it already is that key's remapped JAR by definition — and every bytecode consumer then resolves a patched key exactly like a vanilla one. Patched *sources* JARs (NFRT/ForgeGradle `-sources.jar`) contain no bytecode and cannot be validated; the tool says so rather than telling you to decompile again.
11. **`compare_versions_detailed`** - AST-level version comparison
12. **`index_minecraft_version`** - Create full-text search index
13. **`search_indexed`** - Fast FTS5 search on indexed versions
Expand Down
25 changes: 10 additions & 15 deletions __tests__/core/mapping-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,14 +355,14 @@ describe('Mojmap Tiny v2 Structure Verification', () => {

// Find Entity class line
const entityLineIdx = lines.findIndex(
(l) => l.startsWith('c\t') && l.includes('Entity') && !l.includes('EntityType')
(l) => l.startsWith('c\t') && l.includes('Entity') && !l.includes('EntityType'),
);
expect(entityLineIdx).toBeGreaterThan(0);

// The line immediately after a class definition should be a field or method
// (starts with a tab character), NOT another class definition
const nextLine = lines[entityLineIdx + 1];
if (nextLine && nextLine.trim()) {
if (nextLine?.trim()) {
// If there's content, it should be a nested member (starts with tab)
// or another class (no tab). We're checking that fields/methods
// ARE nested under classes, not grouped separately.
Expand Down Expand Up @@ -414,7 +414,7 @@ describe('Mojmap Tiny v2 Structure Verification', () => {
TEST_VERSION,
'tick',
'mojmap',
'intermediary'
'intermediary',
);

expect(result.found).toBe(true);
Expand Down Expand Up @@ -455,28 +455,23 @@ describe('Unobfuscated version handling', () => {

it('should throw actionable error for getMappings(yarn) on unobfuscated version', async () => {
const mappingService = getMappingService();
await expect(
mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'yarn'),
).rejects.toThrow(/unobfuscated.*mojmap/is);
await expect(mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'yarn')).rejects.toThrow(
/unobfuscated.*mojmap/is,
);
}, 30000);

it('should throw actionable error for getMappings(mojmap) on unobfuscated version', async () => {
const mappingService = getMappingService();
await expect(
mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'mojmap'),
).rejects.toThrow(/unobfuscated.*already in Mojang/is);
await expect(mappingService.getMappings(UNOBFUSCATED_TEST_VERSION, 'mojmap')).rejects.toThrow(
/unobfuscated.*already in Mojang/is,
);
}, 30000);

it('should throw actionable error for lookupMapping on unobfuscated version', async () => {
const mappingService = getMappingService();
// lookupMapping calls getMappings internally, which throws for unobfuscated versions
await expect(
mappingService.lookupMapping(
UNOBFUSCATED_TEST_VERSION,
'Entity',
'mojmap',
'yarn',
),
mappingService.lookupMapping(UNOBFUSCATED_TEST_VERSION, 'Entity', 'mojmap', 'yarn'),
).rejects.toThrow(/unobfuscated/i);
}, 30000);

Expand Down
18 changes: 9 additions & 9 deletions __tests__/core/version-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,14 @@ describe('Version Management', () => {
}, 30000);

// Regression tests for https://github.com/MCDxAI/minecraft-dev-mcp/issues/5
it.each([
'26.1-snapshot-10',
'26.1-snapshot-11',
'26.1-rc-3',
])('should return true for %s (issue #5)', async (version) => {
const versionManager = getVersionManager();
const result = await versionManager.isVersionUnobfuscated(version);
expect(result).toBe(true);
}, 30000);
it.each(['26.1-snapshot-10', '26.1-snapshot-11', '26.1-rc-3'])(
'should return true for %s (issue #5)',
async (version) => {
const versionManager = getVersionManager();
const result = await versionManager.isVersionUnobfuscated(version);
expect(result).toBe(true);
},
30000,
);
});
});
14 changes: 14 additions & 0 deletions __tests__/fixtures/at-fixture-src/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
# Rebuild the Access Transformer fixture JAR from the stub sources.
# The compiled JAR (../summoningrituals-mc-stubs.jar) is committed so tests do
# not need a JDK at runtime; re-run this only when the stub sources change.
# Requires JDK 17+ (records). Usage: bash build.sh
set -euo pipefail
here="$(cd "$(dirname "$0")" && pwd)"
out="$here/../summoningrituals-mc-stubs.jar"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mapfile -t srcs < <(find "$here" -name '*.java')
javac -d "$tmp" "${srcs[@]}"
( cd "$tmp" && jar --create --file "$out" . )
echo "Wrote $out"
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.mojang.serialization;
// Minimal stub so fixture descriptors resolve to com/mojang/serialization/Codec.
public interface Codec<T> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.minecraft.advancements.critereon;
// Public enclosing class so the inner-class accessibility check passes.
public class LocationPredicate {
// Package-private nested record: implicit canonical constructor + accessors
// exist in bytecode but never appear in decompiled source. Reproduces
// net.minecraft.advancements.critereon.LocationPredicate$PositionPredicate.
record PositionPredicate(MinMaxBounds.Doubles x, MinMaxBounds.Doubles y, MinMaxBounds.Doubles z) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package net.minecraft.advancements.critereon;
public class MinMaxBounds {
// Nested type targeted only as a parameter type in descriptors.
public static final class Doubles {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.minecraft.advancements.critereon;
import java.util.Optional;
// Public enclosing class; nested records are package-private (as in vanilla).
public class StatePropertiesPredicate {
public interface ValueMatcher {}
record ExactMatcher(String value) implements ValueMatcher {}
record PropertyMatcher(String name, ValueMatcher valueMatcher) {}
record RangedMatcher(Optional<String> minValue, Optional<String> maxValue) implements ValueMatcher {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.minecraft.client.gui.screens.inventory.tooltip;
import net.minecraft.util.FormattedCharSequence;
public class ClientTextTooltip {
private final FormattedCharSequence text;
public ClientTextTooltip(FormattedCharSequence text) { this.text = text; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package net.minecraft.util;
public interface FormattedCharSequence {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.minecraft.world.entity.boss.wither;
public class WitherBoss {
private final float[] xRotHeads = new float[3];
private final float[] yRotHeads = new float[3];
private final float[] xRotOHeads = new float[3];
private final float[] yRotOHeads = new float[3];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package net.minecraft.world.level.storage.loot;
import net.minecraft.world.level.storage.loot.providers.number.NumberProvider;
// Plain class (NOT a record). Private constructor + private fields (matches
// real 1.21.1: min/max are private, the (NumberProvider, NumberProvider) ctor is private).
public class IntRange {
private final NumberProvider min;
private final NumberProvider max;
private IntRange(NumberProvider min, NumberProvider max) { this.min = min; this.max = max; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package net.minecraft.world.level.storage.loot.predicates;
import java.util.List;
public class AnyOfCondition extends CompositeLootItemCondition {
// Package-private constructor (as in vanilla). A constructor is NEVER
// overridable, so widening it must not produce an "overridable" warning.
AnyOfCondition(List<LootItemCondition> terms) { super(terms); }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package net.minecraft.world.level.storage.loot.predicates;
import com.mojang.serialization.Codec;
import java.util.List;
import java.util.function.Function;
public abstract class CompositeLootItemCondition implements LootItemCondition {
protected final List<LootItemCondition> terms;
protected CompositeLootItemCondition(List<LootItemCondition> terms) { this.terms = terms; }
protected static <T extends CompositeLootItemCondition> Codec<T> createInlineCodec(
Function<List<LootItemCondition>, T> factory) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package net.minecraft.world.level.storage.loot.predicates;
public interface LootItemCondition {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
package net.minecraft.world.level.storage.loot.providers.number;
public interface NumberProvider {}
Binary file added __tests__/fixtures/summoningrituals-mc-stubs.jar
Binary file not shown.
19 changes: 19 additions & 0 deletions __tests__/fixtures/summoningrituals.accesstransformer.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
public net.minecraft.advancements.critereon.LocationPredicate$PositionPredicate
public net.minecraft.advancements.critereon.LocationPredicate$PositionPredicate <init>(Lnet/minecraft/advancements/critereon/MinMaxBounds$Doubles;Lnet/minecraft/advancements/critereon/MinMaxBounds$Doubles;Lnet/minecraft/advancements/critereon/MinMaxBounds$Doubles;)V
public net.minecraft.advancements.critereon.StatePropertiesPredicate$ExactMatcher
public net.minecraft.advancements.critereon.StatePropertiesPredicate$ExactMatcher value()Ljava/lang/String;
public net.minecraft.advancements.critereon.StatePropertiesPredicate$PropertyMatcher
public net.minecraft.advancements.critereon.StatePropertiesPredicate$PropertyMatcher name()Ljava/lang/String;
public net.minecraft.advancements.critereon.StatePropertiesPredicate$PropertyMatcher valueMatcher()Lnet/minecraft/advancements/critereon/StatePropertiesPredicate$ValueMatcher;
public net.minecraft.advancements.critereon.StatePropertiesPredicate$RangedMatcher
public net.minecraft.advancements.critereon.StatePropertiesPredicate$RangedMatcher maxValue()Ljava/util/Optional;
public net.minecraft.advancements.critereon.StatePropertiesPredicate$RangedMatcher minValue()Ljava/util/Optional;
public net.minecraft.client.gui.screens.inventory.tooltip.ClientTextTooltip text
public net.minecraft.world.entity.boss.wither.WitherBoss yRotHeads
public net.minecraft.world.entity.boss.wither.WitherBoss yRotOHeads
public net.minecraft.world.level.storage.loot.IntRange <init>(Lnet/minecraft/world/level/storage/loot/providers/number/NumberProvider;Lnet/minecraft/world/level/storage/loot/providers/number/NumberProvider;)V
public net.minecraft.world.level.storage.loot.IntRange max
public net.minecraft.world.level.storage.loot.IntRange min
public net.minecraft.world.level.storage.loot.predicates.AnyOfCondition <init>(Ljava/util/List;)V
public net.minecraft.world.level.storage.loot.predicates.CompositeLootItemCondition createInlineCodec(Ljava/util/function/Function;)Lcom/mojang/serialization/Codec;
public net.minecraft.world.level.storage.loot.predicates.CompositeLootItemCondition terms
Loading
Loading