Skip to content

Commit 2bfe741

Browse files
feat(v26.8-Alpha.5): DEC-PRE261 Option A foundation - Mojang official mappings cross-resolution
- OfficialMappings: parses Mojang client.txt ProGuard format (class lines; members/comments skipped) into an official->runtime reverse index. - GameContentBindingInstaller: setOfficialMappings() + rt() translation - binder class targets (BuiltInRegistries, Registry, Identifier, Item, Block) resolve through the chain on REMAPPED profiles when mappings are supplied. Without mappings the PROFILE_UNSUPPORTED gate stands. - Scope note: class-level chain proven; member-level (field/method) translation is the next increment - client.txt member lines need per-class keyed parsing. - 4 unit tests (parse/skip rules, pass-through, absent-file, remap gate). - Version bumped to v26.8-Alpha.5.
1 parent f03474f commit 2bfe741

4 files changed

Lines changed: 189 additions & 6 deletions

File tree

aprism-loader-core/src/main/java/com/aprism/loader/contentbind/GameContentBindingInstaller.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public final class GameContentBindingInstaller {
4646

4747
private final GameRegistries gameRegistries;
4848
private boolean remapProfile;
49+
private OfficialMappings officialMappings;
4950

5051
/**
5152
* Outcome of one bind attempt.
@@ -73,6 +74,22 @@ public void setRemapProfile(boolean remapProfile) {
7374
this.remapProfile = remapProfile;
7475
}
7576

77+
/**
78+
* Supplies Mojang official mappings (client.txt) enabling cross-mapped
79+
* binding on REMAPPED profiles (DEC-PRE261 Option A, v26.8-Alpha.5).
80+
*/
81+
public void setOfficialMappings(OfficialMappings mappings) {
82+
this.officialMappings = mappings;
83+
}
84+
85+
/** Translates an official target name to the runtime name when needed. */
86+
private String rt(String officialName) {
87+
if (remapProfile && officialMappings != null) {
88+
return officialMappings.runtimeName(officialName);
89+
}
90+
return officialName;
91+
}
92+
7693
/**
7794
* Binds every registered item and block into the real registries.
7895
* Never throws; failures are isolated per entry.
@@ -210,16 +227,16 @@ private RegistryHandles resolveHandles() {
210227
ClassLoader loader = Thread.currentThread().getContextClassLoader() != null
211228
? Thread.currentThread().getContextClassLoader()
212229
: getClass().getClassLoader();
213-
Class<?> registries = loader.loadClass(BUILT_IN_REGISTRIES);
230+
Class<?> registries = loader.loadClass(rt(BUILT_IN_REGISTRIES));
214231
Class<?> registryHelper = loader.loadClass(REGISTRY_HELPER);
215-
Class<?> registryIface = loader.loadClass("net.minecraft.core.Registry");
216-
Class<?> identifier = loader.loadClass(IDENTIFIER);
232+
Class<?> registryIface = loader.loadClass(rt("net.minecraft.core.Registry"));
233+
Class<?> identifier = loader.loadClass(rt(IDENTIFIER));
217234
Field itemField = registries.getField("ITEM");
218235
Field blockField = registries.getField("BLOCK");
219236
Object itemRegistry = itemField.get(null);
220237
Object blockRegistry = blockField.get(null);
221-
Class<?> itemClass = loader.loadClass(MC_ITEM);
222-
Class<?> blockClass = loader.loadClass(MC_BLOCK);
238+
Class<?> itemClass = loader.loadClass(rt(MC_ITEM));
239+
Class<?> blockClass = loader.loadClass(rt(MC_BLOCK));
223240
return new RegistryHandles(registryIface, registryHelper, itemRegistry,
224241
blockRegistry, itemClass, blockClass,
225242
IdentifierFactory.detect(identifier));
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.aprism.loader.contentbind;
2+
3+
import java.io.BufferedReader;
4+
import java.io.IOException;
5+
import java.nio.charset.StandardCharsets;
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
import java.util.logging.Logger;
11+
// GitHub@NDBlockConnect | BlockConnect@StarsailsClover
12+
13+
/**
14+
* Loads Mojang's official {@code client.txt} ProGuard mapping and resolves
15+
* official Mojang class names to their runtime (obfuscated) names
16+
* (v26.8-Alpha.5, DEC-PRE261 Option A foundation).
17+
*
18+
* <p>On the REMAPPED profile the runtime classes carry obfuscated names.
19+
* Binding targets written against official names must therefore be
20+
* translated: {@code official --[client.txt reverse]--> obfuscated
21+
* (runtime)}. Member mappings are skipped; classes suffice for binder
22+
* targets.
23+
*
24+
* <p>File format (ProGuard): class lines are
25+
* {@code obf.qual.Name -> official.qual.Name:}; member lines are indented.
26+
*
27+
* @author BlockConnect@StarsailsClover
28+
*/
29+
public final class OfficialMappings {
30+
// GitHub@NDBlockConnect | BlockConnect@StarsailsClover
31+
32+
private static final Logger LOG = Logger.getLogger("aprism.contentbind");
33+
34+
private final Map<String, String> officialToRuntime;
35+
36+
private OfficialMappings(Map<String, String> officialToRuntime) {
37+
this.officialToRuntime = officialToRuntime;
38+
}
39+
40+
/**
41+
* Parses a Mojang {@code client.txt} mapping file.
42+
*
43+
* @param clientTxt path to the mapping file
44+
* @return the loaded mapping, or null when the file is absent
45+
* @throws IOException when the file exists but cannot be read
46+
*/
47+
public static OfficialMappings load(Path clientTxt) throws IOException {
48+
if (clientTxt == null || !Files.isRegularFile(clientTxt)) {
49+
return null;
50+
}
51+
Map<String, String> map = new HashMap<>(30_000);
52+
try (BufferedReader br = Files.newBufferedReader(clientTxt,
53+
StandardCharsets.UTF_8)) {
54+
String line;
55+
while ((line = br.readLine()) != null) {
56+
if (line.isEmpty() || line.startsWith(" ") || line.startsWith("#")) {
57+
continue; // members/comments skipped; classes only
58+
}
59+
int arrow = line.indexOf(" -> ");
60+
if (arrow < 0) {
61+
continue;
62+
}
63+
String officialPart = line.substring(arrow + 4);
64+
if (officialPart.endsWith(":")) {
65+
officialPart = officialPart.substring(0,
66+
officialPart.length() - 1);
67+
}
68+
// Keep the LAST occurrence (inner-class lines may repeat).
69+
map.put(officialPart, line.substring(0, arrow));
70+
}
71+
}
72+
int size = map.size();
73+
LOG.info("OfficialMappings loaded: " + size + " class entries");
74+
return new OfficialMappings(map);
75+
}
76+
77+
/**
78+
* Resolves an official Mojang class name to its runtime name.
79+
*
80+
* @param officialName e.g. {@code net.minecraft.core.registries.BuiltInRegistries}
81+
* @return the runtime (obfuscated) name, or the input unchanged when the
82+
* name is not in the mapping (e.g. already-runtime or library)
83+
*/
84+
public String runtimeName(String officialName) {
85+
return officialToRuntime.getOrDefault(officialName, officialName);
86+
}
87+
88+
/**
89+
* @return the number of mapped classes
90+
*/
91+
public int size() {
92+
return officialToRuntime.size();
93+
}
94+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.aprism.loader.contentbind;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
8+
import org.junit.jupiter.api.Test;
9+
import org.junit.jupiter.api.io.TempDir;
10+
// GitHub@NDBlockConnect | BlockConnect@StarsailsClover
11+
12+
/**
13+
* Tests for {@link OfficialMappings}: ProGuard client.txt parsing and
14+
* official-to-runtime class-name resolution (DEC-PRE261 Option A
15+
* foundation).
16+
*/
17+
class OfficialMappingsTest {
18+
19+
@TempDir
20+
Path tempDir;
21+
22+
private static final String CLIENT_TXT = """
23+
# comment line should be skipped
24+
abc.def -> net.minecraft.core.registries.BuiltInRegistries:
25+
field a -> ITEM
26+
ghi -> net.minecraft.world.item.Item:
27+
method a(net.minecraft.world.item.ItemStack) -> method_1
28+
skip.Me -> skip.Me:
29+
""";
30+
31+
@Test
32+
void loadsClassEntriesAndSkipsMembersAndComments() throws Exception {
33+
Path f = tempDir.resolve("client.txt");
34+
Files.writeString(f, CLIENT_TXT);
35+
OfficialMappings m = OfficialMappings.load(f);
36+
37+
assertNotNull(m);
38+
assertTrue(m.size() >= 3);
39+
assertEquals("abc.def",
40+
m.runtimeName("net.minecraft.core.registries.BuiltInRegistries"));
41+
assertEquals("ghi", m.runtimeName("net.minecraft.world.item.Item"));
42+
}
43+
44+
@Test
45+
void unknownNamesPassThroughUnchanged() throws Exception {
46+
Path f = tempDir.resolve("client.txt");
47+
Files.writeString(f, CLIENT_TXT);
48+
OfficialMappings m = OfficialMappings.load(f);
49+
50+
assertEquals("com.mojang.brigadier.CommandDispatcher",
51+
m.runtimeName("com.mojang.brigadier.CommandDispatcher"));
52+
}
53+
54+
@Test
55+
void absentFileLoadsAsNull() throws Exception {
56+
assertNull(OfficialMappings.load(tempDir.resolve("nope.txt")));
57+
assertNull(OfficialMappings.load(null));
58+
}
59+
60+
@Test
61+
void remapGateStillRefusesWithoutMappings() {
62+
// Without official mappings, REMAPPED profiles keep refusing.
63+
var reg = new com.aprism.loader.registry.GameRegistries();
64+
var k = com.aprism.api.registry.ResourceKey.parse("aprism:x");
65+
reg.items().register(k,
66+
new com.aprism.api.registry.ItemContent(k, 4));
67+
GameContentBindingInstaller installer = new GameContentBindingInstaller(reg);
68+
installer.setRemapProfile(true);
69+
var results = installer.bindAll();
70+
assertEquals("PROFILE_UNSUPPORTED", results.get(0).refusal());
71+
}
72+
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Author: BlockConnect@StarsailsClover
33

44
# Aprism version (public): v<Year>.<minor>[-Alpha.<n>]
5-
aprismVersion = v26.8-Alpha.4
5+
aprismVersion = v26.8-Alpha.5
66
# Internal phase tracker (not shown in public version strings)
77
aprismPhase = Phase0
88
aprismGroup = com.aprism

0 commit comments

Comments
 (0)