From 0613b1e2b23578dc8c22bd293b6b0c931852f291 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Fri, 24 Oct 2025 15:01:43 -0700 Subject: [PATCH 1/9] Update for unpick v3/4 --- .../src/main/kotlin/codebook.gradle.kts | 2 +- .../java/io/papermc/codebook/cli/Main.java | 47 +-------- gradle/libs.versions.toml | 4 +- .../java/io/papermc/codebook/CodeBook.java | 15 +-- .../codebook/config/CodeBookContext.java | 1 - .../codebook/pages/AsmProcessorPage.java | 73 ++++++++++++++ .../papermc/codebook/pages/CodeBookPage.java | 7 -- .../io/papermc/codebook/pages/FixJarPage.java | 44 +-------- .../papermc/codebook/pages/RemapLvtPage.java | 41 ++------ .../io/papermc/codebook/pages/UnpickPage.java | 99 ++++++++++++++----- 10 files changed, 164 insertions(+), 169 deletions(-) create mode 100644 src/main/java/io/papermc/codebook/pages/AsmProcessorPage.java diff --git a/build-logic/src/main/kotlin/codebook.gradle.kts b/build-logic/src/main/kotlin/codebook.gradle.kts index cb41e9b..ecae1af 100644 --- a/build-logic/src/main/kotlin/codebook.gradle.kts +++ b/build-logic/src/main/kotlin/codebook.gradle.kts @@ -34,7 +34,7 @@ repositories { java { toolchain { - languageVersion = JavaLanguageVersion.of(17) + languageVersion = JavaLanguageVersion.of(21) } withSourcesJar() } diff --git a/codebook-cli/src/main/java/io/papermc/codebook/cli/Main.java b/codebook-cli/src/main/java/io/papermc/codebook/cli/Main.java index c3e0530..a3d9b2d 100644 --- a/codebook-cli/src/main/java/io/papermc/codebook/cli/Main.java +++ b/codebook-cli/src/main/java/io/papermc/codebook/cli/Main.java @@ -175,10 +175,7 @@ static final class ParamMappingsOptions { private @Nullable UnpickOptions unpick; static final class UnpickOptions { - @CommandLine.ArgGroup( - heading = - "%n%nUnpick requires unpick definitions. When specifying unpick definitions, unpick constants are also required.%n", - multiplicity = "1") + @CommandLine.ArgGroup(heading = "%n%nUnpick requires unpick definitions.%n", multiplicity = "1") private @Nullable UnpickDefinitionsOptions unpickDefinitions; static final class UnpickDefinitionsOptions { @@ -200,32 +197,6 @@ static final class UnpickDefinitionsOptions { description = "A download URL for the unpick definitions to use for the unpick process.") private @Nullable URI unpickUri; } - - @CommandLine.ArgGroup( - heading = - "%n%nUnpick requires a constants jar. When specifying unpick constants, unpick definitions are also required.%n", - multiplicity = "1") - private @Nullable ConstantsJarOptions constantsJar; - - static final class ConstantsJarOptions { - @CommandLine.Option( - names = "--constants-coords", - paramLabel = "", - description = "The Maven coordinates for the constants jar to use for the unpick process.") - private @Nullable String constantsCoords; - - @CommandLine.Option( - names = {"--constants-file"}, - paramLabel = "", - description = "The constants jar to use for the unpick process.") - private @Nullable Path constantsFile; - - @CommandLine.Option( - names = "--constants-uri", - paramLabel = "", - description = "A download URL for the constants jar to use for the unpick process.") - private @Nullable URI constantsUri; - } } @CommandLine.Option( @@ -440,7 +411,7 @@ private CodeBookContext createContext() { p -> new Coords(p.paramsCoords, null, "zip", this.paramsMavenBaseUrl)); final @Nullable CodeBookResource unpickDefinitions = this.getResource( - "unpick_definitions.jar", + "definitions.unpick", this.unpick != null ? this.unpick.unpickDefinitions : null, d -> d.unpickFile, d -> d.unpickUri, @@ -452,19 +423,6 @@ private CodeBookContext createContext() { return new Coords(d.unpickCoords, "constants", null, this.unpickMavenBaseUrl); }); - final @Nullable CodeBookResource constantJar = this.getResource( - "unpick_constants.jar", - this.unpick != null ? this.unpick.constantsJar : null, - c -> c.constantsFile, - c -> c.constantsUri, - c -> { - if (this.unpickMavenBaseUrl == null) { - throw new UserErrorException( - "Cannot define unpick constants Maven coordinates without also setting --unpick-maven-base-url"); - } - return new Coords(c.constantsCoords, "constants", null, this.unpickMavenBaseUrl); - }); - @Nullable Reports reports = null; if (this.reports != null && this.reports.reportsDir != null) { final Set reportsToGenerate; @@ -488,7 +446,6 @@ private CodeBookContext createContext() { .mappings(mappings) .paramMappings(paramMappings) .unpickDefinitions(unpickDefinitions) - .constantsJar(constantJar) .outputJar(this.outputJar) .overwrite(this.forceWrite) .input(input) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f3f7a14..487f41f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ palantir = "2.50.0" indra = "4.0.0" slf4j = "2.0.7" lorenz = "0.5.7" -unpick = "2.3.0" +unpick = "3.0.0-beta.11" asm = "9.9" feather = "1.1.0" recordBuilder = "37" @@ -40,7 +40,7 @@ asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" } asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" } unpick-format = { module = "net.fabricmc.unpick:unpick-format-utils", version.ref = "unpick" } -unpick-cli = { module = "net.fabricmc.unpick:unpick-cli", version.ref = "unpick" } +unpick-cli = { module = "net.fabricmc.unpick:unpick", version.ref = "unpick" } hypo-platform = "dev.denwav.hypo:hypo-platform:2.3.0" hypo-model = { module = "dev.denwav.hypo:hypo-model" } diff --git a/src/main/java/io/papermc/codebook/CodeBook.java b/src/main/java/io/papermc/codebook/CodeBook.java index 873be47..dc82ce6 100644 --- a/src/main/java/io/papermc/codebook/CodeBook.java +++ b/src/main/java/io/papermc/codebook/CodeBook.java @@ -78,8 +78,8 @@ private void exec(final Path tempDir) { final var book = List.of( ExtractVanillaJarPage.class, RemapJarPage.class, - UnpickPage.class, InspectJarPage.class, + UnpickPage.class, FixJarPage.class, RemapLvtPage.class); @@ -149,13 +149,6 @@ private Module createInitialModule(final Path tempDir) { unpickDefinitions = null; } - final @Nullable Path constantsJar; - if (this.ctx.constantsJar() != null) { - constantsJar = this.ctx.constantsJar().resolveResourceFile(tempDir); - } else { - constantsJar = null; - } - return new AbstractModule() { @Override protected void configure() { @@ -177,12 +170,6 @@ protected void configure() { this.bind(CodeBookPage.UnpickDefinitions.KEY).toProvider(Providers.of(null)); } - if (constantsJar != null) { - this.bind(CodeBookPage.ConstantsJar.KEY).toInstance(constantsJar); - } else { - this.bind(CodeBookPage.ConstantsJar.KEY).toProvider(Providers.of(null)); - } - if (CodeBook.this.ctx.reports() != null) { this.bind(CodeBookPage.Report.KEY).toInstance(CodeBook.this.ctx.reports()); this.install(CodeBook.this.ctx.reports()); diff --git a/src/main/java/io/papermc/codebook/config/CodeBookContext.java b/src/main/java/io/papermc/codebook/config/CodeBookContext.java index 0ef96bb..cd2a2cf 100644 --- a/src/main/java/io/papermc/codebook/config/CodeBookContext.java +++ b/src/main/java/io/papermc/codebook/config/CodeBookContext.java @@ -39,7 +39,6 @@ public record CodeBookContext( @Nullable @org.jetbrains.annotations.Nullable CodeBookResource mappings, @Nullable @org.jetbrains.annotations.Nullable CodeBookResource paramMappings, @Nullable @org.jetbrains.annotations.Nullable CodeBookResource unpickDefinitions, - @Nullable @org.jetbrains.annotations.Nullable CodeBookResource constantsJar, @NotNull Path outputJar, boolean overwrite, @NotNull CodeBookInput input, diff --git a/src/main/java/io/papermc/codebook/pages/AsmProcessorPage.java b/src/main/java/io/papermc/codebook/pages/AsmProcessorPage.java new file mode 100644 index 0000000..acbfe39 --- /dev/null +++ b/src/main/java/io/papermc/codebook/pages/AsmProcessorPage.java @@ -0,0 +1,73 @@ +/* + * codebook is a remapper utility for the PaperMC project. + * + * Copyright (c) 2023 Kyle Wood (DenWav) + * Contributors + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 3 only, no later versions. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ + +package io.papermc.codebook.pages; + +import dev.denwav.hypo.asm.AsmClassData; +import dev.denwav.hypo.core.HypoContext; +import dev.denwav.hypo.model.HypoModelUtil; +import dev.denwav.hypo.model.data.ClassData; +import io.papermc.codebook.exceptions.UnexpectedException; +import java.io.IOException; +import java.util.ArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public abstract class AsmProcessorPage extends CodeBookPage { + + protected final HypoContext context; + + protected AsmProcessorPage(final HypoContext context) { + this.context = context; + } + + @Override + public void exec() { + this.processClasses(); + } + + protected void processClasses() { + final var tasks = new ArrayList>(); + for (final ClassData classData : this.context.getProvider().allClasses()) { + final var task = this.context.getExecutor().submit(() -> { + try { + this.processClass((AsmClassData) classData); + } catch (final Exception e) { + throw HypoModelUtil.rethrow(e); + } + }); + tasks.add(task); + } + + try { + for (final Future task : tasks) { + task.get(); + } + } catch (final ExecutionException e) { + throw new UnexpectedException("Failed to process classes", e.getCause()); + } catch (final InterruptedException e) { + throw new UnexpectedException("Class processing interrupted", e); + } + } + + protected abstract void processClass(final AsmClassData classData) throws IOException; +} diff --git a/src/main/java/io/papermc/codebook/pages/CodeBookPage.java b/src/main/java/io/papermc/codebook/pages/CodeBookPage.java index 07a7010..ffbe452 100644 --- a/src/main/java/io/papermc/codebook/pages/CodeBookPage.java +++ b/src/main/java/io/papermc/codebook/pages/CodeBookPage.java @@ -138,13 +138,6 @@ public void to(final @Nullable T value) { Key KEY = Key.get(Path.class, UnpickDefinitions.class); } - @Qualifier - @Target(ElementType.PARAMETER) - @Retention(RetentionPolicy.RUNTIME) - public @interface ConstantsJar { - Key KEY = Key.get(Path.class, ConstantsJar.class); - } - @Qualifier @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) diff --git a/src/main/java/io/papermc/codebook/pages/FixJarPage.java b/src/main/java/io/papermc/codebook/pages/FixJarPage.java index bd846b6..0c40b50 100644 --- a/src/main/java/io/papermc/codebook/pages/FixJarPage.java +++ b/src/main/java/io/papermc/codebook/pages/FixJarPage.java @@ -28,69 +28,31 @@ import dev.denwav.hypo.asm.AsmMethodData; import dev.denwav.hypo.core.HypoContext; import dev.denwav.hypo.hydrate.generic.HypoHydration; -import dev.denwav.hypo.model.HypoModelUtil; import dev.denwav.hypo.model.data.ClassData; import dev.denwav.hypo.model.data.ClassKind; import dev.denwav.hypo.model.data.FieldData; import dev.denwav.hypo.model.data.MethodData; import dev.denwav.hypo.model.data.Visibility; -import io.papermc.codebook.exceptions.UnexpectedException; import jakarta.inject.Inject; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import org.checkerframework.checker.nullness.qual.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; -public final class FixJarPage extends CodeBookPage { - - private final HypoContext context; +public final class FixJarPage extends AsmProcessorPage { @Inject public FixJarPage(@Hypo final HypoContext context) { - this.context = context; + super(context); } @Override - public void exec() { - try { - this.fixJar(); - } catch (final IOException e) { - throw new UnexpectedException("Failed to fix jar", e); - } - } - - private void fixJar() throws IOException { - final var tasks = new ArrayList>(); - for (final ClassData classData : this.context.getProvider().allClasses()) { - final var task = this.context.getExecutor().submit(() -> { - try { - this.processClass((AsmClassData) classData); - } catch (final IOException e) { - throw HypoModelUtil.rethrow(e); - } - }); - tasks.add(task); - } - - try { - for (final Future task : tasks) { - task.get(); - } - } catch (final ExecutionException e) { - throw new UnexpectedException("Failed to fix jar", e.getCause()); - } catch (final InterruptedException e) { - throw new UnexpectedException("Jar fixing interrupted", e); - } - } - - private void processClass(final AsmClassData classData) throws IOException { + protected void processClass(final AsmClassData classData) throws IOException { OverrideAnnotationAdder.addAnnotations(classData); EmptyRecordFixer.fixClass(classData); RecordFieldAccessFixer.fixClass(classData); diff --git a/src/main/java/io/papermc/codebook/pages/RemapLvtPage.java b/src/main/java/io/papermc/codebook/pages/RemapLvtPage.java index 1c25079..98105f0 100644 --- a/src/main/java/io/papermc/codebook/pages/RemapLvtPage.java +++ b/src/main/java/io/papermc/codebook/pages/RemapLvtPage.java @@ -24,31 +24,26 @@ import dev.denwav.hypo.asm.AsmClassData; import dev.denwav.hypo.core.HypoContext; -import dev.denwav.hypo.model.HypoModelUtil; -import dev.denwav.hypo.model.data.ClassData; import io.papermc.codebook.exceptions.UnexpectedException; import io.papermc.codebook.lvt.LvtNamer; import io.papermc.codebook.report.Reports; import jakarta.inject.Inject; import java.io.IOException; -import java.util.ArrayList; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import org.cadixdev.lorenz.MappingSet; import org.checkerframework.checker.nullness.qual.Nullable; -public final class RemapLvtPage extends CodeBookPage { +public final class RemapLvtPage extends AsmProcessorPage { - private final HypoContext context; private final @Nullable MappingSet paramMappings; private final Reports reports; + private @Nullable LvtNamer lvtNamer; @Inject public RemapLvtPage( @Hypo final HypoContext hypoContext, @ParamMappings @Nullable final MappingSet paramMappings, @Report final Reports reports) { - this.context = hypoContext; + super(hypoContext); this.paramMappings = paramMappings; this.reports = reports; } @@ -59,37 +54,19 @@ public void exec() { return; } - final LvtNamer lvtNamer; try { - lvtNamer = new LvtNamer(this.context, this.paramMappings, this.reports); + this.lvtNamer = new LvtNamer(this.context, this.paramMappings, this.reports); } catch (final IOException e) { throw new UnexpectedException("Failed to create LVT namer", e); } - this.remapLvt(lvtNamer); + this.processClasses(); } - private void remapLvt(final LvtNamer lvtNamer) { - final ArrayList> tasks = new ArrayList<>(); - for (final ClassData classData : this.context.getProvider().allClasses()) { - final var task = this.context.getExecutor().submit(() -> { - try { - lvtNamer.processClass((AsmClassData) classData); - } catch (final Exception e) { - throw HypoModelUtil.rethrow(e); - } - }); - tasks.add(task); - } - - try { - for (final Future task : tasks) { - task.get(); - } - } catch (final ExecutionException e) { - throw new UnexpectedException("Failed to remap LVT", e.getCause()); - } catch (final InterruptedException e) { - throw new UnexpectedException("LVT remap interrupted", e); + @Override + protected void processClass(final AsmClassData classData) throws IOException { + if (this.lvtNamer != null) { + this.lvtNamer.processClass(classData); } } } diff --git a/src/main/java/io/papermc/codebook/pages/UnpickPage.java b/src/main/java/io/papermc/codebook/pages/UnpickPage.java index 8dcad1f..9423310 100644 --- a/src/main/java/io/papermc/codebook/pages/UnpickPage.java +++ b/src/main/java/io/papermc/codebook/pages/UnpickPage.java @@ -22,58 +22,105 @@ package io.papermc.codebook.pages; -import io.papermc.codebook.exceptions.UnexpectedException; -import io.papermc.codebook.util.IOUtil; +import daomephsta.unpick.api.ConstantUninliner; +import daomephsta.unpick.api.classresolvers.ClassResolvers; +import daomephsta.unpick.api.classresolvers.IClassResolver; +import daomephsta.unpick.api.constantgroupers.ConstantGroupers; +import dev.denwav.hypo.asm.AsmClassData; +import dev.denwav.hypo.core.HypoContext; +import dev.denwav.hypo.model.data.ClassData; import jakarta.inject.Inject; +import java.io.BufferedReader; import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.zip.ZipFile; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; -public final class UnpickPage extends CodeBookPage { +public final class UnpickPage extends AsmProcessorPage { - private final Path inputJar; private final List classpath; - private final Path tempDir; private final @Nullable Path unpickDefinitions; - private final @Nullable Path constantsJar; + private @MonotonicNonNull ConstantUninliner uninliner; @Inject public UnpickPage( - @InputJar final Path inputJar, + @Hypo final HypoContext context, @ClasspathJars final List classpath, - @TempDir final Path tempDir, - @UnpickDefinitions final @Nullable Path unpickDefinitions, - @ConstantsJar final @Nullable Path constantsJar) { - this.inputJar = inputJar; + @UnpickDefinitions final @Nullable Path unpickDefinitions) { + super(context); this.classpath = classpath; - this.tempDir = tempDir; this.unpickDefinitions = unpickDefinitions; - this.constantsJar = constantsJar; } + @Override public void exec() { - if (this.unpickDefinitions == null || this.constantsJar == null) { + if (this.unpickDefinitions == null) { return; } - final Path outputJar = this.tempDir.resolve("unpicked.jar"); + IClassResolver classResolver = new IClassResolver() { + @Override + public @Nullable ClassReader resolveClass(final String internalName) { + try { + final @Nullable ClassData cls = + UnpickPage.this.context.getContextProvider().findClass(internalName); + if (cls instanceof final AsmClassData asmClassData) { + // TODO - do something smarter here to avoid re-serializing classes all the time + final ClassWriter classWriter = new ClassWriter(0); + asmClassData.getNode().accept(classWriter); + return new ClassReader(classWriter.toByteArray()); + } + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + return null; + } + }; - final var args = new ArrayList(); - args.addAll(List.of( - IOUtil.absolutePathString(this.inputJar), - IOUtil.absolutePathString(outputJar), - IOUtil.absolutePathString(this.unpickDefinitions), - IOUtil.absolutePathString(this.constantsJar))); - args.addAll(this.classpath.stream().map(IOUtil::absolutePathString).toList()); + final List zips = new ArrayList<>(); + try (final BufferedReader definitionsReader = Files.newBufferedReader(this.unpickDefinitions)) { + for (final Path classpathJar : this.classpath) { + final ZipFile zip = new ZipFile(classpathJar.toFile()); + zips.add(zip); + classResolver = classResolver.chain(ClassResolvers.jar(zip)); + } - try { - daomephsta.unpick.cli.Main.main(args.toArray(new String[0])); + classResolver = classResolver.chain(ClassResolvers.classpath()); + + this.uninliner = ConstantUninliner.builder() + .grouper(ConstantGroupers.dataDriven() + .classResolver(classResolver) + .mappingSource(definitionsReader) + .build()) + .classResolver(classResolver) + .build(); + + this.processClasses(); } catch (final IOException e) { - throw new UnexpectedException("Failed to run unpick", e); + throw new UncheckedIOException(e); + } finally { + for (final ZipFile zip : zips) { + try { + zip.close(); + } catch (final IOException e) { + // Ignore + } + } } + } - this.bind(InputJar.KEY).to(outputJar); + @Override + protected void processClass(final AsmClassData classData) { + if (this.uninliner == null) { + return; + } + this.uninliner.transform(classData.getNode()); } } From c5ef567bc710520fa7814f937fd7c506a9565c5f Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:30:58 -0700 Subject: [PATCH 2/9] Support unpick definitions in jars at extras/definitions.unpick Also reuse the jar if it's the same one as for param mappings --- .../java/io/papermc/codebook/CodeBook.java | 6 ++- .../io/papermc/codebook/pages/UnpickPage.java | 37 +++++++++++++++++-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/papermc/codebook/CodeBook.java b/src/main/java/io/papermc/codebook/CodeBook.java index dc82ce6..3722372 100644 --- a/src/main/java/io/papermc/codebook/CodeBook.java +++ b/src/main/java/io/papermc/codebook/CodeBook.java @@ -144,7 +144,11 @@ private Module createInitialModule(final Path tempDir) { final @Nullable Path unpickDefinitions; if (this.ctx.unpickDefinitions() != null) { - unpickDefinitions = this.ctx.unpickDefinitions().resolveResourceFile(tempDir); + if (this.ctx.unpickDefinitions().equals(this.ctx.paramMappings())) { + unpickDefinitions = paramMappingsFile; + } else { + unpickDefinitions = this.ctx.unpickDefinitions().resolveResourceFile(tempDir); + } } else { unpickDefinitions = null; } diff --git a/src/main/java/io/papermc/codebook/pages/UnpickPage.java b/src/main/java/io/papermc/codebook/pages/UnpickPage.java index 9423310..f577853 100644 --- a/src/main/java/io/papermc/codebook/pages/UnpickPage.java +++ b/src/main/java/io/papermc/codebook/pages/UnpickPage.java @@ -33,10 +33,13 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.zip.ZipException; import java.util.zip.ZipFile; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -65,7 +68,16 @@ public void exec() { return; } - IClassResolver classResolver = new IClassResolver() { + boolean isZip; + try (final ZipFile zf = new ZipFile(this.unpickDefinitions.toFile())) { + isZip = true; + } catch (final ZipException e) { + isZip = false; + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + + final IClassResolver classResolver = new IClassResolver() { @Override public @Nullable ClassReader resolveClass(final String internalName) { try { @@ -85,7 +97,26 @@ public void exec() { }; final List zips = new ArrayList<>(); - try (final BufferedReader definitionsReader = Files.newBufferedReader(this.unpickDefinitions)) { + + if (isZip) { + try (final FileSystem definitionsFs = FileSystems.newFileSystem(this.unpickDefinitions)) { + final Path definitionsPath = definitionsFs.getPath("extras/definitions.unpick"); + this.unpick(definitionsPath, zips, classResolver); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } else { + try { + this.unpick(this.unpickDefinitions, zips, classResolver); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + } + + private void unpick(final Path definitionsPath, final List zips, IClassResolver classResolver) + throws IOException { + try (final BufferedReader definitionsReader = Files.newBufferedReader(definitionsPath)) { for (final Path classpathJar : this.classpath) { final ZipFile zip = new ZipFile(classpathJar.toFile()); zips.add(zip); @@ -103,8 +134,6 @@ public void exec() { .build(); this.processClasses(); - } catch (final IOException e) { - throw new UncheckedIOException(e); } finally { for (final ZipFile zip : zips) { try { From f7a754dbf4db5adb82b37ed531a3706b93afc09c Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sat, 25 Oct 2025 10:33:42 -0700 Subject: [PATCH 3/9] Move class resolver --- .../io/papermc/codebook/pages/UnpickPage.java | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/main/java/io/papermc/codebook/pages/UnpickPage.java b/src/main/java/io/papermc/codebook/pages/UnpickPage.java index f577853..6e9b89b 100644 --- a/src/main/java/io/papermc/codebook/pages/UnpickPage.java +++ b/src/main/java/io/papermc/codebook/pages/UnpickPage.java @@ -77,7 +77,27 @@ public void exec() { throw new UncheckedIOException(e); } - final IClassResolver classResolver = new IClassResolver() { + final List zips = new ArrayList<>(); + + if (isZip) { + try (final FileSystem definitionsFs = FileSystems.newFileSystem(this.unpickDefinitions)) { + final Path definitionsPath = definitionsFs.getPath("extras/definitions.unpick"); + this.unpick(definitionsPath, zips, this.context); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } else { + try { + this.unpick(this.unpickDefinitions, zips, this.context); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + } + + private void unpick(final Path definitionsPath, final List zips, final HypoContext context) + throws IOException { + IClassResolver classResolver = new IClassResolver() { @Override public @Nullable ClassReader resolveClass(final String internalName) { try { @@ -96,26 +116,6 @@ public void exec() { } }; - final List zips = new ArrayList<>(); - - if (isZip) { - try (final FileSystem definitionsFs = FileSystems.newFileSystem(this.unpickDefinitions)) { - final Path definitionsPath = definitionsFs.getPath("extras/definitions.unpick"); - this.unpick(definitionsPath, zips, classResolver); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - } else { - try { - this.unpick(this.unpickDefinitions, zips, classResolver); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - } - } - - private void unpick(final Path definitionsPath, final List zips, IClassResolver classResolver) - throws IOException { try (final BufferedReader definitionsReader = Files.newBufferedReader(definitionsPath)) { for (final Path classpathJar : this.classpath) { final ZipFile zip = new ZipFile(classpathJar.toFile()); From 7ef036e919ae241c83d63b32fe1a9eecfdf1b123 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sat, 25 Oct 2025 12:00:39 -0700 Subject: [PATCH 4/9] Fix error from missing client classes --- .../io/papermc/codebook/pages/UnpickPage.java | 52 +++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/papermc/codebook/pages/UnpickPage.java b/src/main/java/io/papermc/codebook/pages/UnpickPage.java index 6e9b89b..9f30728 100644 --- a/src/main/java/io/papermc/codebook/pages/UnpickPage.java +++ b/src/main/java/io/papermc/codebook/pages/UnpickPage.java @@ -26,6 +26,11 @@ import daomephsta.unpick.api.classresolvers.ClassResolvers; import daomephsta.unpick.api.classresolvers.IClassResolver; import daomephsta.unpick.api.constantgroupers.ConstantGroupers; +import daomephsta.unpick.constantmappers.datadriven.parser.v3.UnpickV3Reader; +import daomephsta.unpick.constantmappers.datadriven.tree.ForwardingUnpickV3Visitor; +import daomephsta.unpick.constantmappers.datadriven.tree.GroupDefinition; +import daomephsta.unpick.constantmappers.datadriven.tree.expr.Expression; +import daomephsta.unpick.constantmappers.datadriven.tree.expr.FieldExpression; import dev.denwav.hypo.asm.AsmClassData; import dev.denwav.hypo.core.HypoContext; import dev.denwav.hypo.model.data.ClassData; @@ -82,21 +87,20 @@ public void exec() { if (isZip) { try (final FileSystem definitionsFs = FileSystems.newFileSystem(this.unpickDefinitions)) { final Path definitionsPath = definitionsFs.getPath("extras/definitions.unpick"); - this.unpick(definitionsPath, zips, this.context); + this.unpick(definitionsPath, zips); } catch (final IOException e) { throw new UncheckedIOException(e); } } else { try { - this.unpick(this.unpickDefinitions, zips, this.context); + this.unpick(this.unpickDefinitions, zips); } catch (final IOException e) { throw new UncheckedIOException(e); } } } - private void unpick(final Path definitionsPath, final List zips, final HypoContext context) - throws IOException { + private void unpick(final Path definitionsPath, final List zips) throws IOException { IClassResolver classResolver = new IClassResolver() { @Override public @Nullable ClassReader resolveClass(final String internalName) { @@ -128,7 +132,45 @@ private void unpick(final Path definitionsPath, final List zips, final this.uninliner = ConstantUninliner.builder() .grouper(ConstantGroupers.dataDriven() .classResolver(classResolver) - .mappingSource(definitionsReader) + .mappingSource(visitor -> { + try { + new UnpickV3Reader(definitionsReader) + .accept(new ForwardingUnpickV3Visitor(visitor) { + // Filter out any groups where all constants reference missing classes + // (client classes when applying to the server) + // This may need further refinement to handle applying outdated definitions leniently + @Override + public void visitGroupDefinition( + final GroupDefinition groupDefinition) { + final List constants = + new ArrayList<>(groupDefinition.constants()); + for (final Expression constant : groupDefinition.constants()) { + if (constant instanceof final FieldExpression field) { + try { + final @Nullable ClassData clsData = UnpickPage.this + .context + .getContextProvider() + .findClass(field.className); + if (clsData == null) { + constants.remove(constant); + } + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + } + if (!constants.isEmpty()) { + super.visitGroupDefinition( + GroupDefinition.Builder.from(groupDefinition) + .setConstants(constants) + .build()); + } + } + }); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + }) .build()) .classResolver(classResolver) .build(); From d1e6bd541d0813b9ca3650c187e60b2d6482f9e0 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sat, 25 Oct 2025 12:09:58 -0700 Subject: [PATCH 5/9] spotless --- src/main/java/io/papermc/codebook/pages/UnpickPage.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/papermc/codebook/pages/UnpickPage.java b/src/main/java/io/papermc/codebook/pages/UnpickPage.java index 9f30728..507af88 100644 --- a/src/main/java/io/papermc/codebook/pages/UnpickPage.java +++ b/src/main/java/io/papermc/codebook/pages/UnpickPage.java @@ -138,7 +138,8 @@ private void unpick(final Path definitionsPath, final List zips) throws .accept(new ForwardingUnpickV3Visitor(visitor) { // Filter out any groups where all constants reference missing classes // (client classes when applying to the server) - // This may need further refinement to handle applying outdated definitions leniently + // This may need further refinement to handle applying outdated + // definitions leniently @Override public void visitGroupDefinition( final GroupDefinition groupDefinition) { From 8cd7dbce94c5cdbb1356c4ffe7d2a37a6e423086 Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sat, 25 Oct 2025 13:40:55 -0700 Subject: [PATCH 6/9] Update unpick --- gradle/libs.versions.toml | 2 +- .../java/io/papermc/codebook/pages/UnpickPage.java | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 487f41f..1891bc4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ palantir = "2.50.0" indra = "4.0.0" slf4j = "2.0.7" lorenz = "0.5.7" -unpick = "3.0.0-beta.11" +unpick = "3.0.0-beta.12" asm = "9.9" feather = "1.1.0" recordBuilder = "37" diff --git a/src/main/java/io/papermc/codebook/pages/UnpickPage.java b/src/main/java/io/papermc/codebook/pages/UnpickPage.java index 507af88..9ec2927 100644 --- a/src/main/java/io/papermc/codebook/pages/UnpickPage.java +++ b/src/main/java/io/papermc/codebook/pages/UnpickPage.java @@ -48,8 +48,7 @@ import java.util.zip.ZipFile; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; public final class UnpickPage extends AsmProcessorPage { @@ -103,15 +102,12 @@ public void exec() { private void unpick(final Path definitionsPath, final List zips) throws IOException { IClassResolver classResolver = new IClassResolver() { @Override - public @Nullable ClassReader resolveClass(final String internalName) { + public @Nullable ClassNode resolveClass(final String internalName) { try { final @Nullable ClassData cls = UnpickPage.this.context.getContextProvider().findClass(internalName); if (cls instanceof final AsmClassData asmClassData) { - // TODO - do something smarter here to avoid re-serializing classes all the time - final ClassWriter classWriter = new ClassWriter(0); - asmClassData.getNode().accept(classWriter); - return new ClassReader(classWriter.toByteArray()); + return asmClassData.getNode(); } } catch (final IOException e) { throw new UncheckedIOException(e); From 824f8e5a40b494f35be82dd4bc100963df243a0b Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sat, 25 Oct 2025 14:10:59 -0700 Subject: [PATCH 7/9] Fix filtering --- .../io/papermc/codebook/pages/UnpickPage.java | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/papermc/codebook/pages/UnpickPage.java b/src/main/java/io/papermc/codebook/pages/UnpickPage.java index 9ec2927..a9b32b9 100644 --- a/src/main/java/io/papermc/codebook/pages/UnpickPage.java +++ b/src/main/java/io/papermc/codebook/pages/UnpickPage.java @@ -30,6 +30,7 @@ import daomephsta.unpick.constantmappers.datadriven.tree.ForwardingUnpickV3Visitor; import daomephsta.unpick.constantmappers.datadriven.tree.GroupDefinition; import daomephsta.unpick.constantmappers.datadriven.tree.expr.Expression; +import daomephsta.unpick.constantmappers.datadriven.tree.expr.ExpressionVisitor; import daomephsta.unpick.constantmappers.datadriven.tree.expr.FieldExpression; import dev.denwav.hypo.asm.AsmClassData; import dev.denwav.hypo.core.HypoContext; @@ -133,28 +134,30 @@ private void unpick(final Path definitionsPath, final List zips) throws new UnpickV3Reader(definitionsReader) .accept(new ForwardingUnpickV3Visitor(visitor) { // Filter out any groups where all constants reference missing classes - // (client classes when applying to the server) - // This may need further refinement to handle applying outdated - // definitions leniently + // (client classes when applying to the server or outdated definitions) @Override public void visitGroupDefinition( final GroupDefinition groupDefinition) { final List constants = new ArrayList<>(groupDefinition.constants()); for (final Expression constant : groupDefinition.constants()) { - if (constant instanceof final FieldExpression field) { - try { - final @Nullable ClassData clsData = UnpickPage.this - .context - .getContextProvider() - .findClass(field.className); - if (clsData == null) { - constants.remove(constant); + constant.accept(new ExpressionVisitor() { + @Override + public void visitFieldExpression( + final FieldExpression fieldExpression) { + try { + final @Nullable ClassData clsData = UnpickPage.this + .context + .getContextProvider() + .findClass(fieldExpression.className); + if (clsData == null) { + constants.remove(constant); + } + } catch (final IOException e) { + throw new UncheckedIOException(e); } - } catch (final IOException e) { - throw new UncheckedIOException(e); } - } + }); } if (!constants.isEmpty()) { super.visitGroupDefinition( From a6ee16cc1ef45567529246e4ba1840f221660cbd Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sun, 26 Oct 2025 09:23:49 -0700 Subject: [PATCH 8/9] Update unpick --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1891bc4..cea4527 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,7 +4,7 @@ palantir = "2.50.0" indra = "4.0.0" slf4j = "2.0.7" lorenz = "0.5.7" -unpick = "3.0.0-beta.12" +unpick = "3.0.0-beta.13" asm = "9.9" feather = "1.1.0" recordBuilder = "37" From d867f78ea783c8d0f9ed28c75ba57c610c78ce8c Mon Sep 17 00:00:00 2001 From: Jason Penilla <11360596+jpenilla@users.noreply.github.com> Date: Sun, 26 Oct 2025 10:12:48 -0700 Subject: [PATCH 9/9] Rename version catalog entry --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 71c323f..15ef1ec 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,7 +21,7 @@ dependencies { implementation(libs.bundles.asm) implementation(libs.unpick.format) - implementation(libs.unpick.cli) + implementation(libs.unpick) implementation(platform(libs.hypo.platform)) implementation(libs.bundles.hypo.full) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cea4527..afba3ce 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,7 +40,7 @@ asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" } asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" } unpick-format = { module = "net.fabricmc.unpick:unpick-format-utils", version.ref = "unpick" } -unpick-cli = { module = "net.fabricmc.unpick:unpick", version.ref = "unpick" } +unpick = { module = "net.fabricmc.unpick:unpick", version.ref = "unpick" } hypo-platform = "dev.denwav.hypo:hypo-platform:2.3.0" hypo-model = { module = "dev.denwav.hypo:hypo-model" }