├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.MD ├── build-logic ├── build.gradle.kts ├── settings.gradle.kts └── src │ └── main │ └── kotlin │ ├── extensions.kt │ ├── ignite.base-conventions.gradle.kts │ ├── ignite.launcher-conventions.gradle.kts │ ├── ignite.parent-conventions.gradle.kts │ └── ignite.publish-conventions.gradle.kts ├── build.gradle.kts ├── console ├── build.gradle.kts └── src │ └── main │ ├── java │ └── net │ │ └── minecrell │ │ └── terminalconsole │ │ ├── HighlightErrorConverter.java │ │ ├── MinecraftFormattingConverter.java │ │ ├── SimpleTerminalConsole.java │ │ ├── TCALookup.java │ │ ├── TerminalConsoleAppender.java │ │ └── util │ │ └── LoggerNamePatternSelector.java │ └── resources │ └── META-INF │ ├── MANIFEST.MF │ └── org │ └── apache │ └── logging │ └── log4j │ └── core │ └── config │ └── plugins │ └── Log4j2Plugins.dat ├── example ├── build.gradle.kts └── src │ └── main │ ├── java │ └── io │ │ └── github │ │ └── dueris │ │ └── example │ │ ├── EclipseBootstrapEntrypoint.java │ │ ├── EclipseExample.java │ │ ├── EclipseModEntrypoint.java │ │ ├── mixin │ │ └── MinecraftServerMixin.java │ │ └── tests │ │ ├── ColoredLoggingOutputTest.java │ │ ├── ForgeAccessTest.java │ │ ├── JavaPluginJarFileTest.java │ │ ├── PluginManagerGetPlugin.java │ │ ├── StaticGetPluginTest.java │ │ ├── TestFailedException.java │ │ └── TestInstance.java │ └── resources │ ├── example.accesswidener │ ├── example.mixins.json │ ├── example_at.cfg │ └── paper-plugin.yml ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── injection ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── java │ └── com │ │ └── dragoncommissions │ │ └── mixbukkit │ │ ├── MixBukkit.java │ │ ├── MixinPluginInstance.java │ │ ├── addons │ │ └── AutoMapper.java │ │ ├── agent │ │ ├── AgentMain.java │ │ ├── ClassesManager.java │ │ └── JVMAttacher.java │ │ ├── api │ │ ├── MixinPlugin.java │ │ ├── ObfMap.java │ │ ├── action │ │ │ ├── MixinAction.java │ │ │ └── impl │ │ │ │ ├── MActionCallSuper.java │ │ │ │ ├── MActionInsertShellCode.java │ │ │ │ ├── MActionMethodCallSpoofer.java │ │ │ │ ├── MActionMethodReplacer.java │ │ │ │ └── MActionPipeline.java │ │ ├── locator │ │ │ ├── HookLocator.java │ │ │ └── impl │ │ │ │ ├── HLocatorFieldAccess.java │ │ │ │ ├── HLocatorFieldRead.java │ │ │ │ ├── HLocatorFieldWrite.java │ │ │ │ ├── HLocatorHead.java │ │ │ │ ├── HLocatorMethodInvoke.java │ │ │ │ └── HLocatorReturn.java │ │ └── shellcode │ │ │ ├── IShellCode.java │ │ │ ├── LocalVarManager.java │ │ │ ├── ShellCode.java │ │ │ ├── ShellCodeInfo.java │ │ │ └── impl │ │ │ ├── api │ │ │ ├── CallbackInfo.java │ │ │ ├── ShellCodeComment.java │ │ │ ├── ShellCodePrintMessage.java │ │ │ ├── ShellCodePrintTopStackType.java │ │ │ └── ShellCodeReflectionMixinPluginMethodCall.java │ │ │ └── inner │ │ │ ├── IShellCodeLoadClassFromPCL.java │ │ │ ├── IShellCodeMethodInvoke.java │ │ │ ├── IShellCodeNewArrayAndAddContent.java │ │ │ ├── IShellCodePushInt.java │ │ │ └── IShellCodeReflectionMethodInvoke.java │ │ └── utils │ │ ├── ASMUtils.java │ │ ├── CustomPrinter.java │ │ ├── CustomTextifier.java │ │ ├── PostPreState.java │ │ └── io │ │ └── BukkitErrorOutputStream.java │ └── resources │ └── META-INF │ └── MANIFEST.MF ├── jitpack.yml ├── logo.png ├── settings.gradle.kts └── src └── main ├── java └── io │ └── github │ └── dueris │ └── eclipse │ ├── api │ ├── Agent.java │ ├── GameLibrary.java │ ├── Launcher.java │ ├── McVersion.java │ ├── Transformer.java │ ├── entrypoint │ │ ├── BootstrapInitializer.java │ │ ├── EntrypointContainer.java │ │ ├── EntrypointInstance.java │ │ └── ModInitializer.java │ ├── game │ │ └── GameProvider.java │ ├── mod │ │ ├── ModContainer.java │ │ ├── ModEngine.java │ │ ├── ModMetadata.java │ │ └── ModResource.java │ └── util │ │ ├── BootstrapEntryContext.java │ │ ├── ClassLoaders.java │ │ ├── IgniteConstants.java │ │ └── IgniteExclusions.java │ ├── loader │ ├── EclipseLauncher.java │ ├── Main.java │ ├── MixinJavaAgent.java │ ├── api │ │ └── impl │ │ │ ├── MixinModEngine.java │ │ │ ├── ModContainerImpl.java │ │ │ └── ModResourceImpl.java │ ├── ember │ │ ├── DynamicClassLoader.java │ │ ├── Ember.java │ │ ├── EmberClassLoader.java │ │ ├── EmberMixinService.java │ │ ├── mixin │ │ │ ├── EmberMixinBootstrap.java │ │ │ ├── EmberMixinContainer.java │ │ │ └── EmberMixinLogger.java │ │ └── patch │ │ │ ├── EmberTransformer.java │ │ │ ├── GamePatch.java │ │ │ ├── TransformPhase.java │ │ │ └── TransformerService.java │ ├── launch │ │ ├── patch │ │ │ ├── BootstrapEntrypointPatch.java │ │ │ ├── BrandingPatch.java │ │ │ ├── CraftServerNamePatch.java │ │ │ ├── PatchHooks.java │ │ │ └── package-info.java │ │ ├── property │ │ │ ├── MixinGlobalPropertyService.java │ │ │ └── MixinStringPropertyKey.java │ │ └── transformer │ │ │ ├── AccessWidenerTransformer.java │ │ │ ├── ForgeTransformerService.java │ │ │ ├── MixinTransformer.java │ │ │ └── forge │ │ │ ├── EqualityCheckingLinkedHashMap.java │ │ │ ├── FinalModifierDefinition.java │ │ │ ├── ForgeAccessTransformer.java │ │ │ ├── ModifierType.java │ │ │ ├── TargetData.java │ │ │ ├── TransformTarget.java │ │ │ └── package-info.java │ ├── minecraft │ │ ├── McVersionUtil.java │ │ ├── MinecraftGameProvider.java │ │ └── PaperclipJar.java │ └── util │ │ ├── Getter.java │ │ ├── LaunchException.java │ │ ├── Pair.java │ │ ├── ResourceConnection.java │ │ ├── Util.java │ │ └── mrj │ │ ├── AbstractClassLoader.java │ │ ├── AbstractSecureClassLoader.java │ │ └── AbstractUrlClassLoader.java │ └── plugin │ ├── EclipsePlugin.java │ ├── access │ ├── EclipseMain.java │ ├── MixinPlugin.java │ ├── MixinPluginMeta.java │ └── PluginClassloaderHolder.java │ ├── mixin │ ├── BootstrapMixin.java │ ├── DirectoryProviderSourceMixin.java │ ├── FolderRepositorySourceMixin.java │ ├── JavaPluginMixin.java │ ├── MainMixin.java │ ├── ModernPluginLoadingStrategyMixin.java │ ├── PackDetectorMixin.java │ ├── PaperClasspathBuilderMixin.java │ ├── PaperPluginMetaMixin.java │ ├── PaperPluginParentMixin.java │ ├── PaperPluginsCommandMixin.java │ ├── PaperServerPluginProviderMixin.java │ ├── PluginInitializerManagerMixin.java │ ├── PluginRemapperMixin.java │ ├── RemappedPluginIndexMixin.java │ └── SysoutCatcherMixin.java │ └── util │ ├── BootstrapEntrypoint.java │ ├── DependencyLoader.java │ ├── FileDeleterVisitor.java │ ├── Injectors.java │ ├── OptionSetUtils.java │ └── SerializedOptionSetData.java └── resources ├── META-INF └── services │ ├── io.github.dueris.eclipse.loader.ember.patch.GamePatch │ ├── io.github.dueris.eclipse.loader.ember.patch.TransformerService │ ├── org.spongepowered.asm.service.IGlobalPropertyService │ ├── org.spongepowered.asm.service.IMixinService │ └── org.spongepowered.asm.service.IMixinServiceBootstrap ├── eclipse.accesswidener ├── eclipse.mixins.json ├── paper-plugin.yml └── tinylog.properties /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff 2 | .idea/ 3 | 4 | *.iml 5 | *.ipr 6 | *.iws 7 | 8 | # IntelliJ 9 | out/ 10 | # mpeltonen/sbt-idea plugin 11 | .idea_modules/ 12 | 13 | # JIRA plugin 14 | atlassian-ide-plugin.xml 15 | 16 | # Compiled class file 17 | *.class 18 | 19 | # Log file 20 | *.log 21 | 22 | # BlueJ files 23 | *.ctxt 24 | 25 | # Package Files # 26 | *.jar 27 | *.war 28 | *.nar 29 | *.ear 30 | *.zip 31 | *.tar.gz 32 | *.rar 33 | 34 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 35 | hs_err_pid* 36 | 37 | *~ 38 | 39 | # temporary files which can be created if a process still has a handle open of a deleted file 40 | .fuse_hidden* 41 | 42 | # KDE directory preferences 43 | .directory 44 | 45 | # Linux trash folder which might appear on any partition or disk 46 | .Trash-* 47 | 48 | # .nfs files are created when an open file is removed but is still being accessed 49 | .nfs* 50 | 51 | # General 52 | .DS_Store 53 | .AppleDouble 54 | .LSOverride 55 | 56 | # Icon must end with two \r 57 | Icon 58 | 59 | # Thumbnails 60 | ._* 61 | 62 | # Files that might appear in the root of a volume 63 | .DocumentRevisions-V100 64 | .fseventsd 65 | .Spotlight-V100 66 | .TemporaryItems 67 | .Trashes 68 | .VolumeIcon.icns 69 | .com.apple.timemachine.donotpresent 70 | 71 | # Directories potentially created on remote AFP share 72 | .AppleDB 73 | .AppleDesktop 74 | Network Trash Folder 75 | Temporary Items 76 | .apdisk 77 | 78 | # Windows thumbnail cache files 79 | Thumbs.db 80 | Thumbs.db:encryptable 81 | ehthumbs.db 82 | ehthumbs_vista.db 83 | 84 | # Dump file 85 | *.stackdump 86 | 87 | # Folder config file 88 | [Dd]esktop.ini 89 | 90 | # Recycle Bin used on file shares 91 | $RECYCLE.BIN/ 92 | 93 | # Windows Installer files 94 | *.cab 95 | *.msi 96 | *.msix 97 | *.msm 98 | *.msp 99 | 100 | # Windows shortcuts 101 | *.lnk 102 | 103 | .gradle 104 | build/ 105 | 106 | # Ignore Gradle GUI config 107 | gradle-app.setting 108 | 109 | # Cache of project 110 | .gradletasknamecache 111 | 112 | **/build/ 113 | 114 | # Common working directory 115 | run/ 116 | 117 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 118 | !gradle-wrapper.jar 119 | 120 | .idea/ 121 | run/ 122 | cache/ 123 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "java.compile.nullAnalysis.mode": "automatic", 3 | "java.configuration.updateBuildConfiguration": "automatic", 4 | "java.project.sourcePaths": [ 5 | "src/main/java" 6 | ] 7 | } -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | 3 | **Eclipse** is a Paper plugin that introduces SpongePowered/Fabric Mixin to the Paper server environment. This project implements a modified version of the Ignite Mixin Launcher, modified to be more flexible and agnostic in terms of the Minecraft version(allowing it to function on numerous versions with little issues) and built specifically for the Eclipse environment. 4 | 5 | Eclipse includes a large array of features: 6 | - **Access Wideners** 7 | - **Fabric Mixins** 8 | - **Mixin Extras** 9 | 10 | ## Getting Started 11 | 12 | To use Eclipse, include the following options in your `paper-plugin.yaml` (or `.yml`): 13 | 14 | ```yaml 15 | mixins: [ 16 | "example.mixins.json" 17 | ] 18 | wideners: [ 19 | "eclipse.accesswidener" 20 | ] 21 | datapack-entry: true 22 | ``` 23 | 24 | ### Configuration Fields 25 | 26 | - **mixins**: Defines the Mixin configuration files your plugin will use 27 | 28 | Example: 29 | ```json 30 | { 31 | "mixins": [ 32 | "ExampleMixin" 33 | ], 34 | "package": "me.dueris.example.mixin", 35 | "compatibilityLevel": "JAVA_21" 36 | } 37 | ``` 38 | 39 | - **wideners**: Specifies the access wideners. 40 | 41 | - **datapack-entry**: Allows Eclipse to also load your plugin as a vanilla datapack, similar to a fabric mod. 42 | 43 | ## Installation 44 | 1. Download Eclipse from [modrinth](https://modrinth.com/plugin/eclipse-mixin). 45 | 2. Place the Eclipse plugin jar in your servers `plugins` directory. 46 | 3. Configure your plugins `paper-plugin.yaml` as shown above. 47 | 48 | ## Examples 49 | For a plugin named `ExamplePlugin`: 50 | - `paper-plugin.yaml` 51 | ```yaml 52 | name: ExamplePlugin 53 | main: me.dueris.example.Main 54 | version: 1.0.0 55 | mixins: [ 56 | "eclipse.mixins.json" 57 | ] 58 | wideners: [ 59 | "eclipse.accesswidener" 60 | ] 61 | datapack-entry: true 62 | ``` 63 | 64 | - `eclipse.mixins.json` 65 | ```json 66 | { 67 | "mixins": [ 68 | "ExampleMixin" 69 | ], 70 | "package": "me.dueris.example.mixin", 71 | "compatibilityLevel": "JAVA_21" 72 | } 73 | ``` 74 | 75 | ## License 76 | This project is licensed under GPL-3.0 with an additional permission clause: 77 | Redistribution and use in binary form are allowed, provided that explicit permission is obtained from the author for direct integration into third-party projects. 78 | 79 | See the [LICENSE](https://github.com/Dueris/Eclipse/blob/master/LICENSE) file for more details. 80 | 81 | --- -------------------------------------------------------------------------------- /build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation(libs.build.nexus) 7 | implementation(libs.build.shadow) 8 | } 9 | 10 | dependencies { 11 | compileOnly(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) 12 | } 13 | 14 | java { 15 | sourceCompatibility = JavaVersion.VERSION_21 16 | targetCompatibility = JavaVersion.VERSION_21 17 | } 18 | 19 | kotlin { 20 | target { 21 | compilations.configureEach { 22 | kotlinOptions { 23 | jvmTarget = "21" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ignite-build-logic" 2 | 3 | dependencyResolutionManagement { 4 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 5 | repositories { 6 | gradlePluginPortal() 7 | } 8 | 9 | versionCatalogs { 10 | register("libs") { 11 | from(files("../gradle/libs.versions.toml")) // include from parent project 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/extensions.kt: -------------------------------------------------------------------------------- 1 | import org.gradle.api.plugins.JavaPluginExtension 2 | import org.gradle.jvm.toolchain.JavaLanguageVersion 3 | 4 | fun JavaPluginExtension.javaTarget(version: Int) { 5 | toolchain.languageVersion.set(JavaLanguageVersion.of(version)) 6 | } 7 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/ignite.base-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | } 4 | 5 | // Expose version catalog 6 | val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class) 7 | 8 | group = rootProject.group 9 | version = rootProject.version 10 | 11 | java { 12 | javaTarget(21) 13 | withSourcesJar() 14 | } 15 | 16 | dependencies { 17 | compileOnly(libs.jetbrains.annotations) 18 | } 19 | 20 | tasks { 21 | javadoc { 22 | val minimalOptions: MinimalJavadocOptions = options 23 | options.encoding("UTF-8") 24 | 25 | if (minimalOptions is StandardJavadocDocletOptions) { 26 | val options: StandardJavadocDocletOptions = minimalOptions 27 | options.addStringOption("Xdoclint:none", "-quiet") 28 | } 29 | } 30 | 31 | compileJava { 32 | options.encoding = "UTF-8" 33 | options.compilerArgs.addAll( 34 | listOf( 35 | "-nowarn", 36 | "-Xlint:-unchecked", 37 | "-Xlint:-deprecation" 38 | ) 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/ignite.launcher-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | 3 | plugins { 4 | id("ignite.base-conventions") 5 | id("com.gradleup.shadow") 6 | } 7 | 8 | // Expose version catalog 9 | val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class) 10 | 11 | val implementationVersion = project.version.toString() 12 | val regexPattern = """(\d+\.\d+)""".toRegex() 13 | val apiVersion = regexPattern.find(implementationVersion)?.value 14 | 15 | tasks.getByName("jar") { 16 | manifest { 17 | attributes( 18 | "Premain-Class" to "io.github.dueris.eclipse.loader.MixinJavaAgent", 19 | "Agent-Class" to "io.github.dueris.eclipse.loader.MixinJavaAgent", 20 | "Launcher-Agent-Class" to "io.github.dueris.eclipse.loader.MixinJavaAgent", 21 | "Main-Class" to "io.github.dueris.eclipse.loader.Main", 22 | "Multi-Release" to true, 23 | "Automatic-Module-Name" to "net.minecrell.terminalconsole", 24 | 25 | "Specification-Title" to "eclipse", 26 | "Specification-Version" to apiVersion, 27 | "Specification-Vendor" to "Dueris", 28 | 29 | "Implementation-Title" to project.name, 30 | "Implementation-Version" to implementationVersion, 31 | "Implementation-Vendor" to "Dueris" 32 | ) 33 | 34 | attributes( 35 | "org/objectweb/asm/", 36 | "Implementation-Version" to "9.7.1" 37 | ) 38 | } 39 | } 40 | 41 | tasks.getByName("shadowJar") { 42 | mergeServiceFiles() 43 | 44 | relocate("com.google.gson", "io.github.dueris.eclipse.loader.libs.gson") 45 | } 46 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/ignite.parent-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("io.github.gradle-nexus.publish-plugin") 3 | } 4 | 5 | nexusPublishing { 6 | repositories { 7 | sonatype { 8 | nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) 9 | snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) 10 | } 11 | } 12 | } 13 | 14 | tasks.getByName("clean") { 15 | doLast { 16 | delete(rootProject.layout.buildDirectory) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /build-logic/src/main/kotlin/ignite.publish-conventions.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("ignite.base-conventions") 3 | `maven-publish` 4 | signing 5 | } 6 | 7 | // Expose version catalog 8 | val libs = extensions.getByType(org.gradle.accessors.dm.LibrariesForLibs::class) 9 | 10 | java { 11 | withJavadocJar() 12 | } 13 | -------------------------------------------------------------------------------- /console/build.gradle.kts: -------------------------------------------------------------------------------- 1 | version = "v1.0.0" 2 | 3 | println("Loaded subproject \"${project.name}\" with version '$version'") 4 | 5 | dependencies { 6 | compileOnly(libs.tinylog.impl) 7 | } 8 | 9 | tasks.getByName("compileJava").dependsOn(":injection:paperweightUserdevSetup") -------------------------------------------------------------------------------- /console/src/main/java/net/minecrell/terminalconsole/TCALookup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2017 Minecrell 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package net.minecrell.terminalconsole; 26 | 27 | import org.apache.logging.log4j.core.LogEvent; 28 | import org.apache.logging.log4j.core.config.plugins.Plugin; 29 | import org.apache.logging.log4j.core.lookup.AbstractLookup; 30 | import org.apache.logging.log4j.core.lookup.StrLookup; 31 | import org.checkerframework.checker.nullness.qual.Nullable; 32 | 33 | /** 34 | * A {@link StrLookup} that returns properties specific to 35 | * {@link TerminalConsoleAppender}. The following properties are supported: 36 | * 37 | *
    38 | *
  • {@code ${tca:disableAnsi}}: Can be used together with 39 | * {@code PatternLayout} to disable ANSI colors for patterns like 40 | * {@code %highlight} or {@code %style} if ANSI colors are unsupported 41 | * or are disabled for {@link TerminalConsoleAppender}. 42 | * 43 | *

    Example usage: 44 | * {@code }

  • 45 | *
46 | */ 47 | @Plugin(name = "tca", category = StrLookup.CATEGORY) 48 | public final class TCALookup extends AbstractLookup { 49 | 50 | /** 51 | * Lookup key that returns if ANSI colors are unsupported/disabled. 52 | */ 53 | public final static String KEY_DISABLE_ANSI = "disableAnsi"; 54 | 55 | @Override 56 | @Nullable 57 | public String lookup(LogEvent event, String key) { 58 | if (KEY_DISABLE_ANSI.equals(key)) { 59 | return String.valueOf(!TerminalConsoleAppender.isAnsiSupported()); 60 | } 61 | return null; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /console/src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Automatic-Module-Name: net.minecrell.terminalconsole 3 | 4 | -------------------------------------------------------------------------------- /console/src/main/resources/META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat: -------------------------------------------------------------------------------- 1 | coreterminalconsole5net.minecrell.terminalconsole.TerminalConsoleAppenderappenderloggernamepatternselector testClass : List.of(StaticGetPluginTest.class, PluginManagerGetPlugin.class, ColoredLoggingOutputTest.class, JavaPluginJarFileTest.class, ForgeAccessTest.class)) { 14 | try { 15 | TestInstance testInstance = testClass.newInstance(); 16 | try { 17 | testInstance.test(); 18 | } catch (TestFailedException throwable) { 19 | throw new RuntimeException("Test failed! : " + testClass.getName(), throwable); 20 | } 21 | } catch (InstantiationException | IllegalAccessException e) { 22 | throw new RuntimeException("Unable to create new test instance!", e); 23 | } 24 | } 25 | EclipseExample.getPlugin(EclipseExample.class).getLog4JLogger().info("All tests passed!"); 26 | } 27 | 28 | void test() throws TestFailedException; 29 | } 30 | -------------------------------------------------------------------------------- /example/src/main/resources/example.accesswidener: -------------------------------------------------------------------------------- 1 | accessWidener v1 named 2 | -------------------------------------------------------------------------------- /example/src/main/resources/example.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "mixins": [ 3 | "MinecraftServerMixin" 4 | ], 5 | "package": "io.github.dueris.example.mixin", 6 | "compatibilityLevel": "JAVA_21" 7 | } -------------------------------------------------------------------------------- /example/src/main/resources/example_at.cfg: -------------------------------------------------------------------------------- 1 | # Make public & mutable 2 | public-f net.minecraft.Util DEFAULT_MAX_THREADS 3 | public net.minecraft.server.MinecraftServer constructOrExtractCrashReport(Ljava/lang/Throwable;)Lnet/minecraft/CrashReport; 4 | public net.minecraft.server.MinecraftServer saveDebugReport(Ljava/nio/file/Path;)V 5 | protected net.minecraft.server.MinecraftServer debugCommandProfiler 6 | protected+f net.minecraft.server.MinecraftServer$TimeProfiler 7 | -------------------------------------------------------------------------------- /example/src/main/resources/paper-plugin.yml: -------------------------------------------------------------------------------- 1 | name: EclipseTest 2 | version: '1.0.0-SNAPSHOT' 3 | main: io.github.dueris.example.EclipseExample 4 | api-version: '1.21' 5 | prefix: EclipseTest 6 | load: POSTWORLD 7 | authors: [ Dueris ] 8 | description: Test plugin for Eclipse 9 | 10 | mixins: [ 11 | "example.mixins.json" 12 | ] 13 | wideners: [ 14 | "example.accesswidener" 15 | ] 16 | 17 | transformer: "example_at.cfg" 18 | 19 | entrypoints: 20 | bootstrap: "io.github.dueris.example.EclipseBootstrapEntrypoint" 21 | server: "io.github.dueris.example.EclipseModEntrypoint" -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group=me.dueris.eclipse 2 | version=2.0.4-SNAPSHOT 3 | description=A mixin plugin using the ignite modEngine for Paper 4 | 5 | ossrhUsername=System.getenv("OSSRH_USERNAME") 6 | ossrhPassword=System.getenv("OSSRH_PASSWORD") -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | metadata.format.version = "1.1" 2 | 3 | [versions] 4 | accessWidener = "2.1.0" 5 | gson = "2.11.0" 6 | jetbrains = "24.1.0" 7 | mixin = "0.15.4+mixin.0.8.7" 8 | mixinExtras = "0.4.1" 9 | nexus = "2.0.0" 10 | shadow = "8.3.1" 11 | tinylog = "2.7.0" 12 | asm = "9.7.1" 13 | 14 | [libraries] 15 | jetbrains-annotations = { module = "org.jetbrains:annotations", version.ref = "jetbrains" } 16 | 17 | # logger 18 | tinylog-api = { module = "org.tinylog:tinylog-api", version.ref = "tinylog" } 19 | tinylog-impl = { module = "org.tinylog:tinylog-impl", version.ref = "tinylog" } 20 | 21 | # loader 22 | accessWidener = { module = "net.fabricmc:access-widener", version.ref = "accessWidener" } 23 | asm = { module = "org.ow2.asm:asm", version.ref = "asm" } 24 | asm-analysis = { module = "org.ow2.asm:asm-analysis", version.ref = "asm" } 25 | asm-commons = { module = "org.ow2.asm:asm-commons", version.ref = "asm" } 26 | asm-tree = { module = "org.ow2.asm:asm-tree", version.ref = "asm" } 27 | asm-util = { module = "org.ow2.asm:asm-util", version.ref = "asm" } 28 | mixin = { module = "net.fabricmc:sponge-mixin", version.ref = "mixin" } 29 | mixinExtras = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixinExtras" } 30 | gson = { module = "com.google.code.gson:gson", version.ref = "gson" } 31 | 32 | build-nexus = { module = "io.github.gradle-nexus:publish-plugin", version.ref = "nexus" } 33 | build-shadow = { module = "com.gradleup.shadow:shadow-gradle-plugin", version.ref = "shadow" } 34 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dueris/Eclipse/9134d4833073c9454ec9a08e04572e13cd31c819/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /injection/.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /.gradle/ 3 | /build/ 4 | /bin/ 5 | /run/ 6 | -------------------------------------------------------------------------------- /injection/build.gradle.kts: -------------------------------------------------------------------------------- 1 | version = "v1.0.0" 2 | 3 | println("Loaded subproject \"${project.name}\" with version '$version'") 4 | 5 | dependencies { 6 | compileOnly("org.projectlombok:lombok:1.18.22") 7 | 8 | compileOnly("org.ow2.asm:asm:9.7") 9 | compileOnly("org.ow2.asm:asm-util:9.7") 10 | compileOnly("com.github.olivergondza:maven-jdk-tools-wrapper:0.1") 11 | compileOnly("org.javassist:javassist:3.30.2-GA") 12 | compileOnly("net.bytebuddy:byte-buddy-agent:1.14.19") 13 | compileOnly("io.github.kasukusakura:jvm-self-attach:0.0.1") 14 | compileOnly("io.github.karlatemp:unsafe-accessor:1.7.0") 15 | compileOnly("org.eclipse.jgit:org.eclipse.jgit:6.10.0.202406032230-r") 16 | } 17 | 18 | tasks.getByName("compileJava").dependsOn(":paperweightUserdevSetup") -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/MixBukkit.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit; 2 | 3 | import com.dragoncommissions.mixbukkit.agent.ClassesManager; 4 | import com.dragoncommissions.mixbukkit.agent.JVMAttacher; 5 | import com.dragoncommissions.mixbukkit.api.MixinPlugin; 6 | import com.dragoncommissions.mixbukkit.api.ObfMap; 7 | import com.dragoncommissions.mixbukkit.utils.io.BukkitErrorOutputStream; 8 | import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; 9 | import lombok.Getter; 10 | import lombok.SneakyThrows; 11 | import org.apache.logging.log4j.Logger; 12 | import org.bukkit.Bukkit; 13 | import org.bukkit.ChatColor; 14 | import org.jetbrains.annotations.ApiStatus; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.io.InputStream; 21 | import java.lang.instrument.Instrumentation; 22 | import java.util.HashMap; 23 | import java.util.Map; 24 | import java.util.concurrent.atomic.AtomicReference; 25 | import java.util.jar.JarFile; 26 | 27 | public class MixBukkit { 28 | 29 | public final static String VERSION = "0.1"; 30 | public static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(MixBukkit.class); 31 | 32 | @Getter 33 | private static final Map plugins = new HashMap<>(); 34 | public static MixBukkit INSTANCE; 35 | public static boolean DEBUG = true; 36 | public static boolean WRITE_TRANSFORMED_CLASS = true; 37 | public static boolean SAFE_MODE = false; 38 | public static Instrumentation INSTRUMENTATION = null; 39 | public static boolean PREPARED = false; 40 | public static BukkitErrorOutputStream ERROR_OUTPUT_STREAM = new BukkitErrorOutputStream(); 41 | public static ClassesManager classesManager; 42 | 43 | @ApiStatus.Internal 44 | public static AtomicReference CLASSLOADER = new AtomicReference<>(); 45 | 46 | @Getter 47 | private static JVMAttacher jvmAttacher; 48 | 49 | @Getter 50 | private File pluginFile; 51 | 52 | public MixBukkit(PaperPluginClassLoader classLoader) { 53 | CLASSLOADER.set(classLoader); 54 | } 55 | 56 | public static PaperPluginClassLoader getClassLoader() { 57 | return CLASSLOADER.get(); 58 | } 59 | 60 | @SneakyThrows 61 | public static void addLibrary(File file) { 62 | try { 63 | INSTRUMENTATION.appendToSystemClassLoaderSearch(new JarFile(file)); 64 | } catch (IOException e) { 65 | throw new RuntimeException(e); 66 | } 67 | 68 | if (DEBUG) { 69 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Loading " + file.getAbsolutePath()); 70 | } 71 | } 72 | 73 | @SneakyThrows 74 | public void onEnable(Logger logger, File pluginFile) { 75 | this.pluginFile = pluginFile; 76 | INSTANCE = this; 77 | 78 | jvmAttacher = new JVMAttacher(this); 79 | jvmAttacher.attach(); 80 | if (INSTRUMENTATION == null) { 81 | try { 82 | logger.warn("Failed grabbing instrumentation! If you believe this is an issue, please open a ticket"); 83 | logger.warn(""); 84 | logger.warn("======= FAILED GETTING INSTRUMENTATION ======"); 85 | logger.warn("Please check those things before opening an issue:"); 86 | logger.warn("1. Do you have -XX:+DisableAttachMechanism? If yes, remove it from server start command."); 87 | logger.warn("2. Does the server have permission to spawn a process? If no, give it. Normally yes unless you are using a server panel that limits the privilege"); 88 | logger.warn(""); 89 | logger.warn("============================================="); 90 | 91 | throw new NullPointerException("Instrumentation is null"); 92 | } catch (Exception e) { 93 | logger.error("An error occurred:", e); 94 | } 95 | } 96 | 97 | ClassesManager.init(); 98 | PREPARED = true; 99 | } 100 | 101 | public @NotNull MixinPlugin registerMixinPlugin(@NotNull MixinPluginInstance plugin, InputStream membersMapStream) { 102 | MixinPlugin mixinPlugin = plugins.get(plugin.name()); 103 | if (mixinPlugin != null) { 104 | return mixinPlugin; 105 | } 106 | mixinPlugin = new MixinPlugin(plugin, new ObfMap(membersMapStream)); 107 | plugins.put(plugin.name(), mixinPlugin); 108 | 109 | 110 | return mixinPlugin; 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/MixinPluginInstance.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit; 2 | 3 | public record MixinPluginInstance(String name) { 4 | } 5 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/agent/AgentMain.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.agent; 2 | 3 | import java.lang.instrument.Instrumentation; 4 | 5 | public class AgentMain { 6 | 7 | public static void agentmain(Instrumentation instrumentation, String args) { 8 | 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/agent/ClassesManager.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.agent; 2 | 3 | import com.dragoncommissions.mixbukkit.MixBukkit; 4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 5 | import javassist.ClassPool; 6 | import org.objectweb.asm.tree.ClassNode; 7 | 8 | import java.lang.instrument.ClassFileTransformer; 9 | import java.lang.instrument.IllegalClassFormatException; 10 | import java.security.ProtectionDomain; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class ClassesManager { 15 | 16 | 17 | private static final ClassPool cp = ClassPool.getDefault(); 18 | public static Map classes = new HashMap<>(); 19 | public static Map classNodes = new HashMap<>(); 20 | 21 | public static void init() { 22 | MixBukkit.INSTRUMENTATION.addTransformer(new ClassFileTransformer() { 23 | @Override 24 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 25 | classes.put(className.replace("/", "."), classfileBuffer); 26 | return classfileBuffer; 27 | } 28 | }, true); 29 | } 30 | 31 | public static ClassNode getClassNode(String name) { 32 | ClassNode classNode1 = classNodes.get(name); 33 | if (classNode1 == null) { 34 | byte[] classBytecode = getClassBytecode(name); 35 | if (classBytecode == null) return null; 36 | classNode1 = ASMUtils.toClassNode(classBytecode); 37 | return classNode1; 38 | } 39 | return classNode1; 40 | } 41 | 42 | public synchronized static byte[] getClassBytecode(String name) { 43 | name = name.replace("/", "."); 44 | byte[] bytes = classes.get(name); 45 | if (bytes == null) { 46 | Class[] allLoadedClasses = MixBukkit.INSTRUMENTATION.getAllLoadedClasses(); 47 | for (Class allLoadedClass : allLoadedClasses) { 48 | if (allLoadedClass.getName().equals(name)) { 49 | try { 50 | MixBukkit.INSTRUMENTATION.retransformClasses(allLoadedClass); 51 | } catch (Exception e) { 52 | e.printStackTrace(); 53 | } 54 | } 55 | } 56 | } 57 | return classes.get(name); 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/agent/JVMAttacher.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.agent; 2 | 3 | import com.dragoncommissions.mixbukkit.MixBukkit; 4 | import io.github.karlatemp.unsafeaccessor.UnsafeAccess; 5 | import io.github.kasukusakura.jsa.JvmSelfAttach; 6 | import lombok.SneakyThrows; 7 | import org.apache.logging.log4j.LogManager; 8 | 9 | import java.io.File; 10 | import java.lang.management.ManagementFactory; 11 | 12 | public class JVMAttacher { 13 | 14 | static final UnsafeAccess UA = UnsafeAccess.getInstance(); 15 | private final MixBukkit mixBukkit; 16 | 17 | public JVMAttacher(MixBukkit mixBukkit) { 18 | this.mixBukkit = mixBukkit; 19 | } 20 | 21 | @SneakyThrows 22 | public void attach() { 23 | LogManager.getLogger("JVM Attachment").info("Attaching JVM Instrumentation.."); 24 | JvmSelfAttach.init(new File(System.getProperty("java.io.tmpdir"))); 25 | MixBukkit.INSTRUMENTATION = JvmSelfAttach.getInstrumentation(); 26 | 27 | } 28 | 29 | public int getCurrentPID() { 30 | return Integer.parseInt(ManagementFactory.getRuntimeMXBean().getName().split("@")[0]); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/ObfMap.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api; 2 | 3 | import com.dragoncommissions.mixbukkit.MixBukkit; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.ChatColor; 9 | 10 | import java.io.InputStream; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Scanner; 14 | 15 | public class ObfMap { 16 | 17 | // Deobf -> obf 18 | private final Map fieldMappings = new HashMap<>(); 19 | private final Map methodMappings = new HashMap<>(); 20 | 21 | public ObfMap(InputStream memberMap) { 22 | if (memberMap == null) { 23 | if (MixBukkit.DEBUG) { 24 | Bukkit.getConsoleSender().sendMessage(ChatColor.DARK_GRAY + "// Mapping is null"); 25 | } 26 | return; 27 | } 28 | Scanner scanner = new Scanner(memberMap); 29 | while (scanner.hasNextLine()) { 30 | String line = scanner.nextLine(); 31 | if (line.startsWith("#")) continue; 32 | String[] s = line.split(" "); 33 | if (s.length == 4) { // Method mapping, 34 | methodMappings.put(new MethodMapping(s[0], s[2], s[3]), s[1]); 35 | } else if (s.length == 3) { // Field mapping, 36 | fieldMappings.put(new FieldMapping(s[0], s[2]), s[1]); 37 | } else { 38 | System.out.println("Illegal Mapping: " + line + " (Length: " + s.length + ")"); 39 | } 40 | } 41 | } 42 | 43 | public String resolveMapping(FieldMapping fieldMapping) { 44 | String s = fieldMappings.get(fieldMapping); 45 | if (s == null) return fieldMapping.getFieldName(); 46 | return s; 47 | } 48 | 49 | public String resolveMapping(MethodMapping methodMapping) { 50 | String s = methodMappings.get(methodMapping); 51 | if (s == null) return methodMapping.getMethodName(); 52 | return s; 53 | } 54 | 55 | @Getter 56 | @EqualsAndHashCode 57 | @ToString 58 | public static class FieldMapping { 59 | private final String ownerName; // Replaced / with . 60 | private final String fieldName; 61 | 62 | public FieldMapping(String ownerName, String fieldName) { 63 | this.ownerName = ownerName.replace(".", "/"); 64 | this.fieldName = fieldName; 65 | } 66 | 67 | public String getFieldName() { 68 | return fieldName; 69 | } 70 | 71 | public String getOwnerName() { 72 | return ownerName; 73 | } 74 | } 75 | 76 | @Getter 77 | @EqualsAndHashCode 78 | @ToString 79 | public static class MethodMapping { 80 | private final String ownerName; // Replaced / with . 81 | private final String descriptor; 82 | private final String methodName; 83 | 84 | public MethodMapping(String ownerName, String descriptor, String methodName) { 85 | this.ownerName = ownerName.replace(".", "/"); 86 | this.descriptor = descriptor; 87 | this.methodName = methodName; 88 | } 89 | 90 | public String getOwnerName() { 91 | return ownerName; 92 | } 93 | 94 | public String getDescriptor() { 95 | return descriptor; 96 | } 97 | 98 | public String getMethodName() { 99 | return methodName; 100 | } 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/action/MixinAction.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action; 2 | 3 | import org.objectweb.asm.tree.MethodNode; 4 | 5 | public interface MixinAction { 6 | 7 | void action(Class owner, MethodNode method); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionCallSuper.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.MixinPlugin; 4 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeMethodInvoke; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.objectweb.asm.tree.MethodNode; 10 | import org.objectweb.asm.tree.VarInsnNode; 11 | 12 | import java.lang.reflect.Method; 13 | import java.lang.reflect.Modifier; 14 | 15 | public record MActionCallSuper(MixinPlugin plugin) implements MixinAction { 16 | 17 | 18 | @Override 19 | public void action(@NotNull Class owner, MethodNode method) { 20 | Method m = null; 21 | Method superMethod = null; 22 | for (Method declaredMethod : owner.getDeclaredMethods()) { 23 | if (declaredMethod.getName().equals(method.name)) { 24 | if (ASMUtils.getDescriptor(declaredMethod.getReturnType(), declaredMethod.getParameterTypes()) 25 | .equals(method.desc)) { 26 | m = declaredMethod; 27 | } 28 | } 29 | } 30 | if (m != null) { 31 | Class superclass = owner.getSuperclass(); 32 | while (superclass != null) { 33 | for (Method declaredMethod : superclass.getDeclaredMethods()) { 34 | if (declaredMethod.getName().equals(method.name)) { 35 | if (ASMUtils.getDescriptor(declaredMethod.getReturnType(), declaredMethod.getParameterTypes()) 36 | .equals(method.desc)) { 37 | superMethod = declaredMethod; 38 | } 39 | } 40 | } 41 | superclass = superclass.getSuperclass(); 42 | } 43 | } 44 | if (superMethod == null) { 45 | throw new IllegalArgumentException("Could not find super method in " + owner.getSimpleName()); 46 | } 47 | method.instructions.clear(); 48 | int varNum = 0; 49 | if (Modifier.isStatic(superMethod.getModifiers())) { 50 | method.instructions.add(new VarInsnNode(Opcode.ALOAD, varNum++)); 51 | } 52 | Class[] parameterTypes = superMethod.getParameterTypes(); 53 | for (int i = 0; i < parameterTypes.length; i++) { 54 | method.instructions.add(ASMUtils.loadVar(parameterTypes[i], varNum++)); 55 | } 56 | method.instructions.add(new IShellCodeMethodInvoke(superMethod).generate()); 57 | method.instructions.add(ASMUtils.genReturnNode(superMethod.getReturnType())); 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionInsertShellCode.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 4 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 6 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 7 | import org.bukkit.Bukkit; 8 | import org.bukkit.ChatColor; 9 | import org.objectweb.asm.tree.InsnList; 10 | import org.objectweb.asm.tree.MethodNode; 11 | 12 | import java.util.List; 13 | 14 | public record MActionInsertShellCode(ShellCode shellCode, HookLocator hookLocator) implements MixinAction { 15 | 16 | @Override 17 | public void action(Class owner, MethodNode method) { 18 | // Copy hookLocator.getLineNumber(method) to listHooks 19 | List hooks = hookLocator.getLineNumber(method.instructions); 20 | 21 | LocalVarManager localVarManager = new LocalVarManager(method); 22 | 23 | // Hook! 24 | InsnList newInstructions = new InsnList(); 25 | for (int i = 0; i < method.instructions.size(); i++) { 26 | if (hooks.contains(i)) { 27 | if (shellCode.getShellCodeInfo().calledDirectly()) { 28 | try { 29 | InsnList instructions = shellCode.generate(method, localVarManager); 30 | newInstructions.add(instructions); 31 | newInstructions.add(shellCode.popExtraStack()); 32 | } catch (Exception e) { 33 | Bukkit.getConsoleSender() 34 | .sendMessage(ChatColor.RED + "[!] Shell Code \"" + ChatColor.YELLOW + shellCode.getShellCodeInfo() 35 | .name() + ChatColor.RED + "\" has failed generating instructions: Exception Thrown"); 36 | e.printStackTrace(); 37 | } 38 | } else { 39 | Bukkit.getConsoleSender() 40 | .sendMessage(ChatColor.RED + "[!] Shell Code \"" + ChatColor.YELLOW + shellCode.getShellCodeInfo() 41 | .name() + ChatColor.RED + "\" shouldn't be called directly (calledDirectly = false)"); 42 | } 43 | 44 | } 45 | newInstructions.add(method.instructions.get(i)); 46 | } 47 | method.instructions = newInstructions; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionMethodCallSpoofer.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke; 5 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 6 | import javassist.bytecode.Opcode; 7 | import lombok.SneakyThrows; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.objectweb.asm.tree.*; 10 | 11 | import java.lang.reflect.Method; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.UUID; 15 | import java.util.function.Predicate; 16 | 17 | @SuppressWarnings("Untested") 18 | public class MActionMethodCallSpoofer implements MixinAction { 19 | 20 | private static final Map getters = new HashMap<>(); 21 | private final Method method; 22 | private final String key; 23 | private Predicate filter; 24 | 25 | private MActionMethodCallSpoofer(@NotNull Method method, ReturnValueGetter returnValueGetter, Predicate filter) { 26 | this.method = method; 27 | if (method.getReturnType() == void.class) 28 | throw new IllegalArgumentException("Method: " + method.getName() + " is not returning anything. Nothing to spoof"); 29 | UUID uuid = UUID.randomUUID(); 30 | while (getters.containsKey(uuid.toString())) { 31 | uuid = UUID.randomUUID(); 32 | System.out.println("Come on! You are so f**king lucky! You literally got same UUID, how?"); 33 | System.out.println("If you see this message without cheating, today is literally your most luckiest day"); 34 | System.out.println("I would say the chance of it is lower than every thing you can think of"); 35 | System.out.println("UUID: " + uuid); 36 | } 37 | getters.put(uuid.toString(), returnValueGetter); 38 | this.key = uuid.toString(); 39 | } 40 | 41 | public static Object get(String value) { 42 | return getters.get(value).getReturnValue(); 43 | } 44 | 45 | @Override 46 | @SneakyThrows 47 | public void action(Class owner, MethodNode methodNode) { 48 | InsnList out = new InsnList(); 49 | int amount = 0; 50 | for (AbstractInsnNode instruction : methodNode.instructions) { 51 | out.add(instruction); 52 | if (instruction instanceof MethodInsnNode insn) { 53 | if (insn.name.equals(method.getName()) && insn.owner.equals(method.getDeclaringClass().getName() 54 | .replace(".", "/")) 55 | && insn.desc.equals(ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes())) && filter.test(amount++)) { 56 | out.add(new InsnNode(Opcode.POP)); // Pop the return value first 57 | out.add(new LdcInsnNode(key)); 58 | try { 59 | out.add(new IShellCodeReflectionMethodInvoke(MActionMethodCallSpoofer.class.getDeclaredMethod("get", String.class)).generate()); 60 | } catch (NoSuchMethodException e) { 61 | throw new RuntimeException(e); 62 | } 63 | out.add(ASMUtils.cast(method.getReturnType())); 64 | } 65 | } 66 | } 67 | methodNode.instructions = out; 68 | } 69 | 70 | public interface ReturnValueGetter { 71 | Object getReturnValue(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionMethodReplacer.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import lombok.SneakyThrows; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.InsnNode; 12 | import org.objectweb.asm.tree.MethodNode; 13 | 14 | import java.lang.reflect.Method; 15 | 16 | public record MActionMethodReplacer(Method handler) implements MixinAction { 17 | 18 | @Override 19 | @SneakyThrows 20 | public void action(Class owner, @NotNull MethodNode methodNode) { 21 | methodNode.tryCatchBlocks.clear(); 22 | methodNode.localVariables.clear(); 23 | methodNode.instructions.clear(); 24 | Class returnType = ASMUtils.getReturnType(methodNode.desc); 25 | if (returnType != handler.getReturnType()) 26 | throw new IllegalArgumentException("Handler: " + handler.getName() + " is not returning same type as target method (" + handler.getReturnType() 27 | .getName() + "(return type of handler) != " + returnType.getName() + "(return type of target) )"); 28 | LocalVarManager varManager = new LocalVarManager(methodNode); 29 | InsnList out = new InsnList(); 30 | Class[] parameterTypes = handler.getParameterTypes(); 31 | for (int i = 0; i < parameterTypes.length; i++) { 32 | Class parameterType = parameterTypes[i]; 33 | out.add(ASMUtils.castToObject(i, parameterType)); 34 | } 35 | IShellCodeReflectionMethodInvoke shellCodeReflectionMethodInvoke = new IShellCodeReflectionMethodInvoke(handler); 36 | out.add(shellCodeReflectionMethodInvoke.generate(methodNode, varManager)); 37 | if (!methodNode.desc.endsWith("V")) { 38 | out.add(ASMUtils.cast(returnType)); 39 | out.add(ASMUtils.genReturnNode(returnType)); 40 | } else { 41 | out.add(new InsnNode(Opcode.RETURN)); 42 | } 43 | methodNode.instructions = out; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/action/impl/MActionPipeline.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.action.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.action.MixinAction; 4 | import org.objectweb.asm.tree.MethodNode; 5 | 6 | public record MActionPipeline(MixinAction... actions) implements MixinAction { 7 | 8 | @Override 9 | public void action(Class owner, MethodNode method) { 10 | for (MixinAction action : actions) { 11 | action.action(owner, method); 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/locator/HookLocator.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator; 2 | 3 | import org.objectweb.asm.tree.InsnList; 4 | 5 | import java.util.List; 6 | 7 | public interface HookLocator { 8 | 9 | /** 10 | * Process a method, and return the line number to hook at. Return empty array if it fails to find target hook location 11 | * 12 | * @param insnList MethodNode input 13 | * @return Array of line numbers, it will be inserted, means 0 will make first instruction into hooking instruction 14 | */ 15 | List getLineNumber(InsnList insnList); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorFieldAccess.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 5 | import com.dragoncommissions.mixbukkit.utils.PostPreState; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.objectweb.asm.tree.FieldInsnNode; 8 | import org.objectweb.asm.tree.InsnList; 9 | 10 | import java.lang.reflect.Field; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.function.Predicate; 14 | 15 | public class HLocatorFieldAccess implements HookLocator { 16 | 17 | private final Field field; 18 | private final PostPreState state; 19 | private final Predicate filter; 20 | 21 | public HLocatorFieldAccess(Field field, PostPreState state, Predicate filter) { 22 | this.field = field; 23 | this.state = state; 24 | this.filter = filter; 25 | } 26 | 27 | @Override 28 | public List getLineNumber(@NotNull InsnList insnList) { 29 | List out = new ArrayList<>(); 30 | int amount = 0; 31 | for (int i = 0; i < insnList.size(); i++) { 32 | if (insnList.get(i) instanceof FieldInsnNode fieldInsnNode) { 33 | String owner = field.getDeclaringClass().getName().replace(".", "/"); 34 | String name = field.getName(); 35 | String desc = ASMUtils.toDescriptorTypeName(field.getType().getName()); 36 | if (fieldInsnNode.owner.equals(owner) && fieldInsnNode.name.equals(name) && fieldInsnNode.desc.equals(desc) && filter.test(amount++)) { 37 | out.add(i + (state == PostPreState.POST ? 1 : 0)); 38 | } 39 | } 40 | } 41 | return out; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorFieldRead.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 5 | import com.dragoncommissions.mixbukkit.utils.PostPreState; 6 | import javassist.bytecode.Opcode; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.objectweb.asm.tree.FieldInsnNode; 9 | import org.objectweb.asm.tree.InsnList; 10 | 11 | import java.lang.reflect.Field; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.function.Predicate; 15 | 16 | public class HLocatorFieldRead implements HookLocator { 17 | 18 | private final Field field; 19 | private final PostPreState state; 20 | private final Predicate filter; 21 | 22 | public HLocatorFieldRead(Field field, PostPreState state, Predicate filter) { 23 | this.field = field; 24 | this.state = state; 25 | this.filter = filter; 26 | } 27 | 28 | @Override 29 | public List getLineNumber(@NotNull InsnList insnList) { 30 | List out = new ArrayList<>(); 31 | int amount = 0; 32 | for (int i = 0; i < insnList.size(); i++) { 33 | if (insnList.get(i) instanceof FieldInsnNode fieldInsnNode) { 34 | String owner = field.getDeclaringClass().getName().replace(".", "/"); 35 | String name = field.getName(); 36 | String desc = ASMUtils.toDescriptorTypeName(field.getType().getName()); 37 | if (fieldInsnNode.owner.equals(owner) && fieldInsnNode.name.equals(name) && fieldInsnNode.desc.equals(desc) && filter.test(amount++)) { 38 | if (fieldInsnNode.getOpcode() == Opcode.GETFIELD || fieldInsnNode.getOpcode() == Opcode.GETSTATIC) 39 | out.add(i + (state == PostPreState.POST ? 1 : 0)); 40 | } 41 | } 42 | } 43 | return out; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorFieldWrite.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 5 | import com.dragoncommissions.mixbukkit.utils.PostPreState; 6 | import javassist.bytecode.Opcode; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.objectweb.asm.tree.FieldInsnNode; 9 | import org.objectweb.asm.tree.InsnList; 10 | 11 | import java.lang.reflect.Field; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.function.Predicate; 15 | 16 | public class HLocatorFieldWrite implements HookLocator { 17 | 18 | private final Field field; 19 | private final PostPreState state; 20 | private final Predicate filter; 21 | 22 | public HLocatorFieldWrite(Field field, PostPreState state, Predicate filter) { 23 | this.field = field; 24 | this.state = state; 25 | this.filter = filter; 26 | } 27 | 28 | @Override 29 | public List getLineNumber(@NotNull InsnList insnList) { 30 | List out = new ArrayList<>(); 31 | int amount = 0; 32 | for (int i = 0; i < insnList.size(); i++) { 33 | if (insnList.get(i) instanceof FieldInsnNode fieldInsnNode) { 34 | String owner = field.getDeclaringClass().getName().replace(".", "/"); 35 | String name = field.getName(); 36 | String desc = ASMUtils.toDescriptorTypeName(field.getType().getName()); 37 | if (fieldInsnNode.owner.equals(owner) && fieldInsnNode.name.equals(name) && fieldInsnNode.desc.equals(desc) && filter.test(amount++)) { 38 | if (fieldInsnNode.getOpcode() == Opcode.PUTFIELD || fieldInsnNode.getOpcode() == Opcode.PUTSTATIC) 39 | out.add(i + (state == PostPreState.POST ? 1 : 0)); 40 | } 41 | } 42 | } 43 | return out; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorHead.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import org.objectweb.asm.tree.InsnList; 5 | 6 | import java.util.List; 7 | 8 | public class HLocatorHead implements HookLocator { 9 | @Override 10 | public List getLineNumber(InsnList insnNodes) { 11 | return List.of(0); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorMethodInvoke.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 5 | import com.dragoncommissions.mixbukkit.utils.PostPreState; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.objectweb.asm.tree.InsnList; 8 | import org.objectweb.asm.tree.MethodInsnNode; 9 | 10 | import java.lang.reflect.Method; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.function.Predicate; 14 | 15 | public class HLocatorMethodInvoke implements HookLocator { 16 | 17 | private final String owner; 18 | private final String desc; 19 | private final String name; 20 | private final PostPreState state; 21 | private final Predicate filter; 22 | 23 | public HLocatorMethodInvoke(@NotNull Method method, PostPreState state, Predicate filter) { 24 | owner = method.getDeclaringClass().getName().replace(".", "/"); 25 | desc = ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes()); 26 | name = method.getName(); 27 | this.filter = filter; 28 | this.state = state; 29 | } 30 | 31 | public HLocatorMethodInvoke(@NotNull Class owner, @NotNull Method method, PostPreState state, Predicate filter) { 32 | this.owner = owner.getName().replace(".", "/"); 33 | desc = ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes()); 34 | name = method.getName(); 35 | this.filter = filter; 36 | this.state = state; 37 | } 38 | 39 | @Override 40 | public List getLineNumber(@NotNull InsnList insnList) { 41 | int amount = 0; 42 | List out = new ArrayList<>(); 43 | for (int i = 0; i < insnList.size(); i++) { 44 | if (insnList.get(i) instanceof MethodInsnNode insnNode) { 45 | if (insnNode.owner.equals(owner) && insnNode.name.equals(name) && insnNode.desc.equals(desc) && filter.test(amount++)) { 46 | out.add(i + (state == PostPreState.POST ? 1 : 0)); 47 | } 48 | } 49 | } 50 | return out; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/locator/impl/HLocatorReturn.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.locator.impl; 2 | 3 | import com.dragoncommissions.mixbukkit.api.locator.HookLocator; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.objectweb.asm.tree.AbstractInsnNode; 6 | import org.objectweb.asm.tree.InsnList; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class HLocatorReturn implements HookLocator { 12 | @Override 13 | public List getLineNumber(@NotNull InsnList insnList) { 14 | List list = new ArrayList<>(); 15 | for (int i = 0; i < insnList.size(); i++) { 16 | AbstractInsnNode insnNode = insnList.get(i); 17 | if (insnNode.getOpcode() >= 172 && insnNode.getOpcode() <= 177) { 18 | list.add(i); 19 | } 20 | } 21 | return list; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/IShellCode.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode; 2 | 3 | import org.objectweb.asm.tree.InsnList; 4 | import org.objectweb.asm.tree.MethodNode; 5 | 6 | public interface IShellCode { 7 | 8 | InsnList generate(MethodNode methodNode, LocalVarManager varManager); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/LocalVarManager.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode; 2 | 3 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 4 | import lombok.Getter; 5 | import org.objectweb.asm.tree.MethodNode; 6 | 7 | public class LocalVarManager { 8 | 9 | @Getter 10 | private final MethodNode methodNode; 11 | 12 | private int latestVarNumber; 13 | 14 | public LocalVarManager(MethodNode methodNode) { 15 | this.methodNode = methodNode; 16 | latestVarNumber = ASMUtils.getLatestVarNumber(methodNode.instructions) + 1; 17 | } 18 | 19 | public int allocateVarNumber() { 20 | return latestVarNumber++; 21 | } 22 | 23 | public int getLatestUnusedVarNumber() { 24 | return latestVarNumber; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/ShellCode.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode; 2 | 3 | import org.objectweb.asm.Opcodes; 4 | import org.objectweb.asm.tree.InsnList; 5 | import org.objectweb.asm.tree.InsnNode; 6 | 7 | public abstract class ShellCode implements IShellCode { 8 | 9 | public ShellCode() { 10 | if (getShellCodeInfo() == null) 11 | throw new NullPointerException("Shellcode: " + this.getClass() 12 | .getName() + " is invalid! @ShellCodeInfo annotation is not presented."); 13 | } 14 | 15 | public InsnList generate() { 16 | return generate(null, null); 17 | } 18 | 19 | public InsnList popExtraStack() { 20 | InsnList list = new InsnList(); 21 | for (int i = 0; i < getShellCodeInfo().stacksContent().length; i++) { 22 | list.add(new InsnNode(Opcodes.POP)); 23 | } 24 | return list; 25 | } 26 | 27 | public ShellCodeInfo getShellCodeInfo() { 28 | try { 29 | return getClass().getAnnotationsByType(ShellCodeInfo.class)[0]; 30 | } catch (Exception ignored) { 31 | return null; 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/ShellCodeInfo.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Target(ElementType.TYPE) 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ShellCodeInfo { 11 | 12 | /** 13 | * @return Official name of the shellcode 14 | */ 15 | String name(); 16 | 17 | /** 18 | * @return Description of the shellcode, like what it will do etc. 19 | */ 20 | String description(); 21 | 22 | /** 23 | * @return Returns true if it's going to modify the MethodNode. For example: Add a try & catch block 24 | */ 25 | boolean requireMethodNodeModification() default false; 26 | 27 | /** 28 | * @return Returns true if it will need to allocate new variables 29 | */ 30 | boolean requireVarManager() default false; 31 | 32 | /** 33 | * @return Content of stack, first one pushed into stack should be in the bottom of return value 34 | */ 35 | String[] stacksContent() default {}; 36 | 37 | /** 38 | * @return What's required on the stack 39 | */ 40 | String[] requiredStacksContent() default {}; 41 | 42 | /** 43 | * @return Is it safe to call it directly inside a method 44 | */ 45 | boolean calledDirectly() default false; 46 | 47 | /** 48 | * @return Is it required to use -noverify in order to load the shellcode 49 | */ 50 | boolean failsClassVerification() default true; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/CallbackInfo.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeLoadClassFromPCL; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeMethodInvoke; 6 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke; 7 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 8 | import javassist.bytecode.Opcode; 9 | import lombok.Getter; 10 | import lombok.Setter; 11 | import lombok.SneakyThrows; 12 | import org.objectweb.asm.tree.*; 13 | 14 | public class CallbackInfo { 15 | 16 | @Getter 17 | @Setter 18 | private Object returnValue = null; 19 | 20 | @Getter 21 | @Setter 22 | private boolean returned; 23 | 24 | @SneakyThrows 25 | public static InsnList generateCallBackInfo() { 26 | InsnList out = new InsnList(); 27 | out.add(new IShellCodeLoadClassFromPCL(CallbackInfo.class).generate()); 28 | try { 29 | out.add(new IShellCodeMethodInvoke(Class.class.getDeclaredMethod("newInstance")).generate()); 30 | } catch (NoSuchMethodException e) { 31 | throw new RuntimeException(e); 32 | } 33 | return out; 34 | } 35 | 36 | @SneakyThrows 37 | public static InsnList processCallBackInfo(MethodNode hookedMethod, LocalVarManager varManager, int varLocation) { 38 | InsnList out = new InsnList(); 39 | LabelNode returnBranch = new LabelNode(); 40 | LabelNode defaultBranch = new LabelNode(); 41 | 42 | out.add(new VarInsnNode(Opcode.ALOAD, varLocation)); 43 | try { 44 | out.add(new IShellCodeReflectionMethodInvoke(CallbackInfo.class.getDeclaredMethod("isReturned")).generate(null, varManager)); 45 | } catch (NoSuchMethodException e) { 46 | throw new RuntimeException(e); 47 | } 48 | out.add(new TypeInsnNode(Opcode.CHECKCAST, Boolean.class.getName().replace(".", "/"))); 49 | try { 50 | out.add(new IShellCodeMethodInvoke(Boolean.class.getDeclaredMethod("booleanValue")).generate()); 51 | } catch (NoSuchMethodException e) { 52 | throw new RuntimeException(e); 53 | } 54 | out.add(new JumpInsnNode(Opcode.IFEQ, defaultBranch)); 55 | out.add(returnBranch); 56 | out.add(new VarInsnNode(Opcode.ALOAD, varLocation)); 57 | try { 58 | out.add(new IShellCodeReflectionMethodInvoke(CallbackInfo.class.getDeclaredMethod("getReturnValue")).generate(null, varManager)); 59 | } catch (NoSuchMethodException e) { 60 | throw new RuntimeException(e); 61 | } 62 | if (!hookedMethod.desc.endsWith("V")) { 63 | Class returnType = ASMUtils.getReturnType(hookedMethod.desc); 64 | out.add(ASMUtils.cast(returnType)); 65 | out.add(ASMUtils.genReturnNode(returnType)); 66 | } else { 67 | out.add(new InsnNode(Opcode.RETURN)); 68 | } 69 | 70 | out.add(defaultBranch); 71 | return out; 72 | } 73 | 74 | public boolean isReturned() { 75 | return returned; 76 | } 77 | 78 | public void setReturned(boolean returned) { 79 | this.returned = returned; 80 | } 81 | 82 | public Object getReturnValue() { 83 | return returnValue; 84 | } 85 | 86 | public void setReturnValue(Object returnValue) { 87 | this.returnValue = returnValue; 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodeComment.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import lombok.NonNull; 9 | import lombok.RequiredArgsConstructor; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.InsnNode; 12 | import org.objectweb.asm.tree.LdcInsnNode; 13 | import org.objectweb.asm.tree.MethodNode; 14 | 15 | @ShellCodeInfo( 16 | name = "Comment", 17 | description = "Leave a comment, and it won't do anything other than wasting performance", 18 | calledDirectly = true 19 | ) 20 | @RequiredArgsConstructor 21 | public class ShellCodeComment extends ShellCode { 22 | 23 | @NonNull 24 | private String comment; 25 | 26 | @Override 27 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 28 | return ASMUtils.asInsnList( 29 | new LdcInsnNode(comment), 30 | new InsnNode(Opcode.POP) 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodePrintMessage.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import org.objectweb.asm.tree.*; 9 | 10 | import static javassist.bytecode.Opcode.GETSTATIC; 11 | import static javassist.bytecode.Opcode.INVOKEVIRTUAL; 12 | 13 | @AllArgsConstructor 14 | @Getter 15 | @ShellCodeInfo( 16 | name = "stdout Print Message", 17 | description = "Call a System.out.println", 18 | requireVarManager = false, 19 | stacksContent = {}, 20 | requiredStacksContent = {}, 21 | calledDirectly = true 22 | ) 23 | public class ShellCodePrintMessage extends ShellCode { 24 | 25 | private String message; 26 | 27 | 28 | @Override 29 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 30 | InsnList list = new InsnList(); 31 | list.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); 32 | list.add(new LdcInsnNode(message)); 33 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V")); 34 | return list; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodePrintTopStackType.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import org.objectweb.asm.tree.*; 9 | 10 | import static javassist.bytecode.Opcode.GETSTATIC; 11 | import static javassist.bytecode.Opcode.INVOKEVIRTUAL; 12 | 13 | @AllArgsConstructor 14 | @Getter 15 | @ShellCodeInfo( 16 | name = "Print Type", 17 | description = "System.out the full class name of the content that's on the top of the stack", 18 | requireVarManager = false, 19 | stacksContent = {}, 20 | requiredStacksContent = {}, 21 | calledDirectly = true 22 | ) 23 | public class ShellCodePrintTopStackType extends ShellCode { 24 | 25 | private String message; 26 | 27 | 28 | @Override 29 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 30 | InsnList list = new InsnList(); 31 | list.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); 32 | list.add(new LdcInsnNode(message)); 33 | list.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V")); 34 | return list; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/api/ShellCodeReflectionMixinPluginMethodCall.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.api; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.inner.IShellCodeReflectionMethodInvoke; 7 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 8 | import lombok.AllArgsConstructor; 9 | import lombok.SneakyThrows; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.MethodNode; 12 | 13 | import java.lang.reflect.Method; 14 | 15 | @ShellCodeInfo( 16 | name = "Reflection Mixin Plugin Method Call", 17 | description = "It will use a URLClassLoader of a random plugin to load the target plugin class and invoke the method. " + 18 | "A regular method invoke doesn't work since it uses PluginClassLoader.", 19 | requireVarManager = true, 20 | requireMethodNodeModification = true, 21 | stacksContent = {"Return value of invoked method (as Object)"}, 22 | requiredStacksContent = {}, 23 | calledDirectly = true 24 | ) 25 | @AllArgsConstructor 26 | public class ShellCodeReflectionMixinPluginMethodCall extends ShellCode { 27 | 28 | private final Method handler; 29 | 30 | public ShellCodeReflectionMixinPluginMethodCall(Method handler) { 31 | this.handler = handler; 32 | } 33 | 34 | @Override 35 | @SneakyThrows 36 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 37 | InsnList out = new InsnList(); 38 | Class[] parameterTypes = handler.getParameterTypes(); 39 | boolean hasCallBackInfo = false; 40 | for (int i = 0; i < parameterTypes.length; i++) { 41 | Class parameterType = parameterTypes[i]; 42 | if (CallbackInfo.class.isAssignableFrom(parameterType) && i == parameterTypes.length - 1) { 43 | out.add(CallbackInfo.generateCallBackInfo()); 44 | hasCallBackInfo = true; 45 | } else { 46 | out.add(ASMUtils.castToObject(i, parameterType)); 47 | } 48 | } 49 | IShellCodeReflectionMethodInvoke shellCodeReflectionMethodInvoke = new IShellCodeReflectionMethodInvoke(handler); 50 | out.add(shellCodeReflectionMethodInvoke.generate(methodNode, varManager)); 51 | if (hasCallBackInfo) { 52 | Integer varLocation = shellCodeReflectionMethodInvoke.getArgumentVarIndex().get(parameterTypes.length - 1); 53 | out.add(CallbackInfo.processCallBackInfo(methodNode, varManager, varLocation)); 54 | } 55 | return out; 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeMethodInvoke.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.MethodInsnNode; 12 | import org.objectweb.asm.tree.MethodNode; 13 | 14 | import java.lang.reflect.Method; 15 | import java.lang.reflect.Modifier; 16 | 17 | @ShellCodeInfo( 18 | name = "Method Invoke", 19 | description = "Call methods programmatically", 20 | stacksContent = {"Return value of invoked method"}, 21 | requiredStacksContent = {"Object that calls the method", "Arguments (in order)"} 22 | ) 23 | @AllArgsConstructor 24 | @Getter 25 | public class IShellCodeMethodInvoke extends ShellCode { 26 | 27 | private final Method method; 28 | 29 | public IShellCodeMethodInvoke(Method method) { 30 | this.method = method; 31 | } 32 | 33 | @Override 34 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 35 | InsnList list = new InsnList(); 36 | list.add(new MethodInsnNode( 37 | Modifier.isStatic(method.getModifiers()) ? Opcode.INVOKESTATIC : (method.getDeclaringClass() 38 | .isInterface() ? Opcode.INVOKEINTERFACE : Opcode.INVOKEVIRTUAL), 39 | method.getDeclaringClass().getName().replace(".", "/"), 40 | method.getName(), 41 | ASMUtils.getDescriptor(method.getReturnType(), method.getParameterTypes()), 42 | method.getDeclaringClass().isInterface() && !Modifier.isStatic(method.getModifiers()) 43 | )); 44 | return list; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeNewArrayAndAddContent.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import org.objectweb.asm.tree.InsnList; 11 | import org.objectweb.asm.tree.InsnNode; 12 | import org.objectweb.asm.tree.MethodNode; 13 | import org.objectweb.asm.tree.TypeInsnNode; 14 | 15 | @ShellCodeInfo( 16 | name = "New Array and Add Content", 17 | description = "Create a new array, set the content of it (by for looping), and pushes the array to stack", 18 | requireVarManager = false, 19 | stacksContent = {"The created array"}, 20 | requiredStacksContent = {}, 21 | calledDirectly = false 22 | ) 23 | @AllArgsConstructor 24 | @Getter 25 | public class IShellCodeNewArrayAndAddContent extends ShellCode { 26 | 27 | private final int arraySize; 28 | private final Class type; 29 | private final ForLoopInstructionGenerator gen; 30 | 31 | public IShellCodeNewArrayAndAddContent(int arraySize, Class type, ForLoopInstructionGenerator gen) { 32 | this.arraySize = arraySize; 33 | this.type = type; 34 | this.gen = gen; 35 | } 36 | 37 | @Override 38 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 39 | InsnList out = new InsnList(); 40 | out.add(ASMUtils.pushInt(arraySize)); 41 | out.add(new TypeInsnNode(Opcode.ANEWARRAY, type.getName().replace(".", "/"))); 42 | for (int i = 0; i < arraySize; i++) { 43 | out.add(new InsnNode(Opcode.DUP)); 44 | out.add(ASMUtils.pushInt(i)); 45 | out.add(gen.generate(i)); 46 | out.add(new InsnNode(Opcode.AASTORE)); 47 | } 48 | return out; 49 | } 50 | 51 | public interface ForLoopInstructionGenerator { 52 | InsnList generate(int index); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodePushInt.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import javassist.bytecode.Opcode; 7 | import lombok.AllArgsConstructor; 8 | import lombok.Getter; 9 | import org.objectweb.asm.tree.InsnList; 10 | import org.objectweb.asm.tree.InsnNode; 11 | import org.objectweb.asm.tree.IntInsnNode; 12 | import org.objectweb.asm.tree.MethodNode; 13 | 14 | @ShellCodeInfo( 15 | name = "Push Int", 16 | description = "Push an int into stack" 17 | ) 18 | @AllArgsConstructor 19 | @Getter 20 | public class IShellCodePushInt extends ShellCode { 21 | 22 | private final int value; 23 | 24 | public IShellCodePushInt(int value) { 25 | this.value = value; 26 | } 27 | 28 | @Override 29 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 30 | InsnList out = new InsnList(); 31 | if (value <= 5 && value >= -1) { 32 | out.add(new InsnNode(value + 3)); 33 | return out; 34 | } 35 | if (value < 255 & value > 0) { 36 | out.add(new IntInsnNode(Opcode.BIPUSH, value)); 37 | return out; 38 | } 39 | out.add(new IntInsnNode(Opcode.SIPUSH, value)); 40 | return out; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/api/shellcode/impl/inner/IShellCodeReflectionMethodInvoke.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.api.shellcode.impl.inner; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.LocalVarManager; 4 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCode; 5 | import com.dragoncommissions.mixbukkit.api.shellcode.ShellCodeInfo; 6 | import com.dragoncommissions.mixbukkit.utils.ASMUtils; 7 | import javassist.bytecode.Opcode; 8 | import lombok.Getter; 9 | import lombok.SneakyThrows; 10 | import org.bukkit.Bukkit; 11 | import org.objectweb.asm.tree.*; 12 | 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.lang.reflect.Method; 15 | import java.lang.reflect.Modifier; 16 | import java.util.ArrayList; 17 | import java.util.Collections; 18 | import java.util.List; 19 | 20 | @ShellCodeInfo( 21 | name = "Reflection Method Invoke", 22 | description = "Invoke a method with similar usage as ShellCodeMethodInvoke", 23 | stacksContent = {"Return value of invoked method"}, 24 | requiredStacksContent = {"Object that calls the method", "Arguments (in order)"} 25 | ) 26 | @Getter 27 | public class IShellCodeReflectionMethodInvoke extends ShellCode { 28 | 29 | private final Method method; 30 | private List argumentVarIndex = new ArrayList<>(); 31 | 32 | public IShellCodeReflectionMethodInvoke(Method method) { 33 | this.method = method; 34 | } 35 | 36 | @SneakyThrows 37 | private static void action() { 38 | try { 39 | Class.forName("CLASS_NAME_HERE", true, Bukkit.getPluginManager().getPlugins()[0].getClass() 40 | .getClassLoader()) 41 | .getDeclaredMethod("methodNameHere", int.class, float.class, double.class, char.class, boolean.class, byte.class, short.class, String.class, String[].class, int[].class) 42 | .invoke(null); 43 | } catch (IllegalAccessException e) { 44 | throw new RuntimeException(e); 45 | } catch (InvocationTargetException e) { 46 | throw new RuntimeException(e); 47 | } catch (NoSuchMethodException e) { 48 | throw new RuntimeException(e); 49 | } catch (ClassNotFoundException e) { 50 | throw new RuntimeException(e); 51 | } 52 | } 53 | 54 | public List getArgumentVarIndex() { 55 | return argumentVarIndex; 56 | } 57 | 58 | public Method getMethod() { 59 | return method; 60 | } 61 | 62 | @Override 63 | @SneakyThrows 64 | public InsnList generate(MethodNode methodNode, LocalVarManager varManager) { 65 | InsnList out = new InsnList(); 66 | argumentVarIndex = new ArrayList<>(); 67 | int obj = -1; 68 | if (!Modifier.isStatic(method.getModifiers())) { 69 | obj = varManager.allocateVarNumber(); 70 | out.add(new VarInsnNode(Opcode.ASTORE, obj)); 71 | } 72 | int length = method.getParameterTypes().length; 73 | for (int i = 0; i < length; i++) { 74 | int arg = varManager.allocateVarNumber(); 75 | argumentVarIndex.add(arg); 76 | out.add(new VarInsnNode(Opcode.ASTORE, arg)); 77 | } 78 | Collections.reverse(argumentVarIndex); 79 | 80 | out.add(new IShellCodeLoadClassFromPCL(method.getDeclaringClass()).generate()); 81 | out.add(new LdcInsnNode(method.getName())); 82 | out.add(new IShellCodeNewArrayAndAddContent(method.getParameterTypes().length, Class.class, index -> { 83 | InsnList list = new InsnList(); 84 | list.add(ASMUtils.generateGetClassNode(method.getParameterTypes()[index])); 85 | return list; 86 | }).generate()); 87 | try { 88 | out.add(new IShellCodeMethodInvoke(Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class)).generate()); 89 | } catch (NoSuchMethodException e) { 90 | throw new RuntimeException(e); 91 | } 92 | if (obj == -1) { 93 | out.add(new InsnNode(Opcode.ACONST_NULL)); 94 | } else { 95 | out.add(new VarInsnNode(Opcode.ALOAD, obj)); 96 | } 97 | out.add(new IShellCodeNewArrayAndAddContent(argumentVarIndex.size(), Object.class, index -> { 98 | InsnList list = new InsnList(); 99 | try { 100 | list.add(new VarInsnNode(Opcode.ALOAD, argumentVarIndex.get(index))); 101 | } catch (Exception ignored) { 102 | } 103 | return list; 104 | }).generate()); 105 | try { 106 | out.add(new IShellCodeMethodInvoke(Method.class.getDeclaredMethod("invoke", Object.class, Object[].class)).generate()); 107 | } catch (NoSuchMethodException e) { 108 | throw new RuntimeException(e); 109 | } 110 | 111 | return out; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/utils/PostPreState.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.utils; 2 | 3 | public enum PostPreState { 4 | 5 | PRE, 6 | POST 7 | 8 | } 9 | -------------------------------------------------------------------------------- /injection/src/main/java/com/dragoncommissions/mixbukkit/utils/io/BukkitErrorOutputStream.java: -------------------------------------------------------------------------------- 1 | package com.dragoncommissions.mixbukkit.utils.io; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | public class BukkitErrorOutputStream extends OutputStream { 7 | 8 | 9 | public BukkitErrorOutputStream() { 10 | 11 | } 12 | 13 | @Override 14 | public void write(int b) throws IOException { 15 | if (b == '\n') { 16 | System.err.print("\u001B[0m"); 17 | } 18 | System.err.write(b); 19 | if (b == '\n') { 20 | System.err.print("\u001B[31m"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /injection/src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Agent-Class: com.dragoncommissions.mixbukkit.agent.AgentMain 3 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk21 -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Dueris/Eclipse/9134d4833073c9454ec9a08e04572e13cd31c819/logo.png -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.util.* 2 | 3 | "eclipse".also { rootProject.name = it } 4 | 5 | pluginManagement { 6 | includeBuild("build-logic") 7 | repositories { 8 | gradlePluginPortal() 9 | maven("https://repo.papermc.io/repository/maven-public/") 10 | } 11 | } 12 | 13 | plugins { 14 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 15 | } 16 | 17 | for (name in listOf("injection", "example", "console")) { 18 | val projName = name.lowercase(Locale.ENGLISH) 19 | include(projName) 20 | findProject(":$projName")!!.projectDir = file(name) 21 | } -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/Agent.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api; 2 | 3 | import java.lang.instrument.ClassFileTransformer; 4 | import java.nio.file.Path; 5 | 6 | public interface Agent { 7 | /** 8 | * Adds the given {@link Path} to the system classpath 9 | * 10 | * @param path the {@link Path} to register 11 | */ 12 | void appendToClasspath(Path path); 13 | 14 | /** 15 | * Registers a new {@link ClassFileTransformer} to the system classpath 16 | * 17 | * @param fileTransformer the {@link ClassFileTransformer} to register 18 | */ 19 | void registerClassFileTransformer(ClassFileTransformer fileTransformer); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/GameLibrary.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.nio.file.Path; 6 | 7 | public record GameLibrary(Path libraryPath, String libraryString, boolean trace) { 8 | 9 | @Override 10 | public @NotNull String toString() { 11 | return "GameLibrary[" + 12 | "libraryPath=" + libraryPath + ", " + 13 | "libraryString=" + libraryString + ']'; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/Launcher.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api; 2 | 3 | import io.github.dueris.eclipse.api.mod.ModEngine; 4 | import io.github.dueris.eclipse.api.util.BootstrapEntryContext; 5 | import io.github.dueris.eclipse.loader.EclipseLauncher; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.Map; 9 | 10 | public interface Launcher { 11 | static @NotNull Launcher getInstance() { 12 | Launcher ret = EclipseLauncher.INSTANCE; 13 | 14 | if (ret == null) { 15 | throw new RuntimeException("Accessed EclipseLoader too early!"); 16 | } 17 | 18 | return ret; 19 | } 20 | 21 | /** 22 | * Retrieves the mod engine for Eclipse, which contains methods for 23 | * managing and retrieving mod instances and metadata. 24 | * 25 | * @return The mod engine for Eclipse. 26 | */ 27 | ModEngine modEngine(); 28 | 29 | /** 30 | * Returns the API implementation of the Java Instrument API, allowing 31 | * you to append files to the system classpath, and register class file transformers 32 | * 33 | * @return The Eclipse JVM Agent 34 | */ 35 | Agent agent(); 36 | 37 | /** 38 | * Returns the decompiled context of the launch from the original process. 39 | * 40 | * @return The {@link BootstrapEntryContext} decompiled at launch 41 | */ 42 | BootstrapEntryContext entryContext(); 43 | 44 | /** 45 | * Retrieves the properties saved and used by the Eclipse process 46 | * 47 | * @return the Eclipse property data 48 | */ 49 | Map getProperties(); 50 | 51 | /** 52 | * Retrieves the transformer, which is in charge of managing class 53 | * transformation at runtime. 54 | * 55 | * @return the game transformer 56 | */ 57 | Transformer emberTransformer(); 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/McVersion.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | public record McVersion(String id, String name, int worldVersion, String seriesId, int protocolVersion, 6 | McVersion.PackVersion packVersion, String buildTime, String javaComponent, int javaVersion, 7 | boolean stable, boolean useEditor) { 8 | 9 | @Override 10 | public @NotNull String toString() { 11 | return "McVersion{" + 12 | "id='" + id + '\'' + 13 | ", name='" + name + '\'' + 14 | ", worldVersion=" + worldVersion + 15 | ", seriesId='" + seriesId + '\'' + 16 | ", protocolVersion=" + protocolVersion + 17 | ", packVersion=" + packVersion + 18 | ", buildTime='" + buildTime + '\'' + 19 | ", javaComponent='" + javaComponent + '\'' + 20 | ", javaVersion=" + javaVersion + 21 | ", stable=" + stable + 22 | ", useEditor=" + useEditor + 23 | '}'; 24 | } 25 | 26 | public record PackVersion(int resource, int data) { 27 | @Override 28 | public @NotNull String toString() { 29 | return "PackVersion{" + 30 | "resource=" + resource + 31 | ", data=" + data + 32 | '}'; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/Transformer.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api; 2 | 3 | import io.github.dueris.eclipse.loader.ember.patch.GamePatch; 4 | import io.github.dueris.eclipse.loader.ember.patch.TransformerService; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | public interface Transformer { 9 | /** 10 | * Gets the TransformerService in the registry from the class. Will return null 11 | * if not registered. 12 | * 13 | * @param The TransformerService instance 14 | * @param transformer the class of the TransformerService 15 | * @return The TransformerService found in the registry 16 | */ 17 | @Nullable T getTransformer(final @NotNull Class transformer); 18 | 19 | /** 20 | * Gets the GamePatch in the registry from its class. Will return null if not registered. 21 | * 22 | * @param The GamePatch instance 23 | * @param patch the class of the GamePatch 24 | * @return The GamePatch found in the registry 25 | */ 26 | @Nullable T getPatch(final @NotNull Class patch); 27 | 28 | /** 29 | * Registers a GamePatch to the classpath transformer 30 | * 31 | * @param patch the GamePatch to register 32 | */ 33 | void patch(GamePatch patch); 34 | 35 | /** 36 | * Registers a TransformerService to the classpath transformer 37 | * 38 | * @param service the TransformerService to register 39 | */ 40 | void transformer(TransformerService service); 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/entrypoint/BootstrapInitializer.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.entrypoint; 2 | 3 | import io.github.dueris.eclipse.api.Launcher; 4 | import io.github.dueris.eclipse.api.mod.ModContainer; 5 | import io.github.dueris.eclipse.api.mod.ModResource; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.nio.file.Path; 9 | import java.util.Objects; 10 | 11 | public interface BootstrapInitializer { 12 | static void enter() { 13 | EntrypointInstance entrypoint = EntrypointContainer.getEntrypoint(BootstrapInitializer.class); 14 | for (ModResource mod : entrypoint.getRegisteredEntrypoints()) { 15 | Launcher launcher = Launcher.getInstance(); 16 | BootstrapContext context = new BootstrapContext() { 17 | @Override 18 | public @NotNull Path getModSource() { 19 | return mod.path().toAbsolutePath().normalize(); 20 | } 21 | 22 | @Override 23 | public @NotNull Path getDataDirectory() { 24 | return ((Path) launcher.getProperties().get("modspath")).resolve( 25 | Objects.requireNonNull(launcher.modEngine().getContainerFromResource(mod), "Container couldnt be resolved from resource!").config().backend().getString("name") 26 | ).toAbsolutePath().normalize(); 27 | } 28 | 29 | @Override 30 | public @NotNull Launcher getLauncher() { 31 | return launcher; 32 | } 33 | 34 | @Override 35 | public ModContainer getModContainer() { 36 | return launcher.modEngine().getContainerFromResource(mod); 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "BootstrapInitializer: " + 42 | getModContainer().toString() + "{,}" + 43 | getDataDirectory() + "{,}" + 44 | getModSource(); 45 | } 46 | }; 47 | entrypoint.enterSpecific(mod, context); 48 | } 49 | } 50 | 51 | /** 52 | * Entrypoint directly before paper plugins bootstrap. 53 | */ 54 | void onInitializeBootstrap(BootstrapContext context); 55 | 56 | interface BootstrapContext { 57 | Path getModSource(); 58 | 59 | Path getDataDirectory(); 60 | 61 | Launcher getLauncher(); 62 | 63 | ModContainer getModContainer(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/entrypoint/EntrypointContainer.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.entrypoint; 2 | 3 | import io.github.dueris.eclipse.api.Launcher; 4 | import io.github.dueris.eclipse.api.mod.ModContainer; 5 | import io.github.dueris.eclipse.api.mod.ModMetadata; 6 | import io.github.dueris.eclipse.api.mod.ModResource; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import javax.management.InstanceNotFoundException; 10 | import java.util.List; 11 | import java.util.concurrent.CopyOnWriteArrayList; 12 | 13 | public class EntrypointContainer { 14 | private static final List entrypoints = new CopyOnWriteArrayList<>(); 15 | 16 | /** 17 | * Registers a new entrypoint instance with the given details. 18 | * 19 | * @param id The unique identifier for the entrypoint. 20 | * @param methodName The name of the method to invoke in the entrypoint. 21 | * @param instanceToInvoke The class of the instance to invoke the method on. 22 | * @param argumentTypes The types of the arguments for the method to invoke. 23 | * @return The created and registered {@link EntrypointInstance}. 24 | */ 25 | public static @NotNull EntrypointInstance register(String id, String methodName, Class instanceToInvoke, Class... argumentTypes) { 26 | EntrypointInstance instance = new EntrypointInstance<>(id, methodName, instanceToInvoke, argumentTypes); 27 | entrypoints.add(instance); 28 | for (ModContainer modContainer : Launcher.getInstance().modEngine().containers()) { 29 | ModMetadata metadata = modContainer.config(); 30 | ModResource resource = modContainer.resource(); 31 | instance.prepare(); 32 | instance.buildEntrypoints(metadata.backend().contains("entrypoints") ? metadata.backend() 33 | .getConfigurationSection("entrypoints") : null, resource); 34 | } 35 | return instance; 36 | } 37 | 38 | /** 39 | * Retrieves an entrypoint by its unique identifier. 40 | * 41 | * @param id The unique identifier of the entrypoint. 42 | * @return The {@link EntrypointInstance} corresponding to the provided ID. 43 | * @throws RuntimeException If no entrypoint with the specified ID is found. 44 | */ 45 | public static EntrypointInstance getEntrypoint(String id) { 46 | return entrypoints.stream().filter(i -> i.getId().equalsIgnoreCase(id)).findFirst() 47 | .orElseThrow(() -> new RuntimeException("No entrypoint with that id was found!", new InstanceNotFoundException())); 48 | } 49 | 50 | /** 51 | * Retrieves an entrypoint by its associated class type. 52 | * 53 | * @param classType The class type of the entrypoint instance. 54 | * @return The {@link EntrypointInstance} corresponding to the provided class type. 55 | * @throws RuntimeException If no entrypoint with the specified class type is found. 56 | */ 57 | public static EntrypointInstance getEntrypoint(Class classType) { 58 | return entrypoints.stream().filter(i -> i.instanceClass.equals(classType)).findFirst() 59 | .orElseThrow(() -> new RuntimeException("No entrypoint with that class type was found!", new InstanceNotFoundException())); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/entrypoint/EntrypointInstance.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.entrypoint; 2 | 3 | import io.github.dueris.eclipse.api.Launcher; 4 | import io.github.dueris.eclipse.api.mod.ModResource; 5 | import io.github.dueris.eclipse.loader.ember.EmberClassLoader; 6 | import org.jetbrains.annotations.*; 7 | import org.simpleyaml.configuration.ConfigurationSection; 8 | 9 | import java.lang.instrument.IllegalClassFormatException; 10 | import java.lang.reflect.Method; 11 | import java.util.Collections; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | public class EntrypointInstance { 17 | protected final Class instanceClass; 18 | private final String id; 19 | private final String methodName; 20 | private final Class[] argumentTypes; 21 | private final Map> registeredEntrypoints = new HashMap<>(); 22 | 23 | EntrypointInstance(String id, String methodName, Class interfaceClass, Class... argumentTypes) { 24 | this.id = id; 25 | this.methodName = methodName; 26 | this.instanceClass = verify(interfaceClass); 27 | this.argumentTypes = argumentTypes; 28 | } 29 | 30 | /** 31 | * Verifies that the provided class is valid. 32 | * 33 | * @param toVerify The class to verify. 34 | * @return The verified class. 35 | * @throws RuntimeException If the class is invalid. 36 | */ 37 | @Contract("_ -> param1") 38 | private @NotNull Class verify(@NotNull Class toVerify) { 39 | if (!toVerify.isInterface()) { 40 | throw new RuntimeException("Class instance is not an interface! " + toVerify.getName(), new IllegalClassFormatException()); 41 | } 42 | return toVerify; 43 | } 44 | 45 | /** 46 | * Enters the entrypoint for each registered mod resource by invoking the method 47 | * with the provided arguments. 48 | * 49 | * @param arguments The arguments to pass to the entrypoint method. 50 | * @throws RuntimeException If any error occurs during method invocation. 51 | */ 52 | public void enter(Object... arguments) { 53 | for (ModResource mod : this.registeredEntrypoints.keySet()) { 54 | enterSpecific(mod, arguments); 55 | } 56 | } 57 | 58 | @ApiStatus.Internal 59 | void enterSpecific(@NotNull final ModResource mod, Object... arguments) { 60 | Class entrypointClass = this.registeredEntrypoints.get(mod); 61 | try { 62 | Method method = entrypointClass.getDeclaredMethod(methodName, argumentTypes); 63 | method.setAccessible(true); 64 | T entrypointInstance = entrypointClass.getConstructor().newInstance(); 65 | method.invoke(entrypointInstance, arguments); 66 | } catch (Throwable throwable) { 67 | throw new RuntimeException("Unable to enter mod, " + Launcher.getInstance().modEngine() 68 | .getContainerFromResource(mod) + " !", throwable); 69 | } 70 | } 71 | 72 | /** 73 | * Retrieves the unique identifier for this entrypoint. 74 | * 75 | * @return The unique ID of this entrypoint. 76 | */ 77 | public String getId() { 78 | return id; 79 | } 80 | 81 | /** 82 | * Builds the entrypoints from the provided configuration section and mod resource. 83 | * It associates the entrypoints with their corresponding mod resources. 84 | * 85 | * @param entrypointContainer The configuration section containing the entrypoint definitions. 86 | * @param resource The mod resource associated with the entrypoint. 87 | * @throws RuntimeException If any error occurs while resolving or validating entrypoints. 88 | */ 89 | @SuppressWarnings("unchecked") 90 | void buildEntrypoints(@Nullable ConfigurationSection entrypointContainer, @NotNull ModResource resource) { 91 | if (entrypointContainer != null) { 92 | if (entrypointContainer.contains(id)) { 93 | String entryName = entrypointContainer.getString(id); 94 | try { 95 | Class clazz = Class.forName(entryName, true, EmberClassLoader.INSTANCE); 96 | if (instanceClass.isAssignableFrom(clazz)) { 97 | registeredEntrypoints.put(resource, (Class) clazz); 98 | } else { 99 | throw new RuntimeException("Class in mod entrypoint container is not an implementation of the required interface!"); 100 | } 101 | } catch (ClassNotFoundException e) { 102 | throw new RuntimeException("Unable to find class: " + entryName, e); 103 | } 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * Prepares the entrypoint for building 110 | */ 111 | void prepare() { 112 | registeredEntrypoints.clear(); 113 | } 114 | 115 | /** 116 | * Returns the registered entrypoints of the entrypoint. Unmodifiable 117 | */ 118 | public @Unmodifiable Set getRegisteredEntrypoints() { 119 | return Collections.unmodifiableSet(this.registeredEntrypoints.keySet()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/entrypoint/ModInitializer.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.entrypoint; 2 | 3 | public interface ModInitializer { 4 | /** 5 | * Main initializer for Eclipse mods. Executed immediately before `net.minecraft.server.Main.main()` is called 6 | */ 7 | void onInitialize(); 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/game/GameProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.game; 2 | 3 | import io.github.dueris.eclipse.api.GameLibrary; 4 | import io.github.dueris.eclipse.api.McVersion; 5 | import io.github.dueris.eclipse.api.Transformer; 6 | import joptsimple.OptionSet; 7 | 8 | import java.nio.file.Path; 9 | import java.util.stream.Stream; 10 | 11 | public interface GameProvider { 12 | 13 | /** 14 | * Retrieves the unique identifier for the game. EX: ("minecraft") 15 | * 16 | * @return The unique ID of the game. 17 | */ 18 | String getGameId(); 19 | 20 | /** 21 | * Retrieves the name of the game. EX: ("Paper") 22 | * 23 | * @return The name of the game. 24 | */ 25 | String getGameName(); 26 | 27 | /** 28 | * Retrieves the version information of the game. 29 | * 30 | * @return The version of the game (see {@link McVersion}). 31 | */ 32 | McVersion getVersion(); 33 | 34 | /** 35 | * Retrieves the entrypoint (e.g., main class or entry method) for the game. 36 | * 37 | * @return The entrypoint for the game as a string (the main class name). 38 | */ 39 | String getEntrypoint(); 40 | 41 | /** 42 | * Retrieves the libraries of for the game. Basically any and all libraries in 43 | * the `libraries` directory, and any patched libraries in cache. 44 | * 45 | * @return A stream of {@link GameLibrary} objects representing the libraries for the game. 46 | */ 47 | Stream getLibraries(); 48 | 49 | /** 50 | * Retrieves the path to the launch JAR file for the game. 51 | * 52 | * @return The {@link Path} to the launch JAR file. 53 | */ 54 | Path getLaunchJar(); 55 | 56 | /** 57 | * Retrieves the directory where the game is launched from. 58 | * 59 | * @return The {@link Path} to the launch directory. 60 | */ 61 | Path getLaunchDirectory(); 62 | 63 | /** 64 | * Prepares the {@link Transformer} for class-transforming. 65 | */ 66 | void prepareTransformer(); 67 | 68 | /** 69 | * Retrieves the transformer used at runtime by the EmberClassLoader 70 | * 71 | * @return The {@link Transformer} instance used in the game setup. 72 | */ 73 | Transformer getTransformer(); 74 | 75 | /** 76 | * Retrieves the set of arguments used to launch the game. 77 | * 78 | * @return An {@link OptionSet} containing the arguments for launching the game. 79 | */ 80 | OptionSet getArguments(); 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/mod/ModContainer.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.mod; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.tinylog.TaggedLogger; 6 | 7 | @ApiStatus.NonExtendable 8 | public interface ModContainer { 9 | @NotNull TaggedLogger logger(); 10 | 11 | @NotNull String id(); 12 | 13 | @NotNull String version(); 14 | 15 | @NotNull ModResource resource(); 16 | 17 | @NotNull ModMetadata config(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/mod/ModEngine.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.mod; 2 | 3 | import io.github.dueris.eclipse.api.game.GameProvider; 4 | import org.jetbrains.annotations.ApiStatus; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import java.util.Collection; 9 | import java.util.List; 10 | import java.util.Optional; 11 | 12 | @ApiStatus.NonExtendable 13 | public interface ModEngine { 14 | boolean loaded(final @NotNull String id); 15 | 16 | @NotNull Optional container(final @NotNull String id); 17 | 18 | @NotNull List resources(); 19 | 20 | @NotNull Collection containers(); 21 | 22 | @NotNull GameProvider gameProvider(); 23 | 24 | /** 25 | * Only returns null if the ModResource isn't registered to a ModContainer, or it's the Launcher or Game resource. 26 | */ 27 | @Nullable ModContainer getContainerFromResource(ModResource modResource); 28 | 29 | @NotNull ModResource getLauncherResource(); 30 | 31 | @NotNull ModResource getGameResource(); 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/mod/ModMetadata.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.mod; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | import org.jetbrains.annotations.Unmodifiable; 7 | import org.simpleyaml.configuration.file.YamlConfiguration; 8 | 9 | import java.util.List; 10 | import java.util.Objects; 11 | 12 | public record ModMetadata(String id, String version, @NotNull List mixins, @NotNull List wideners, 13 | boolean datapackEntry, YamlConfiguration backend) { 14 | 15 | public static @NotNull ModMetadata read(@NotNull YamlConfiguration yaml) { 16 | String id = yaml.getString("name").toLowerCase(); 17 | String version = yaml.getString("version"); 18 | return new ModMetadata( 19 | id, 20 | version, 21 | yaml.contains("mixins") ? yaml.getStringList("mixins") : List.of(), 22 | yaml.contains("wideners") ? yaml.getStringList("wideners") : List.of(), 23 | yaml.getBoolean("datapack-entry", false), yaml 24 | ); 25 | } 26 | 27 | @Override 28 | public int hashCode() { 29 | return Objects.hash(this.id, this.version, this.mixins); 30 | } 31 | 32 | @Override 33 | public boolean equals(final @Nullable Object other) { 34 | if (this == other) return true; 35 | if (!(other instanceof ModMetadata that)) return false; 36 | return Objects.equals(this.id, that.id) 37 | && Objects.equals(this.version, that.version) 38 | && Objects.equals(this.mixins, that.mixins) 39 | && Objects.equals(this.wideners, that.wideners); 40 | } 41 | 42 | @Override 43 | public @Unmodifiable @NotNull List mixins() { 44 | return ImmutableList.copyOf(mixins); 45 | } 46 | 47 | @Override 48 | public @Unmodifiable @NotNull List wideners() { 49 | return ImmutableList.copyOf(wideners); 50 | } 51 | 52 | @Override 53 | public @NotNull String toString() { 54 | return "ModMetadata{" + 55 | "id='" + id + '\'' + 56 | ", version='" + version + '\'' + 57 | ", mixins=" + mixins + 58 | ", wideners=" + wideners + 59 | ", datapackEntry=" + datapackEntry + 60 | ", backend=" + backend + 61 | '}'; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/mod/ModResource.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.mod; 2 | 3 | import org.jetbrains.annotations.ApiStatus; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.UnknownNullability; 6 | 7 | import java.nio.file.FileSystem; 8 | import java.nio.file.Path; 9 | import java.util.jar.Manifest; 10 | 11 | @ApiStatus.NonExtendable 12 | public interface ModResource { 13 | @NotNull String locator(); 14 | 15 | @NotNull Path path(); 16 | 17 | @UnknownNullability 18 | Manifest manifest(); 19 | 20 | @NotNull FileSystem fileSystem(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/util/BootstrapEntryContext.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.util; 2 | 3 | import com.google.gson.JsonArray; 4 | import com.google.gson.JsonObject; 5 | import io.github.dueris.eclipse.loader.util.Getter; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | 14 | public record BootstrapEntryContext(String brand, boolean isProviderContext, Path serverPath, byte[] optionSet) { 15 | public static @NotNull BootstrapEntryContext read() { 16 | JsonObject jsonObject = IgniteConstants.GSON.fromJson(((Getter) () -> { 17 | File contextFile = new File(Paths.get(".").resolve("cache").resolve(".eclipse") 18 | .toFile(), "bootstrap.context"); 19 | if (!contextFile.exists()) { 20 | throw new IllegalStateException("Unable to find bootstrap json! Did Eclipse start correctly?"); 21 | } 22 | try { 23 | return Files.readString(contextFile.toPath()); 24 | } catch (IOException e) { 25 | throw new RuntimeException("Unable to build String contents of Bootstrap!", e); 26 | } 27 | }).get(), JsonObject.class); 28 | 29 | JsonArray retrievedArray = jsonObject.getAsJsonArray("optionset"); 30 | byte[] bytes = new byte[retrievedArray.size()]; 31 | for (int i = 0; i < retrievedArray.size(); i++) { 32 | bytes[i] = retrievedArray.get(i).getAsByte(); 33 | } 34 | return new BootstrapEntryContext( 35 | jsonObject.get("brand").getAsString(), 36 | jsonObject.get("is_provider_context").getAsBoolean(), 37 | Path.of(jsonObject.get("path").getAsString()), 38 | bytes 39 | ); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/util/ClassLoaders.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.tinylog.Logger; 5 | import sun.misc.Unsafe; 6 | 7 | import java.lang.reflect.Field; 8 | import java.net.URL; 9 | import java.net.URLClassLoader; 10 | import java.util.ArrayList; 11 | 12 | public final class ClassLoaders { 13 | private ClassLoaders() { 14 | } 15 | 16 | @SuppressWarnings({"restriction", "unchecked"}) 17 | public static URL @NotNull [] systemClassPaths() { 18 | final ClassLoader classLoader = ClassLoaders.class.getClassLoader(); 19 | if (classLoader instanceof URLClassLoader) { 20 | return ((URLClassLoader) classLoader).getURLs(); 21 | } 22 | 23 | if (classLoader.getClass().getName().startsWith("jdk.internal.loader.ClassLoaders$")) { 24 | try { 25 | final Field field = Unsafe.class.getDeclaredField("theUnsafe"); 26 | field.setAccessible(true); 27 | final Unsafe unsafe = (Unsafe) field.get(null); 28 | 29 | // jdk.internal.loader.ClassLoaders.AppClassLoader.ucp 30 | Field ucpField; 31 | try { 32 | ucpField = classLoader.getClass().getDeclaredField("ucp"); 33 | } catch (final NoSuchFieldException | SecurityException e) { 34 | ucpField = classLoader.getClass().getSuperclass().getDeclaredField("ucp"); 35 | } 36 | 37 | final long ucpFieldOffset = unsafe.objectFieldOffset(ucpField); 38 | final Object ucpObject = unsafe.getObject(classLoader, ucpFieldOffset); 39 | 40 | // jdk.internal.loader.URLClassPath.path 41 | final Field pathField = ucpField.getType().getDeclaredField("path"); 42 | final long pathFieldOffset = unsafe.objectFieldOffset(pathField); 43 | final ArrayList path = (ArrayList) unsafe.getObject(ucpObject, pathFieldOffset); 44 | 45 | return path.toArray(new URL[0]); 46 | } catch (final Exception exception) { 47 | Logger.error(exception, "Failed to retrieve system classloader paths!"); 48 | return new URL[0]; 49 | } 50 | } 51 | 52 | return new URL[0]; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/util/IgniteConstants.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.util; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import io.github.dueris.eclipse.loader.Main; 6 | import org.objectweb.asm.Opcodes; 7 | 8 | public final class IgniteConstants { 9 | public static final String API_TITLE = Main.class.getPackage().getSpecificationTitle(); 10 | 11 | public static final String API_VERSION = Main.class.getPackage().getSpecificationVersion(); 12 | 13 | public static final String IMPLEMENTATION_VERSION = Main.class.getPackage().getImplementationVersion(); 14 | 15 | public static final int ASM_VERSION = Opcodes.ASM9; 16 | 17 | public static final String MOD_CONFIG_YML = "paper-plugin.yml"; 18 | 19 | public static final String MOD_CACHE_DIR = "META-INF/mods/"; 20 | 21 | public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); 22 | 23 | private IgniteConstants() { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/api/util/IgniteExclusions.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.api.util; 2 | 3 | public final class IgniteExclusions { 4 | 5 | public static final String[] TRANSFORMATION_EXCLUDED_RESOURCES = { 6 | "org/spongepowered/asm/" 7 | }; 8 | 9 | public static final String[] TRANSFORMATION_EXCLUDED_PACKAGES = { 10 | // Launcher 11 | "io.github.dueris.eclipse.", 12 | "org.tinylog.", 13 | 14 | // Mixin 15 | "org.spongepowered.asm.", 16 | "com.llamalad7.mixinextras.", 17 | 18 | // Logging 19 | "org.slf4j.", 20 | "org.apache.logging.log4j.", 21 | 22 | // Access Widener 23 | "net.fabricmc.accesswidener.", 24 | }; 25 | 26 | private IgniteExclusions() { 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/Main.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader; 2 | 3 | import io.github.dueris.eclipse.api.Launcher; 4 | import io.github.dueris.eclipse.api.util.IgniteConstants; 5 | import io.github.dueris.eclipse.loader.ember.Ember; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.tinylog.Logger; 8 | 9 | import java.net.URISyntaxException; 10 | import java.nio.file.Path; 11 | import java.util.List; 12 | import java.util.concurrent.atomic.AtomicBoolean; 13 | 14 | public final class Main { 15 | public static AtomicBoolean BOOTED = new AtomicBoolean(false); 16 | public static Path ROOT_ABSOLUTE; 17 | 18 | public static void main(final String @NotNull [] arguments) { 19 | try { 20 | ROOT_ABSOLUTE = Path.of(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI()) 21 | .toAbsolutePath(); 22 | } catch (URISyntaxException e) { 23 | throw new RuntimeException("Invalid URI in CodeSource of Main", e); 24 | } 25 | 26 | try { 27 | // Prepare API and Launcher 28 | Launcher launcher = new EclipseLauncher(); 29 | BOOTED.set(true); 30 | 31 | System.out.println("Preparing Minecraft server"); 32 | Ember ember = new Ember(); 33 | 34 | // Add the game. 35 | final Path gameJar = launcher.modEngine().gameProvider().getLaunchJar(); 36 | try { 37 | System.out.println("Unpacking and linking version:" + launcher.modEngine().gameProvider().getVersion() 38 | .id() + " to " + gameJar); 39 | MixinJavaAgent.appendToClassPath(gameJar); 40 | 41 | Logger.trace("Added game jar: {}", gameJar); 42 | } catch (final Throwable exception) { 43 | Logger.error(exception, "Failed to resolve game jar: {}", gameJar); 44 | System.exit(1); 45 | return; 46 | } 47 | 48 | // Add the game libraries. 49 | final List contained = List.of("net.sf.jopt-simple:jopt-simple:6.0-alpha-3", "net.minecrell:terminalconsoleappender:1.3.0"); 50 | launcher.modEngine().gameProvider().getLibraries().forEach(library -> { 51 | if (!library.libraryPath().toString().endsWith(".jar") || contained.contains(library.libraryString())) 52 | return; 53 | 54 | try { 55 | String unpackMessage = "Unpacking (" + library.libraryString() + ") to " + library.libraryPath(); 56 | if (library.trace()) { 57 | Logger.trace(unpackMessage); 58 | } else { 59 | System.out.println(unpackMessage); 60 | } 61 | MixinJavaAgent.appendToClassPath(library.libraryPath()); 62 | 63 | Logger.trace("Added game library jar: {}", library); 64 | } catch (final Throwable exception) { 65 | Logger.error(exception, "Failed to resolve game library jar: {}", library); 66 | } 67 | }); 68 | 69 | Logger.info("Loading {} {} with Eclipse version {}", launcher.modEngine().gameProvider() 70 | .getGameName(), launcher.modEngine() 71 | .gameProvider() 72 | .getVersion() 73 | .id(), IgniteConstants.IMPLEMENTATION_VERSION); 74 | 75 | // Launch the game. 76 | ember.launchEmber(Launcher.getInstance().modEngine()); 77 | } catch (Throwable throwable) { 78 | throwable.printStackTrace(); 79 | throw new RuntimeException("An unexpected error occurred when starting mixin server!", throwable); 80 | } 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/MixinJavaAgent.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader; 2 | 3 | import io.github.dueris.eclipse.api.Agent; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.io.File; 8 | import java.io.FileNotFoundException; 9 | import java.io.IOException; 10 | import java.lang.instrument.ClassFileTransformer; 11 | import java.lang.instrument.Instrumentation; 12 | import java.nio.file.Path; 13 | import java.util.jar.JarFile; 14 | 15 | public final class MixinJavaAgent implements Agent { 16 | static Agent INSTANCE; 17 | private static Instrumentation INSTRUMENTATION = null; 18 | 19 | private MixinJavaAgent() { 20 | INSTANCE = this; 21 | } 22 | 23 | public static void appendToClassPath(final @NotNull Path path) throws IOException { 24 | final File file = path.toFile(); 25 | if (!file.exists()) throw new FileNotFoundException(file.getAbsolutePath()); 26 | if (file.isDirectory() || !file.getName().endsWith(".jar")) 27 | throw new IOException("Provided path is not a jar file: " + path); 28 | MixinJavaAgent.appendToClassPath(new JarFile(file)); 29 | } 30 | 31 | public static void appendToClassPath(final @NotNull JarFile jar) { 32 | if (MixinJavaAgent.INSTRUMENTATION != null) { 33 | MixinJavaAgent.INSTRUMENTATION.appendToSystemClassLoaderSearch(jar); 34 | return; 35 | } 36 | 37 | throw new IllegalStateException("Unable to addJar for '" + jar.getName() + "'."); 38 | } 39 | 40 | public static void premain(final @NotNull String arguments, final @Nullable Instrumentation instrumentation) { 41 | MixinJavaAgent.agentmain(arguments, instrumentation); 42 | } 43 | 44 | public static void agentmain(final @NotNull String arguments, final @Nullable Instrumentation instrumentation) { 45 | if (MixinJavaAgent.INSTRUMENTATION == null) MixinJavaAgent.INSTRUMENTATION = instrumentation; 46 | if (MixinJavaAgent.INSTRUMENTATION == null) 47 | throw new NullPointerException("Unable to get instrumentation instance!"); 48 | } 49 | 50 | @Override 51 | public void appendToClasspath(Path path) { 52 | try { 53 | MixinJavaAgent.appendToClassPath(path); 54 | } catch (IOException e) { 55 | throw new RuntimeException("Unable to append to classpath!", e); 56 | } 57 | } 58 | 59 | @Override 60 | public void registerClassFileTransformer(ClassFileTransformer fileTransformer) { 61 | INSTRUMENTATION.addTransformer(fileTransformer); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/api/impl/ModContainerImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.api.impl; 2 | 3 | import io.github.dueris.eclipse.api.mod.ModContainer; 4 | import io.github.dueris.eclipse.api.mod.ModMetadata; 5 | import io.github.dueris.eclipse.api.mod.ModResource; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import org.tinylog.TaggedLogger; 9 | 10 | import java.util.Objects; 11 | 12 | public record ModContainerImpl(TaggedLogger logger, ModResource resource, ModMetadata config) implements ModContainer { 13 | 14 | @Override 15 | public @NotNull String id() { 16 | return this.config.id(); 17 | } 18 | 19 | @Override 20 | public @NotNull String version() { 21 | return this.config.version(); 22 | } 23 | 24 | @Override 25 | public @NotNull ModMetadata config() { 26 | return this.config; 27 | } 28 | 29 | @Override 30 | public int hashCode() { 31 | return Objects.hash(this.resource, this.config); 32 | } 33 | 34 | @Override 35 | public boolean equals(final @Nullable Object other) { 36 | if (this == other) return true; 37 | if (!(other instanceof ModContainerImpl that)) return false; 38 | return Objects.equals(this.resource, that.resource) 39 | && Objects.equals(this.config, that.config); 40 | } 41 | 42 | @Override 43 | public @NotNull String toString() { 44 | return "ModContainerImpl(id=" + this.id() + ", version=" + this.version() + ", resource=" + this.resource() + ", config=" + this.config() + ")"; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/api/impl/ModResourceImpl.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.api.impl; 2 | 3 | import io.github.dueris.eclipse.api.mod.ModResource; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | import org.jetbrains.annotations.UnknownNullability; 7 | import org.tinylog.Logger; 8 | 9 | import java.io.IOException; 10 | import java.nio.file.FileSystem; 11 | import java.nio.file.FileSystems; 12 | import java.nio.file.Path; 13 | import java.util.List; 14 | import java.util.Objects; 15 | import java.util.jar.Manifest; 16 | 17 | public final class ModResourceImpl implements ModResource { 18 | private final String locator; 19 | private final Path path; 20 | private final Manifest manifest; 21 | private final boolean child; 22 | private final List children; 23 | 24 | private FileSystem fileSystem; 25 | 26 | public ModResourceImpl(final @NotNull String locator, 27 | final @NotNull Path path, 28 | final @UnknownNullability Manifest manifest, boolean child, List children) { 29 | this.locator = locator; 30 | this.path = path.toAbsolutePath().normalize(); 31 | this.manifest = manifest; 32 | this.child = child; 33 | this.children = children; 34 | } 35 | 36 | @Override 37 | public @NotNull String locator() { 38 | return this.locator; 39 | } 40 | 41 | @Override 42 | public @NotNull Path path() { 43 | return this.path; 44 | } 45 | 46 | @Override 47 | public @UnknownNullability Manifest manifest() { 48 | return this.manifest; 49 | } 50 | 51 | @Override 52 | public @NotNull FileSystem fileSystem() { 53 | if (this.fileSystem == null) { 54 | try { 55 | this.fileSystem = FileSystems.newFileSystem(this.path(), this.getClass().getClassLoader()); 56 | } catch (final IOException exception) { 57 | throw new RuntimeException(exception); 58 | } 59 | } 60 | 61 | return this.fileSystem; 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return Objects.hash(this.locator, this.path, this.manifest); 67 | } 68 | 69 | @Override 70 | public boolean equals(final @Nullable Object other) { 71 | if (this == other) return true; 72 | if (!(other instanceof ModResourceImpl that)) return false; 73 | Logger.trace("Checking for qualification for P1: " + path.toAbsolutePath() 74 | .normalize() + " and P2: " + that.path.toAbsolutePath() 75 | .normalize()); 76 | return Objects.equals(path.toAbsolutePath().normalize(), that.path.toAbsolutePath().normalize()); 77 | } 78 | 79 | @Override 80 | public @NotNull String toString() { 81 | return "ModResourceImpl{locator='" + this.locator + ", path=" + this.path + ", manifest=" + this.manifest + "}"; 82 | } 83 | 84 | public boolean isChild() { 85 | return child; 86 | } 87 | 88 | public List getChildren() { 89 | return children; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/ember/DynamicClassLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.ember; 2 | 3 | import io.github.dueris.eclipse.loader.util.mrj.AbstractUrlClassLoader; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.io.IOException; 8 | import java.net.URL; 9 | import java.util.Enumeration; 10 | import java.util.NoSuchElementException; 11 | 12 | public class DynamicClassLoader extends AbstractUrlClassLoader { 13 | static { 14 | ClassLoader.registerAsParallelCapable(); 15 | } 16 | 17 | DynamicClassLoader(final URL @NotNull [] urls) { 18 | this("dynamic", urls); 19 | } 20 | 21 | DynamicClassLoader(String name, final URL @NotNull [] urls) { 22 | super(name, urls, new DummyClassLoader()); 23 | } 24 | 25 | @Override 26 | public void addURL(final @NotNull URL url) { 27 | super.addURL(url); 28 | } 29 | 30 | protected static final class DummyClassLoader extends ClassLoader { 31 | private static final Enumeration NULL_ENUMERATION = new Enumeration() { 32 | @Override 33 | public boolean hasMoreElements() { 34 | return false; 35 | } 36 | 37 | @Override 38 | public @NotNull URL nextElement() { 39 | throw new NoSuchElementException(); 40 | } 41 | }; 42 | 43 | static { 44 | ClassLoader.registerAsParallelCapable(); 45 | } 46 | 47 | @Override 48 | protected @NotNull Class loadClass(final @NotNull String name, final boolean resolve) throws ClassNotFoundException { 49 | throw new ClassNotFoundException(name); 50 | } 51 | 52 | @Override 53 | public @Nullable URL getResource(final @NotNull String name) { 54 | return null; 55 | } 56 | 57 | @Override 58 | public @NotNull Enumeration getResources(final @NotNull String name) throws IOException { 59 | return DummyClassLoader.NULL_ENUMERATION; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/ember/Ember.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.ember; 2 | 3 | import com.llamalad7.mixinextras.MixinExtrasBootstrap; 4 | import io.github.dueris.eclipse.api.mod.ModEngine; 5 | import io.github.dueris.eclipse.loader.EclipseLauncher; 6 | import io.github.dueris.eclipse.loader.ember.patch.EmberTransformer; 7 | import io.github.dueris.eclipse.loader.ember.patch.TransformerService; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.spongepowered.asm.launch.MixinBootstrap; 10 | import org.spongepowered.asm.mixin.MixinEnvironment; 11 | import org.tinylog.Logger; 12 | 13 | import java.lang.reflect.Method; 14 | import java.net.MalformedURLException; 15 | import java.net.URL; 16 | import java.nio.file.Path; 17 | import java.util.LinkedList; 18 | import java.util.List; 19 | 20 | public final class Ember { 21 | private static final List classPathUrls = new LinkedList<>(); 22 | private static Ember INSTANCE; 23 | private final EclipseLauncher service; 24 | private EmberTransformer transformer; 25 | private EmberClassLoader loader; 26 | 27 | public Ember() { 28 | Ember.INSTANCE = this; 29 | 30 | this.service = EclipseLauncher.INSTANCE; 31 | } 32 | 33 | public static void appendToClassPath(Path path) { 34 | try { 35 | classPathUrls.add(path.toUri().toURL()); 36 | } catch (MalformedURLException e) { 37 | throw new RuntimeException("Url was malformed?", e); 38 | } 39 | } 40 | 41 | static @NotNull Ember instance() { 42 | if (Ember.INSTANCE == null) throw new IllegalStateException("Instance is only available after launch!"); 43 | return Ember.INSTANCE; 44 | } 45 | 46 | @NotNull EmberTransformer transformer() { 47 | return this.transformer; 48 | } 49 | 50 | @NotNull EmberClassLoader loader() { 51 | return this.loader; 52 | } 53 | 54 | public void launchEmber(final @NotNull ModEngine mixinModEngine) { 55 | // Initialize the launch. 56 | this.service.initialize(); 57 | 58 | // Prepare and create the transformer. 59 | mixinModEngine.gameProvider().prepareTransformer(); 60 | this.transformer = (EmberTransformer) mixinModEngine.gameProvider().getTransformer(); 61 | 62 | // Create the class loader. 63 | this.loader = new EmberClassLoader(this.transformer, (classPathUrls.toArray(new URL[0]))); 64 | Thread.currentThread().setContextClassLoader(this.loader); 65 | 66 | // Configure the class loader. 67 | this.service.configure(this.loader, this.transformer); 68 | 69 | // Start the mixin bootstrap. 70 | MixinBootstrap.init(); 71 | 72 | // Prepare the launch. 73 | this.service.prepare(this.transformer); 74 | 75 | // Complete the mixin bootstrap. 76 | this.completeMixinBootstrap(); 77 | 78 | // Initialize mixin extras. 79 | MixinExtrasBootstrap.init(); 80 | 81 | // Execute the launch. 82 | this.service.launch(this.loader); 83 | } 84 | 85 | private void completeMixinBootstrap() { 86 | // Move to the default phase. 87 | try { 88 | final Method method = MixinEnvironment.class.getDeclaredMethod("gotoPhase", MixinEnvironment.Phase.class); 89 | method.setAccessible(true); 90 | method.invoke(null, MixinEnvironment.Phase.INIT); 91 | method.invoke(null, MixinEnvironment.Phase.DEFAULT); 92 | } catch (final Exception exception) { 93 | Logger.error(exception, "Failed to complete mixin bootstrap!"); 94 | } 95 | 96 | // Prepare transformers 97 | for (final TransformerService transformer : this.transformer.transformers()) { 98 | transformer.prepare(); 99 | } 100 | } 101 | 102 | public EclipseLauncher getService() { 103 | return this.service; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/ember/mixin/EmberMixinBootstrap.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.ember.mixin; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.spongepowered.asm.service.IMixinServiceBootstrap; 5 | 6 | public final class EmberMixinBootstrap implements IMixinServiceBootstrap { 7 | public EmberMixinBootstrap() { 8 | } 9 | 10 | @Override 11 | public @NotNull String getName() { 12 | return "Ember"; 13 | } 14 | 15 | @Override 16 | public @NotNull String getServiceClassName() { 17 | return "io.github.dueris.eclipse.loader.launch.ember.EmberMixinService"; 18 | } 19 | 20 | @Override 21 | public void bootstrap() { 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/ember/mixin/EmberMixinContainer.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.ember.mixin; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.spongepowered.asm.launch.platform.container.ContainerHandleURI; 5 | import org.spongepowered.asm.launch.platform.container.ContainerHandleVirtual; 6 | 7 | import java.nio.file.Path; 8 | import java.util.Map; 9 | 10 | public final class EmberMixinContainer extends ContainerHandleVirtual { 11 | public EmberMixinContainer(final @NotNull String name) { 12 | super(name); 13 | } 14 | 15 | public void addResource(final @NotNull String name, final @NotNull Path path) { 16 | this.add(new ResourceContainer(name, path)); 17 | } 18 | 19 | public void addResource(final Map.@NotNull Entry entry) { 20 | this.add(new ResourceContainer(entry.getKey(), entry.getValue())); 21 | } 22 | 23 | @Override 24 | public @NotNull String toString() { 25 | return "EmberMixinContainer{name=" + this.getName() + "}"; 26 | } 27 | 28 | static class ResourceContainer extends ContainerHandleURI { 29 | private final String name; 30 | private final Path path; 31 | 32 | ResourceContainer(final @NotNull String name, final @NotNull Path path) { 33 | super(path.toUri()); 34 | 35 | this.name = name; 36 | this.path = path; 37 | } 38 | 39 | public @NotNull String name() { 40 | return this.name; 41 | } 42 | 43 | public @NotNull Path path() { 44 | return this.path; 45 | } 46 | 47 | @Override 48 | public @NotNull String toString() { 49 | return "ResourceContainer{name=" + this.name + ", path=" + this.path + "}"; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/ember/mixin/EmberMixinLogger.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.ember.mixin; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.spongepowered.asm.logging.ILogger; 5 | import org.spongepowered.asm.logging.Level; 6 | import org.spongepowered.asm.logging.LoggerAdapterAbstract; 7 | import org.tinylog.Logger; 8 | import org.tinylog.TaggedLogger; 9 | 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | public final class EmberMixinLogger extends LoggerAdapterAbstract { 14 | private static final Map LOGGERS = new ConcurrentHashMap<>(); 15 | private final TaggedLogger logger; 16 | 17 | EmberMixinLogger(final @NotNull String id) { 18 | super(id); 19 | 20 | this.logger = Logger.tag(id); 21 | } 22 | 23 | public static @NotNull ILogger get(final @NotNull String name) { 24 | return EmberMixinLogger.LOGGERS.computeIfAbsent(name, EmberMixinLogger::new); 25 | } 26 | 27 | @Override 28 | public @NotNull String getType() { 29 | return "TinyLogger (via Eclipse)"; 30 | } 31 | 32 | @Override 33 | public void catching(final @NotNull Level level, final @NotNull Throwable throwable) { 34 | switch (level) { 35 | case WARN: { 36 | this.logger.warn(throwable); 37 | break; 38 | } 39 | case INFO: { 40 | this.logger.info(throwable); 41 | break; 42 | } 43 | case DEBUG: { 44 | this.logger.debug(throwable); 45 | break; 46 | } 47 | case TRACE: { 48 | this.logger.trace(throwable); 49 | break; 50 | } 51 | default: { 52 | this.logger.error(throwable); 53 | break; 54 | } 55 | } 56 | } 57 | 58 | @Override 59 | public void log(final @NotNull Level level, final @NotNull String message, final @NotNull Object... args) { 60 | switch (level) { 61 | case WARN: { 62 | this.logger.warn(message, args); 63 | break; 64 | } 65 | case INFO: { 66 | this.logger.info(message, args); 67 | break; 68 | } 69 | case DEBUG: { 70 | this.logger.debug(message, args); 71 | break; 72 | } 73 | case TRACE: { 74 | this.logger.trace(message, args); 75 | break; 76 | } 77 | default: { 78 | this.logger.error(message, args); 79 | break; 80 | } 81 | } 82 | } 83 | 84 | @Override 85 | public void log(final @NotNull Level level, final @NotNull String message, final @NotNull Throwable throwable) { 86 | switch (level) { 87 | case WARN: { 88 | this.logger.warn(throwable, message); 89 | break; 90 | } 91 | case INFO: { 92 | this.logger.info(throwable, message); 93 | break; 94 | } 95 | case DEBUG: { 96 | this.logger.debug(throwable, message); 97 | break; 98 | } 99 | case TRACE: { 100 | this.logger.trace(throwable, message); 101 | break; 102 | } 103 | default: { 104 | this.logger.error(throwable, message); 105 | break; 106 | } 107 | } 108 | } 109 | 110 | @Override 111 | public T throwing(final @NotNull T throwable) { 112 | this.logger.error(throwable); 113 | return throwable; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/ember/patch/GamePatch.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.ember.patch; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.objectweb.asm.tree.ClassNode; 5 | 6 | import java.util.Arrays; 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | public abstract class GamePatch { 11 | private final List targets = new LinkedList<>(); 12 | 13 | public List targets() { 14 | return targets; 15 | } 16 | 17 | protected abstract boolean applyPatch(ClassNode classNode); 18 | 19 | protected void appendTargets(@NotNull String @NotNull ... targets) { 20 | this.targets.addAll(Arrays.asList(targets)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/ember/patch/TransformPhase.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.ember.patch; 2 | 3 | public enum TransformPhase { 4 | INITIALIZE, MIXIN 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/ember/patch/TransformerService.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.ember.patch; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | import org.objectweb.asm.Type; 6 | import org.objectweb.asm.tree.ClassNode; 7 | 8 | public interface TransformerService { 9 | void prepare(); 10 | 11 | int priority(final @NotNull TransformPhase phase); 12 | 13 | boolean shouldTransform(final @NotNull Type type, final @NotNull ClassNode node); 14 | 15 | @Nullable ClassNode transform(final @NotNull Type type, final @NotNull ClassNode node, final @NotNull TransformPhase phase) throws Throwable; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/patch/BootstrapEntrypointPatch.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.patch; 2 | 3 | import io.github.dueris.eclipse.loader.ember.patch.GamePatch; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.Type; 7 | import org.objectweb.asm.tree.*; 8 | 9 | import java.util.ListIterator; 10 | 11 | // TODO - remove - Dueris 12 | public class BootstrapEntrypointPatch extends GamePatch { 13 | public BootstrapEntrypointPatch() { 14 | this.appendTargets("net/minecraft/server/Bootstrap"); 15 | } 16 | 17 | @Override 18 | protected boolean applyPatch(@NotNull ClassNode classNode) { 19 | boolean applied = false; 20 | 21 | for (MethodNode node : classNode.methods) { 22 | if (node.name.equals("bootStrap") && node.desc.equals("()V")) { 23 | ListIterator it = node.instructions.iterator(); 24 | 25 | while (it.hasNext()) { 26 | AbstractInsnNode insn = it.next(); 27 | 28 | if (insn.getOpcode() == Opcodes.INVOKESTATIC && 29 | insn instanceof MethodInsnNode methodInsn && 30 | methodInsn.owner.equals("java/time/Instant") && 31 | methodInsn.name.equals("now")) { 32 | 33 | it.add(new LdcInsnNode(Type.getType("Lio/github/dueris/eclipse/api/entrypoint/BootstrapInitializer;"))); 34 | 35 | it.add(new MethodInsnNode( 36 | Opcodes.INVOKESTATIC, 37 | "io/github/dueris/eclipse/api/entrypoint/EntrypointContainer", 38 | "getEntrypoint", 39 | "(Ljava/lang/Class;)Lio/github/dueris/eclipse/api/entrypoint/EntrypointInstance;", 40 | false 41 | )); 42 | 43 | it.add(new InsnNode(Opcodes.ICONST_0)); 44 | it.add(new TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/Object")); 45 | it.add(new MethodInsnNode( 46 | Opcodes.INVOKEVIRTUAL, 47 | "io/github/dueris/eclipse/api/entrypoint/EntrypointInstance", 48 | "enter", 49 | "([Ljava/lang/Object;)V", 50 | false 51 | )); 52 | 53 | applied = true; 54 | break; 55 | } 56 | } 57 | 58 | node.maxStack = Math.max(node.maxStack, 4); 59 | } 60 | } 61 | 62 | return applied; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/patch/BrandingPatch.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.patch; 2 | 3 | import io.github.dueris.eclipse.loader.ember.patch.GamePatch; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.AbstractInsnNode; 7 | import org.objectweb.asm.tree.ClassNode; 8 | import org.objectweb.asm.tree.MethodInsnNode; 9 | import org.objectweb.asm.tree.MethodNode; 10 | 11 | import java.util.ListIterator; 12 | 13 | public class BrandingPatch extends GamePatch { 14 | public BrandingPatch() { 15 | this.appendTargets("net/minecraft/server/MinecraftServer"); 16 | } 17 | 18 | @Override 19 | protected boolean applyPatch(@NotNull ClassNode classNode) { 20 | boolean applied = false; 21 | 22 | for (MethodNode node : classNode.methods) { 23 | if (node.name.equals("getServerModName") && node.desc.endsWith(")Ljava/lang/String;")) { 24 | ListIterator it = node.instructions.iterator(); 25 | 26 | while (it.hasNext()) { 27 | if (it.next().getOpcode() == Opcodes.ARETURN) { 28 | it.previous(); 29 | it.add(new MethodInsnNode(Opcodes.INVOKESTATIC, PatchHooks.INTERNAL_NAME, "insertBranding", "(Ljava/lang/String;)Ljava/lang/String;", false)); 30 | it.next(); 31 | } 32 | } 33 | 34 | applied = true; 35 | } 36 | } 37 | 38 | return applied; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/patch/CraftServerNamePatch.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.patch; 2 | 3 | import io.github.dueris.eclipse.loader.ember.patch.GamePatch; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.objectweb.asm.Opcodes; 6 | import org.objectweb.asm.tree.*; 7 | 8 | import java.util.ListIterator; 9 | 10 | public class CraftServerNamePatch extends GamePatch { 11 | public CraftServerNamePatch() { 12 | this.appendTargets("org/bukkit/craftbukkit/CraftServer"); 13 | } 14 | 15 | @Override 16 | protected boolean applyPatch(@NotNull ClassNode classNode) { 17 | boolean applied = false; 18 | 19 | for (MethodNode node : classNode.methods) { 20 | if (node.name.equals("getName") && node.desc.endsWith(")Ljava/lang/String;")) { 21 | ListIterator it = node.instructions.iterator(); 22 | 23 | while (it.hasNext()) { 24 | AbstractInsnNode insn = it.next(); 25 | 26 | if (insn.getOpcode() == Opcodes.ARETURN) { 27 | it.previous(); 28 | it.remove(); 29 | 30 | it.add(new MethodInsnNode( 31 | Opcodes.INVOKESTATIC, 32 | "net/minecraft/server/MinecraftServer", 33 | "getServer", 34 | "()Lnet/minecraft/server/MinecraftServer;", 35 | false 36 | )); 37 | it.add(new MethodInsnNode( 38 | Opcodes.INVOKEVIRTUAL, 39 | "net/minecraft/server/MinecraftServer", 40 | "getServerModName", 41 | "()Ljava/lang/String;", 42 | false 43 | )); 44 | 45 | it.add(new InsnNode(Opcodes.ARETURN)); 46 | applied = true; 47 | break; 48 | } 49 | } 50 | } 51 | } 52 | 53 | return applied; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/patch/PatchHooks.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.patch; 2 | 3 | import org.tinylog.Logger; 4 | 5 | public class PatchHooks { 6 | public static final String INTERNAL_NAME = PatchHooks.class.getName().replace('.', '/'); 7 | public static final String ECLIPSE = "eclipse"; 8 | public static final String VANILLA = "Paper"; 9 | 10 | public static String insertBranding(final String brand) { 11 | if (brand == null || brand.isEmpty()) { 12 | Logger.warn("Null or empty branding found!", new IllegalStateException()); 13 | return ECLIPSE; 14 | } 15 | 16 | return VANILLA.equals(brand) ? ECLIPSE : brand + "/(" + ECLIPSE + ")"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/patch/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains hard-coded patches for the source that are 3 | * simple enough to not be needed as mixins, and as tests for hardcoded 4 | * patches of the ClassNode. 5 | */ 6 | package io.github.dueris.eclipse.loader.launch.patch; 7 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/property/MixinGlobalPropertyService.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.property; 2 | 3 | import io.github.dueris.eclipse.api.Launcher; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.spongepowered.asm.service.IGlobalPropertyService; 6 | import org.spongepowered.asm.service.IPropertyKey; 7 | 8 | public final class MixinGlobalPropertyService implements IGlobalPropertyService { 9 | @Override 10 | public @NotNull IPropertyKey resolveKey(String name) { 11 | return new MixinStringPropertyKey(name); 12 | } 13 | 14 | private String keyString(IPropertyKey key) { 15 | return ((MixinStringPropertyKey) key).key; 16 | } 17 | 18 | @Override 19 | @SuppressWarnings("unchecked") 20 | public T getProperty(IPropertyKey key) { 21 | return (T) Launcher.getInstance().getProperties().get(keyString(key)); 22 | } 23 | 24 | @Override 25 | public void setProperty(IPropertyKey key, Object value) { 26 | Launcher.getInstance().getProperties().put(keyString(key), value); 27 | } 28 | 29 | @Override 30 | @SuppressWarnings("unchecked") 31 | public T getProperty(IPropertyKey key, T defaultValue) { 32 | return (T) Launcher.getInstance().getProperties().getOrDefault(keyString(key), defaultValue); 33 | } 34 | 35 | @Override 36 | public String getPropertyString(IPropertyKey key, String defaultValue) { 37 | Object o = Launcher.getInstance().getProperties().get(keyString(key)); 38 | return o != null ? o.toString() : defaultValue; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/property/MixinStringPropertyKey.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.property; 2 | 3 | import org.spongepowered.asm.service.IPropertyKey; 4 | 5 | import java.util.Objects; 6 | 7 | public class MixinStringPropertyKey implements IPropertyKey { 8 | public final String key; 9 | 10 | public MixinStringPropertyKey(String key) { 11 | this.key = key; 12 | } 13 | 14 | @Override 15 | public boolean equals(Object obj) { 16 | if (!(obj instanceof MixinStringPropertyKey)) { 17 | return false; 18 | } else { 19 | return Objects.equals(this.key, ((MixinStringPropertyKey) obj).key); 20 | } 21 | } 22 | 23 | @Override 24 | public int hashCode() { 25 | return this.key.hashCode(); 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return this.key; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/transformer/AccessWidenerTransformer.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.transformer; 2 | 3 | import io.github.dueris.eclipse.api.util.IgniteConstants; 4 | import io.github.dueris.eclipse.loader.ember.patch.TransformPhase; 5 | import io.github.dueris.eclipse.loader.ember.patch.TransformerService; 6 | import net.fabricmc.accesswidener.AccessWidener; 7 | import net.fabricmc.accesswidener.AccessWidenerClassVisitor; 8 | import net.fabricmc.accesswidener.AccessWidenerReader; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.objectweb.asm.ClassVisitor; 11 | import org.objectweb.asm.Type; 12 | import org.objectweb.asm.tree.ClassNode; 13 | 14 | import java.io.BufferedReader; 15 | import java.io.IOException; 16 | import java.nio.charset.StandardCharsets; 17 | import java.nio.file.Files; 18 | import java.nio.file.Path; 19 | 20 | public final class AccessWidenerTransformer implements TransformerService { 21 | private final AccessWidener widener = new AccessWidener(); 22 | private final AccessWidenerReader widenerReader = new AccessWidenerReader(this.widener); 23 | 24 | public void addWidener(final @NotNull Path path) throws IOException { 25 | try (final BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { 26 | this.widenerReader.read(reader); 27 | } 28 | } 29 | 30 | @Override 31 | public void prepare() { 32 | } 33 | 34 | @Override 35 | public int priority(final @NotNull TransformPhase phase) { 36 | // Only transform targets on the initialize phase. 37 | if (phase != TransformPhase.INITIALIZE) return -1; 38 | // This prioritizes access widener near the beginning of the transformation 39 | // pipeline. 40 | return 25; 41 | } 42 | 43 | @Override 44 | public boolean shouldTransform(final @NotNull Type type, final @NotNull ClassNode node) { 45 | // Only transform targets that need to be widened. 46 | return this.widener.getTargets().contains(node.name.replace('/', '.')); 47 | } 48 | 49 | @Override 50 | public @NotNull ClassNode transform(final @NotNull Type type, final @NotNull ClassNode node, final @NotNull TransformPhase phase) throws Throwable { 51 | final ClassNode writer = new ClassNode(IgniteConstants.ASM_VERSION); 52 | final ClassVisitor visitor = AccessWidenerClassVisitor.createClassVisitor(IgniteConstants.ASM_VERSION, writer, this.widener); 53 | 54 | node.accept(visitor); 55 | 56 | return writer; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/transformer/ForgeTransformerService.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.transformer; 2 | 3 | import io.github.dueris.eclipse.api.Launcher; 4 | import io.github.dueris.eclipse.loader.ember.patch.TransformPhase; 5 | import io.github.dueris.eclipse.loader.ember.patch.TransformerService; 6 | import io.github.dueris.eclipse.loader.launch.transformer.forge.ForgeAccessTransformer; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | import org.objectweb.asm.Type; 10 | import org.objectweb.asm.tree.ClassNode; 11 | 12 | public class ForgeTransformerService implements TransformerService { 13 | @Override 14 | public void prepare() { 15 | ForgeAccessTransformer.init(Launcher.getInstance().modEngine()); 16 | } 17 | 18 | @Override 19 | public int priority(@NotNull TransformPhase phase) { 20 | if (phase != TransformPhase.INITIALIZE) return -1; 21 | return 35; 22 | } 23 | 24 | @Override 25 | public boolean shouldTransform(@NotNull Type type, @NotNull ClassNode node) { 26 | return ForgeAccessTransformer.transformers.containsValue(node.name.replace('/', '.')); 27 | } 28 | 29 | @Override 30 | public @Nullable ClassNode transform(@NotNull Type type, @NotNull ClassNode node, @NotNull TransformPhase phase) throws Throwable { 31 | return ForgeAccessTransformer.transformNode(node); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/transformer/MixinTransformer.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.transformer; 2 | 3 | import io.github.dueris.eclipse.api.util.IgniteConstants; 4 | import io.github.dueris.eclipse.loader.ember.patch.TransformPhase; 5 | import io.github.dueris.eclipse.loader.ember.patch.TransformerService; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | import org.objectweb.asm.ClassReader; 9 | import org.objectweb.asm.Type; 10 | import org.objectweb.asm.tree.ClassNode; 11 | import org.spongepowered.asm.mixin.MixinEnvironment; 12 | import org.spongepowered.asm.mixin.transformer.IMixinTransformer; 13 | import org.spongepowered.asm.mixin.transformer.IMixinTransformerFactory; 14 | import org.spongepowered.asm.service.ISyntheticClassRegistry; 15 | import org.spongepowered.asm.transformers.MixinClassReader; 16 | 17 | public final class MixinTransformer implements TransformerService { 18 | private IMixinTransformerFactory transformerFactory; 19 | private IMixinTransformer transformer; 20 | private ISyntheticClassRegistry registry; 21 | 22 | public void offer(final @NotNull IMixinTransformerFactory factory) { 23 | this.transformerFactory = factory; 24 | } 25 | 26 | @Override 27 | public void prepare() { 28 | if (this.transformerFactory == null) throw new IllegalStateException("Transformer factory is not available!"); 29 | this.transformer = this.transformerFactory.createTransformer(); 30 | this.registry = this.transformer.getExtensions().getSyntheticClassRegistry(); 31 | } 32 | 33 | @Override 34 | public int priority(final @NotNull TransformPhase phase) { 35 | // We don't want mixin trying to transform targets it's already 36 | // transforming. 37 | if (phase == TransformPhase.MIXIN) return -1; 38 | // This prioritizes mixin in the middle of the transformation 39 | // pipeline. 40 | return 50; 41 | } 42 | 43 | @Override 44 | public boolean shouldTransform(final @NotNull Type type, final @NotNull ClassNode node) { 45 | // We want to send everything for mixin to decide. 46 | return true; 47 | } 48 | 49 | @Override 50 | public @Nullable ClassNode transform(final @NotNull Type type, final @NotNull ClassNode node, final @NotNull TransformPhase phase) throws Throwable { 51 | // Generate the class if it is synthetic through mixin. 52 | if (this.shouldGenerateClass(type)) { 53 | return this.generateClass(type, node) ? node : null; 54 | } 55 | 56 | // Transform the class through mixin. 57 | return this.transformer.transformClass(MixinEnvironment.getCurrentEnvironment(), type.getClassName(), node) ? node : null; 58 | } 59 | 60 | public @NotNull ClassNode classNode(final @NotNull String canonicalName, final @NotNull String internalName, final byte @NotNull [] input, final int readerFlags) throws ClassNotFoundException { 61 | if (input.length != 0) { 62 | final ClassNode node = new ClassNode(IgniteConstants.ASM_VERSION); 63 | final ClassReader reader = new MixinClassReader(input, canonicalName); 64 | reader.accept(node, readerFlags); 65 | return node; 66 | } 67 | 68 | final Type type = Type.getObjectType(internalName); 69 | if (this.shouldGenerateClass(type)) { 70 | final ClassNode node = new ClassNode(IgniteConstants.ASM_VERSION); 71 | if (this.generateClass(type, node)) return node; 72 | } 73 | 74 | throw new ClassNotFoundException(canonicalName); 75 | } 76 | 77 | boolean shouldGenerateClass(final @NotNull Type type) { 78 | return this.registry.findSyntheticClass(type.getClassName()) != null; 79 | } 80 | 81 | boolean generateClass(final @NotNull Type type, final @NotNull ClassNode node) { 82 | return this.transformer.generateClass(MixinEnvironment.getCurrentEnvironment(), type.getClassName(), node); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/transformer/forge/EqualityCheckingLinkedHashMap.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.transformer.forge; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | 5 | import java.util.LinkedHashMap; 6 | 7 | public class EqualityCheckingLinkedHashMap extends LinkedHashMap { 8 | 9 | @Override 10 | public V put(@NotNull K key, @NotNull V value) { 11 | for (K existingKey : keySet()) { 12 | if (existingKey.equals(key)) { 13 | return super.put(existingKey, value); 14 | } 15 | } 16 | 17 | return super.put(key, value); 18 | } 19 | 20 | @Override 21 | public boolean containsKey(Object key) { 22 | for (K existingKey : keySet()) { 23 | if (existingKey.equals(key)) { 24 | return true; 25 | } 26 | } 27 | return false; 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/transformer/forge/FinalModifierDefinition.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.transformer.forge; 2 | 3 | public record FinalModifierDefinition(boolean removeFinal, boolean addFinal) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/transformer/forge/ModifierType.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.transformer.forge; 2 | 3 | import org.objectweb.asm.Opcodes; 4 | 5 | import java.util.function.Function; 6 | 7 | public enum ModifierType { 8 | PUBLIC(ModifierType::makePublic), 9 | PROTECTED(ModifierType::makeProtected), 10 | DEFAULT(ModifierType::makePublic), 11 | PRIVATE(ModifierType::makePrivate); 12 | 13 | private final Function transformer; 14 | 15 | ModifierType(Function transformer) { 16 | this.transformer = transformer; 17 | } 18 | 19 | private static int makePublic(int i) { 20 | return (i & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) | Opcodes.ACC_PUBLIC; 21 | } 22 | 23 | private static int makeProtected(int i) { 24 | if ((i & Opcodes.ACC_PUBLIC) != 0) { 25 | return i; 26 | } 27 | 28 | return (i & ~(Opcodes.ACC_PRIVATE)) | Opcodes.ACC_PROTECTED; 29 | } 30 | 31 | public static int removeFinal(int i) { 32 | return i & ~Opcodes.ACC_FINAL; 33 | } 34 | 35 | public static int addFinal(int i) { 36 | return i | Opcodes.ACC_FINAL; 37 | } 38 | 39 | private static int makePrivate(int i) { 40 | return (i & ~(Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED)) | Opcodes.ACC_PRIVATE; 41 | } 42 | 43 | public int apply(int i) { 44 | return transformer.apply(i); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/transformer/forge/TargetData.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.transformer.forge; 2 | 3 | public interface TargetData { 4 | String className(); 5 | 6 | ModifierType modifier(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/transformer/forge/TransformTarget.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.launch.transformer.forge; 2 | 3 | public record TransformTarget(TransformTarget.Type type, 4 | TargetData data, FinalModifierDefinition definition) { 5 | 6 | @Override 7 | public boolean equals(Object obj) { 8 | if (obj == null || getClass() != obj.getClass()) { 9 | return false; 10 | } 11 | 12 | TransformTarget other = (TransformTarget) obj; 13 | 14 | if (type != other.type) { 15 | return false; 16 | } 17 | 18 | if (type == Type.FIELD && data instanceof FieldData thisField && other.data instanceof FieldData otherField) { 19 | return thisField.className().equals(otherField.className()) 20 | && thisField.fieldName().equals(otherField.fieldName()) 21 | && thisField.modifier == otherField.modifier; 22 | } 23 | 24 | if (type == Type.CLASS && data instanceof ClassData thisClass && other.data instanceof ClassData otherClass) { 25 | return thisClass.className().equals(otherClass.className()) 26 | && thisClass.modifier == otherClass.modifier; 27 | } 28 | 29 | if (type == Type.METHOD && data instanceof MethodData thisMethod && other.data instanceof MethodData otherMethod) { 30 | return thisMethod.className().equals(otherMethod.className()) 31 | && thisMethod.methodDescriptor().equals(otherMethod.methodDescriptor()); 32 | } 33 | 34 | return false; 35 | } 36 | 37 | public enum Type { 38 | CLASS, METHOD, FIELD 39 | } 40 | 41 | public record ClassData(String className, ModifierType modifier) implements TargetData { 42 | } 43 | 44 | public record FieldData(String className, String fieldName, ModifierType modifier) implements TargetData { 45 | } 46 | 47 | public record MethodData(String className, String methodDescriptor, ModifierType modifier) implements TargetData { 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/launch/transformer/forge/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of the Forge/NeoForge access transformer, similar to fabric access wideners. 3 | */ 4 | package io.github.dueris.eclipse.loader.launch.transformer.forge; -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/minecraft/McVersionUtil.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.minecraft; 2 | 3 | import com.google.gson.stream.JsonReader; 4 | import com.google.gson.stream.JsonToken; 5 | import io.github.dueris.eclipse.api.McVersion; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.nio.charset.StandardCharsets; 13 | 14 | public final class McVersionUtil { 15 | 16 | private McVersionUtil() { 17 | } 18 | 19 | public static @Nullable McVersion fromVersionJson(InputStream is) { 20 | try (JsonReader reader = new JsonReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { 21 | String id = null; 22 | String name = null; 23 | int worldVersion = -1; 24 | String seriesId = null; 25 | int protocolVersion = -1; 26 | McVersion.PackVersion packVersion = null; 27 | String buildTime = null; 28 | String javaComponent = null; 29 | int javaVersion = -1; 30 | boolean stable = false; 31 | boolean useEditor = false; 32 | 33 | reader.beginObject(); 34 | 35 | while (reader.hasNext()) { 36 | switch (reader.nextName()) { 37 | case "id": 38 | if (reader.peek() != JsonToken.STRING) { 39 | throw new IOException("\"id\" in version json must be a string"); 40 | } 41 | id = reader.nextString(); 42 | break; 43 | case "name": 44 | if (reader.peek() != JsonToken.STRING) { 45 | throw new IOException("\"name\" in version json must be a string"); 46 | } 47 | name = reader.nextString(); 48 | break; 49 | case "world_version": 50 | worldVersion = reader.nextInt(); 51 | break; 52 | case "series_id": 53 | seriesId = reader.nextString(); 54 | break; 55 | case "protocol_version": 56 | protocolVersion = reader.nextInt(); 57 | break; 58 | case "pack_version": 59 | packVersion = readPackVersion(reader); 60 | break; 61 | case "build_time": 62 | buildTime = reader.nextString(); 63 | break; 64 | case "java_component": 65 | javaComponent = reader.nextString(); 66 | break; 67 | case "java_version": 68 | javaVersion = reader.nextInt(); 69 | break; 70 | case "stable": 71 | stable = reader.nextBoolean(); 72 | break; 73 | case "use_editor": 74 | useEditor = reader.nextBoolean(); 75 | break; 76 | default: 77 | reader.skipValue(); 78 | } 79 | } 80 | 81 | reader.endObject(); 82 | 83 | return new McVersion( 84 | id, name, worldVersion, seriesId, protocolVersion, packVersion, 85 | buildTime, javaComponent, javaVersion, stable, useEditor 86 | ); 87 | } catch (IOException e) { 88 | e.printStackTrace(); 89 | } 90 | return null; 91 | } 92 | 93 | private static McVersion.@NotNull PackVersion readPackVersion(@NotNull JsonReader reader) throws IOException { 94 | int resource = -1; 95 | int data = -1; 96 | 97 | reader.beginObject(); 98 | while (reader.hasNext()) { 99 | switch (reader.nextName()) { 100 | case "resource": 101 | resource = reader.nextInt(); 102 | break; 103 | case "data": 104 | data = reader.nextInt(); 105 | break; 106 | default: 107 | reader.skipValue(); 108 | } 109 | } 110 | reader.endObject(); 111 | return new McVersion.PackVersion(resource, data); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/minecraft/MinecraftGameProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.minecraft; 2 | 3 | import io.github.dueris.eclipse.api.GameLibrary; 4 | import io.github.dueris.eclipse.api.Launcher; 5 | import io.github.dueris.eclipse.api.McVersion; 6 | import io.github.dueris.eclipse.api.Transformer; 7 | import io.github.dueris.eclipse.api.game.GameProvider; 8 | import io.github.dueris.eclipse.api.util.BootstrapEntryContext; 9 | import io.github.dueris.eclipse.loader.MixinJavaAgent; 10 | import io.github.dueris.eclipse.loader.ember.patch.EmberTransformer; 11 | import io.github.dueris.eclipse.loader.util.LaunchException; 12 | import io.github.dueris.eclipse.plugin.access.EclipseMain; 13 | import io.github.dueris.eclipse.plugin.util.OptionSetUtils; 14 | import joptsimple.OptionSet; 15 | 16 | import java.io.IOException; 17 | import java.nio.file.Files; 18 | import java.nio.file.Path; 19 | import java.nio.file.Paths; 20 | import java.util.Map; 21 | import java.util.stream.Stream; 22 | 23 | public class MinecraftGameProvider implements GameProvider { 24 | private final String gameBrand; 25 | private final McVersion version; 26 | private final String mainClass; 27 | private final OptionSet optionSet; 28 | private final PaperclipJar paperclipJar; 29 | private EmberTransformer transformer; 30 | 31 | public MinecraftGameProvider() { 32 | Map properties = Launcher.getInstance().getProperties(); 33 | Path gameJarPath = (Path) properties.get("gamejar"); 34 | try { 35 | MixinJavaAgent.appendToClassPath(gameJarPath); 36 | } catch (final IOException exception) { 37 | throw new IllegalStateException("Unable to add paperclip jar to classpath!", exception); 38 | } 39 | 40 | try { 41 | PaperclipJar paperclipJar = new PaperclipJar(gameJarPath.toFile()); 42 | BootstrapEntryContext context = Launcher.getInstance().entryContext(); 43 | this.gameBrand = context.brand(); 44 | this.version = paperclipJar.mcVer(); 45 | this.mainClass = paperclipJar.getMainClass(); 46 | this.optionSet = OptionSetUtils.Serializer.deserialize(context.optionSet()); 47 | this.paperclipJar = paperclipJar; 48 | } catch (IOException e) { 49 | throw new RuntimeException("Unable to build paperclip jar!", e); 50 | } 51 | } 52 | 53 | @Override 54 | public Stream getLibraries() { 55 | return paperclipJar.gameRecord.libraries(); 56 | } 57 | 58 | @Override 59 | public Path getLaunchJar() { 60 | return paperclipJar.gameRecord.gamePath(); 61 | } 62 | 63 | @Override 64 | public String getGameId() { 65 | return "minecraft"; 66 | } 67 | 68 | @Override 69 | public String getGameName() { 70 | return gameBrand; 71 | } 72 | 73 | @Override 74 | public String getEntrypoint() { 75 | return mainClass; 76 | } 77 | 78 | @Override 79 | public Path getLaunchDirectory() { 80 | return Paths.get("."); 81 | } 82 | 83 | public void launch(ClassLoader loader) { 84 | try { 85 | final Path gameJar = (Path) Launcher.getInstance().getProperties().get("gamejar"); 86 | final String gameTarget = this.paperclipJar.getMainClass(); 87 | if (gameJar != null && Files.exists(gameJar)) { 88 | Object instance = Class.forName(gameTarget, true, loader).getConstructor().newInstance(); 89 | EclipseMain.class.getMethod("eclipse$main", OptionSet.class).invoke(instance, optionSet); 90 | } else { 91 | throw new IllegalStateException("No game jar was found to launch!"); 92 | } 93 | } catch (Throwable throwable) { 94 | throw new LaunchException("Unable to launch Minecraft server!", throwable); 95 | } 96 | } 97 | 98 | @Override 99 | public void prepareTransformer() { 100 | this.transformer = new EmberTransformer(); 101 | } 102 | 103 | @Override 104 | public Transformer getTransformer() { 105 | return this.transformer; 106 | } 107 | 108 | @Override 109 | public OptionSet getArguments() { 110 | return optionSet; 111 | } 112 | 113 | @Override 114 | public McVersion getVersion() { 115 | return version; 116 | } 117 | 118 | public PaperclipJar getPaperclipJar() { 119 | return paperclipJar; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/minecraft/PaperclipJar.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.minecraft; 2 | 3 | import io.github.dueris.eclipse.api.GameLibrary; 4 | import io.github.dueris.eclipse.api.Launcher; 5 | import io.github.dueris.eclipse.api.McVersion; 6 | import io.github.dueris.eclipse.loader.util.Util; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import javax.naming.InsufficientResourcesException; 10 | import java.io.*; 11 | import java.nio.file.Path; 12 | import java.nio.file.Paths; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | import java.util.concurrent.atomic.AtomicReference; 16 | import java.util.jar.JarEntry; 17 | import java.util.jar.JarFile; 18 | import java.util.stream.Stream; 19 | 20 | public class PaperclipJar extends JarFile { 21 | private final List libraries = new LinkedList<>(); 22 | protected GameRecord gameRecord; 23 | private String mainClass; 24 | private McVersion mcVer; 25 | 26 | public PaperclipJar(File file) throws IOException { 27 | super(file); 28 | try { 29 | prepareContext(); 30 | } catch (Throwable e) { 31 | e.printStackTrace(); 32 | throw new RuntimeException("Unable to prepare paperclip jar", e); 33 | } 34 | } 35 | 36 | private void prepareContext() throws Throwable { 37 | AtomicReference game = new AtomicReference<>(""); 38 | AtomicReference version = new AtomicReference<>(); 39 | final JarEntry versionJEntry = this.getJarEntry("version.json"); 40 | if (versionJEntry == null) { 41 | throw new InsufficientResourcesException("paperclip jar didn't contain a 'version.json'! (corrupted??)"); 42 | } 43 | 44 | final JarEntry versionListEntry = this.getJarEntry("META-INF/versions.list"); 45 | Util.consumePaperClipList((v) -> { 46 | version.set(v); 47 | game.set(String.format("./versions/%s", v)); 48 | }, versionListEntry, this); 49 | 50 | final JarEntry librariesEntry = this.getJarEntry("META-INF/libraries.list"); 51 | Util.consumePaperClipList(this::addLibrary, librariesEntry, this); 52 | 53 | final JarEntry mainClassEntry = this.getJarEntry("META-INF/main-class"); 54 | try (final InputStream inputStream = this.getInputStream(mainClassEntry); 55 | final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { 56 | mainClass = reader.readLine(); 57 | } 58 | 59 | Util.unloadNested(libraries); 60 | this.gameRecord = new GameRecord(game.get(), libraries.stream(), version.get()); 61 | mcVer = McVersionUtil.fromVersionJson(getInputStream(getJarEntry("version.json"))); 62 | Launcher.getInstance().getProperties().put("gamejar", this.gameRecord.gamePath()); 63 | } 64 | 65 | public String getMainClass() { 66 | return mainClass; 67 | } 68 | 69 | public List getLibraries() { 70 | return libraries; 71 | } 72 | 73 | private void addLibrary(String libraryString, String libraryPath) { 74 | Path path = Paths.get("./libraries/" + libraryPath); 75 | libraries.add(new GameLibrary(path, libraryString, path.toFile().exists())); 76 | } 77 | 78 | public McVersion mcVer() { 79 | return mcVer; 80 | } 81 | 82 | protected record GameRecord(String game, Stream libraries, 83 | String version) { 84 | 85 | public @NotNull Path gamePath() { 86 | return Paths.get(this.game); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/util/Getter.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.util; 2 | 3 | public interface Getter { 4 | T get(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/util/LaunchException.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.util; 2 | 3 | public class LaunchException extends RuntimeException { 4 | public LaunchException(String message, Throwable throwable) { 5 | super(message, throwable); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/util/Pair.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.util; 2 | 3 | public record Pair(T first, A second) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/util/ResourceConnection.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.util; 2 | 3 | import org.jetbrains.annotations.NotNull; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.net.URL; 9 | import java.net.URLConnection; 10 | import java.security.CodeSource; 11 | import java.util.function.Function; 12 | import java.util.jar.Manifest; 13 | 14 | public final class ResourceConnection implements AutoCloseable { 15 | private final URLConnection connection; 16 | private final InputStream stream; 17 | private final Function manifestFunction; 18 | private final Function sourceFunction; 19 | 20 | public ResourceConnection(final @NotNull URL url, 21 | final @NotNull Function<@NotNull URLConnection, @Nullable Manifest> manifestLocator, 22 | final @NotNull Function<@NotNull URLConnection, @Nullable CodeSource> sourceLocator) throws IOException { 23 | this.connection = url.openConnection(); 24 | this.stream = this.connection.getInputStream(); 25 | this.manifestFunction = manifestLocator; 26 | this.sourceFunction = sourceLocator; 27 | } 28 | 29 | public int contentLength() { 30 | return this.connection.getContentLength(); 31 | } 32 | 33 | public @NotNull InputStream stream() { 34 | return this.stream; 35 | } 36 | 37 | public @Nullable Manifest manifest() { 38 | return this.manifestFunction.apply(this.connection); 39 | } 40 | 41 | public @Nullable CodeSource source() { 42 | return this.sourceFunction.apply(this.connection); 43 | } 44 | 45 | @Override 46 | public void close() throws Exception { 47 | this.stream.close(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/util/mrj/AbstractClassLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.util.mrj; 2 | 3 | public abstract class AbstractClassLoader extends ClassLoader { 4 | public AbstractClassLoader(String name, ClassLoader parent) { 5 | super(name, parent); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/util/mrj/AbstractSecureClassLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.util.mrj; 2 | 3 | import java.security.SecureClassLoader; 4 | 5 | public abstract class AbstractSecureClassLoader extends SecureClassLoader { 6 | public AbstractSecureClassLoader(String name, ClassLoader parent) { 7 | super(name, parent); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/loader/util/mrj/AbstractUrlClassLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.loader.util.mrj; 2 | 3 | import java.net.URL; 4 | import java.net.URLClassLoader; 5 | 6 | public abstract class AbstractUrlClassLoader extends URLClassLoader { 7 | public AbstractUrlClassLoader(String name, URL[] urls, ClassLoader parent) { 8 | super(name, urls, parent); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/EclipsePlugin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin; 2 | 3 | import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; 4 | import org.bukkit.plugin.java.JavaPlugin; 5 | 6 | import java.util.Map; 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.atomic.AtomicReference; 9 | 10 | public final class EclipsePlugin extends JavaPlugin { 11 | public static final AtomicReference CURRENT_OPERATING_PROVIDER = new AtomicReference<>(); 12 | public static final Map PLUGIN_TO_PROVIDER = new ConcurrentHashMap<>(); 13 | public static boolean eclipse$allowsJars = false; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/access/EclipseMain.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.access; 2 | 3 | import joptsimple.OptionSet; 4 | 5 | public interface EclipseMain { 6 | void eclipse$main(OptionSet optionSet); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/access/MixinPlugin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.access; 2 | 3 | import io.github.dueris.eclipse.api.mod.ModContainer; 4 | import io.github.dueris.eclipse.api.mod.ModMetadata; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | /** 8 | * API for getting Ignite/Eclipse-related mod metadata 9 | * If the {@link ModContainer} or {@link ModMetadata} is null, its not an eclipse plugin 10 | */ 11 | public interface MixinPlugin { 12 | /** 13 | * Gets the {@link ModContainer} for this plugin instance 14 | */ 15 | @Nullable ModContainer eclipse$getModContainer(); 16 | 17 | /** 18 | * Gets the {@link ModMetadata} for this plugin instance 19 | */ 20 | @Nullable ModMetadata eclipse$getModConfig(); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/access/MixinPluginMeta.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.access; 2 | 3 | public interface MixinPluginMeta { 4 | boolean eclipse$isMixinPlugin(); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/access/PluginClassloaderHolder.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.access; 2 | 3 | import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; 4 | 5 | public interface PluginClassloaderHolder { 6 | PaperPluginClassLoader eclipse$getPluginClassLoader(); 7 | 8 | void eclipse$setPluginClassLoader(PaperPluginClassLoader loader); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/BootstrapMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import io.github.dueris.eclipse.api.entrypoint.BootstrapInitializer; 4 | import net.minecraft.server.Bootstrap; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | import org.spongepowered.asm.mixin.injection.Inject; 8 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 9 | 10 | @Mixin(Bootstrap.class) 11 | public class BootstrapMixin { 12 | 13 | @Inject(method = "bootStrap", at = @At(value = "INVOKE", target = "Ljava/time/Instant;now()Ljava/time/Instant;", ordinal = 0)) 14 | private static void eclipse$injectEntrypoint(CallbackInfo ci) { 15 | BootstrapInitializer.enter(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/DirectoryProviderSourceMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import io.github.dueris.eclipse.loader.Main; 4 | import io.papermc.paper.plugin.provider.source.DirectoryProviderSource; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | import org.spongepowered.asm.mixin.injection.Inject; 9 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 10 | 11 | import java.nio.file.Path; 12 | import java.util.List; 13 | 14 | @Mixin(DirectoryProviderSource.class) 15 | public class DirectoryProviderSourceMixin { 16 | 17 | @Inject(method = "lambda$prepareContext$1", at = @At("HEAD"), cancellable = true) 18 | private static void eclipse$dontLoadEclipse(List files, @NotNull Path path, CallbackInfo ci) { 19 | if (Main.ROOT_ABSOLUTE.toString().equalsIgnoreCase(path.toAbsolutePath().toString())) { 20 | ci.cancel(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/FolderRepositorySourceMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import io.github.dueris.eclipse.api.Launcher; 6 | import io.github.dueris.eclipse.api.mod.ModEngine; 7 | import io.github.dueris.eclipse.api.mod.ModMetadata; 8 | import io.github.dueris.eclipse.plugin.EclipsePlugin; 9 | import net.minecraft.network.chat.Component; 10 | import net.minecraft.server.packs.PackLocationInfo; 11 | import net.minecraft.server.packs.PackSelectionConfig; 12 | import net.minecraft.server.packs.PackType; 13 | import net.minecraft.server.packs.repository.FolderRepositorySource; 14 | import net.minecraft.server.packs.repository.Pack; 15 | import net.minecraft.server.packs.repository.PackSource; 16 | import net.minecraft.world.level.validation.DirectoryValidator; 17 | import org.jetbrains.annotations.NotNull; 18 | import org.slf4j.Logger; 19 | import org.spongepowered.asm.mixin.Final; 20 | import org.spongepowered.asm.mixin.Mixin; 21 | import org.spongepowered.asm.mixin.Shadow; 22 | import org.spongepowered.asm.mixin.Unique; 23 | import org.spongepowered.asm.mixin.injection.At; 24 | import org.spongepowered.asm.mixin.injection.Inject; 25 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 26 | 27 | import java.io.IOException; 28 | import java.nio.file.DirectoryStream; 29 | import java.nio.file.Path; 30 | import java.nio.file.Paths; 31 | import java.util.Iterator; 32 | import java.util.LinkedList; 33 | import java.util.Objects; 34 | import java.util.Optional; 35 | import java.util.concurrent.atomic.AtomicBoolean; 36 | import java.util.function.BiConsumer; 37 | import java.util.function.Consumer; 38 | 39 | @Mixin(FolderRepositorySource.class) 40 | public abstract class FolderRepositorySourceMixin { 41 | 42 | @Shadow 43 | @Final 44 | static Logger LOGGER; 45 | 46 | @Shadow 47 | @Final 48 | private static PackSelectionConfig DISCOVERED_PACK_SELECTION_CONFIG; 49 | 50 | @Shadow 51 | @Final 52 | private DirectoryValidator validator; 53 | 54 | @Shadow 55 | @Final 56 | private PackSource packSource; 57 | 58 | @Shadow 59 | @Final 60 | private PackType packType; 61 | 62 | @Shadow 63 | private static String nameFromPath(Path path) { 64 | return null; 65 | } 66 | 67 | @Shadow 68 | public static void discoverPacks(Path path, DirectoryValidator symlinkFinder, BiConsumer callback) throws IOException { 69 | } 70 | 71 | @SuppressWarnings("unchecked") 72 | @WrapOperation(method = "discoverPacks", at = @At(value = "INVOKE", target = "Ljava/nio/file/DirectoryStream;iterator()Ljava/util/Iterator;")) 73 | private static @NotNull Iterator eclipse$noDirectories(DirectoryStream instance, @NotNull Operation> original) { 74 | Iterator base = original.call(instance); 75 | if (!EclipsePlugin.eclipse$allowsJars) { 76 | return base; 77 | } 78 | 79 | LinkedList filteredBase = new LinkedList<>(); 80 | while (base.hasNext()) { 81 | Path path = base.next(); 82 | if (!path.toFile().isDirectory() && eclipse$filterJar(path)) { 83 | filteredBase.add(path); 84 | } 85 | } 86 | 87 | return (Iterator) filteredBase.iterator(); 88 | } 89 | 90 | /** 91 | * Returns true if the path is a valid pack repository plugin 92 | */ 93 | @Unique 94 | private static boolean eclipse$filterJar(Path path) { 95 | ModEngine modEngine = Launcher.getInstance().modEngine(); 96 | AtomicBoolean isValid = new AtomicBoolean(false); 97 | modEngine.containers().stream() 98 | .filter(p -> p.resource().path().toAbsolutePath().normalize().toString() 99 | .equals(path.toAbsolutePath().normalize().toString())) 100 | .findFirst().ifPresentOrElse(resource -> { 101 | ModMetadata config = resource.config(); 102 | if (config.datapackEntry()) { 103 | isValid.set(true); 104 | } 105 | }, () -> LOGGER.trace("Unable to locate mod in Ignite containers! : {}", path.getFileName())); 106 | return isValid.get(); 107 | } 108 | 109 | @Inject(method = "loadPacks", at = @At("HEAD")) 110 | private void eclipse$loadPluginPacks(Consumer profileAdder, CallbackInfo ci) { 111 | try { 112 | LOGGER.info("Loading plugin repositories..."); 113 | EclipsePlugin.eclipse$allowsJars = true; 114 | eclipse$loadDirectory(profileAdder, Paths.get("plugins")); 115 | eclipse$loadDirectory(profileAdder, Paths.get(".").toAbsolutePath().resolve("cache").resolve(".eclipse") 116 | .resolve("processedMods")); 117 | EclipsePlugin.eclipse$allowsJars = false; 118 | } catch (IOException e) { 119 | throw new RuntimeException("Unable to load plugins repository from Folder repo", e); 120 | } 121 | } 122 | 123 | @Unique 124 | private void eclipse$loadDirectory(Consumer profileAdder, Path directory) throws IOException { 125 | discoverPacks(directory, this.validator, (path, packFactory) -> { 126 | PackLocationInfo packLocationInfo = this.eclipse$createDiscoveredFilePackInfo(path); 127 | Pack pack = Pack.readMetaAndCreate(packLocationInfo, packFactory, this.packType, DISCOVERED_PACK_SELECTION_CONFIG); 128 | if (pack != null) { 129 | profileAdder.accept(pack); 130 | LOGGER.info("Loaded plugin repository: {}", pack.getId()); 131 | } 132 | }); 133 | } 134 | 135 | /** 136 | * Builds the plugin version of the pack information 137 | */ 138 | @Unique 139 | private @NotNull PackLocationInfo eclipse$createDiscoveredFilePackInfo(Path path) { 140 | String string = nameFromPath(path); 141 | return new PackLocationInfo("plugin/" + string, Component.literal(Objects.requireNonNull(string, "Name from path is null!")), this.packSource, Optional.empty()); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/MainMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import io.github.dueris.eclipse.api.entrypoint.EntrypointContainer; 4 | import io.github.dueris.eclipse.api.entrypoint.ModInitializer; 5 | import io.github.dueris.eclipse.plugin.access.EclipseMain; 6 | import joptsimple.OptionSet; 7 | import net.minecraft.SharedConstants; 8 | import org.bukkit.craftbukkit.Main; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | 12 | import java.io.File; 13 | 14 | @Mixin(value = Main.class, priority = 1) 15 | public class MainMixin implements EclipseMain { 16 | 17 | @Shadow 18 | public static boolean useJline; 19 | 20 | @Shadow 21 | public static boolean useConsole; 22 | 23 | @Override 24 | public void eclipse$main(OptionSet options) { 25 | String path = new File(".").getAbsolutePath(); 26 | if (path.contains("!") || path.contains("+")) { 27 | System.err.println("Cannot run server in a directory with ! or + in the pathname. Please rename the affected folders and try again."); 28 | return; 29 | } 30 | 31 | try { 32 | if (options.has("nojline")) { 33 | System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); 34 | useJline = false; 35 | } 36 | 37 | if (options.has("noconsole")) { 38 | Main.useConsole = false; 39 | useJline = false; 40 | System.setProperty(net.minecrell.terminalconsole.TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper 41 | } 42 | 43 | System.setProperty("library.jansi.version", "Paper"); 44 | System.setProperty("jdk.console", "java.base"); 45 | 46 | SharedConstants.tryDetectVersion(); 47 | 48 | EntrypointContainer.getEntrypoint(ModInitializer.class).enter(); 49 | net.minecraft.server.Main.main(options); 50 | } catch (Throwable t) { 51 | t.printStackTrace(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/ModernPluginLoadingStrategyMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import io.github.dueris.eclipse.plugin.EclipsePlugin; 6 | import io.papermc.paper.plugin.entrypoint.strategy.ProviderConfiguration; 7 | import io.papermc.paper.plugin.entrypoint.strategy.modern.ModernPluginLoadingStrategy; 8 | import io.papermc.paper.plugin.provider.PluginProvider; 9 | import io.papermc.paper.plugin.provider.entrypoint.DependencyContext; 10 | import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.spongepowered.asm.mixin.Mixin; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | 15 | /** 16 | * ModernPluginLoadingStrategy Mixin to help resolve the issues with plugin loading /w mixin plugins 17 | */ 18 | @Mixin(ModernPluginLoadingStrategy.class) 19 | public class ModernPluginLoadingStrategyMixin { 20 | 21 | @WrapOperation(method = "loadProviders", at = @At(value = "INVOKE", target = "Lio/papermc/paper/plugin/entrypoint/strategy/ProviderConfiguration;applyContext(Lio/papermc/paper/plugin/provider/PluginProvider;Lio/papermc/paper/plugin/provider/entrypoint/DependencyContext;)V")) 22 | public void cacheCurrentProvider(ProviderConfiguration instance, PluginProvider tPluginProvider, DependencyContext dependencyContext, @NotNull Operation original) { 23 | original.call(instance, tPluginProvider, dependencyContext); 24 | if (tPluginProvider instanceof PaperPluginParent.PaperServerPluginProvider pluginProvider) { 25 | EclipsePlugin.CURRENT_OPERATING_PROVIDER.set(pluginProvider); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/PackDetectorMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 5 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 6 | import net.minecraft.server.packs.repository.PackDetector; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.injection.At; 9 | 10 | import static io.github.dueris.eclipse.plugin.EclipsePlugin.eclipse$allowsJars; 11 | 12 | @Mixin(PackDetector.class) 13 | public class PackDetectorMixin { 14 | 15 | @WrapOperation(method = "detectPackResources", at = @At(value = "INVOKE", target = "Ljava/lang/String;endsWith(Ljava/lang/String;)Z")) 16 | public boolean eclipse$allowJars(String instance, String suffix, Operation original) { 17 | if (eclipse$allowsJars && suffix.equalsIgnoreCase(".zip")) { 18 | return original.call(instance, ".jar"); 19 | } 20 | return original.call(instance, suffix); 21 | } 22 | 23 | // Basically ignore any directories during jar analysis 24 | @ModifyExpressionValue(method = "detectPackResources", at = @At(value = "INVOKE", target = "Ljava/nio/file/attribute/BasicFileAttributes;isDirectory()Z")) 25 | public boolean eclipse$ignoreDirectories(boolean original) { 26 | if (original && eclipse$allowsJars) { 27 | return false; 28 | } 29 | return original; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/PaperClasspathBuilderMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import io.github.dueris.eclipse.loader.MixinJavaAgent; 4 | import io.github.dueris.eclipse.plugin.access.MixinPluginMeta; 5 | import io.papermc.paper.plugin.PluginInitializerManager; 6 | import io.papermc.paper.plugin.bootstrap.PluginProviderContext; 7 | import io.papermc.paper.plugin.loader.PaperClasspathBuilder; 8 | import io.papermc.paper.plugin.loader.PluginClasspathBuilder; 9 | import io.papermc.paper.plugin.loader.library.ClassPathLibrary; 10 | import io.papermc.paper.plugin.loader.library.PaperLibraryStore; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.spongepowered.asm.mixin.Final; 13 | import org.spongepowered.asm.mixin.Mixin; 14 | import org.spongepowered.asm.mixin.Shadow; 15 | import org.spongepowered.asm.mixin.Unique; 16 | import org.spongepowered.asm.mixin.injection.At; 17 | import org.spongepowered.asm.mixin.injection.Inject; 18 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 19 | 20 | import java.nio.file.Path; 21 | import java.util.List; 22 | 23 | @SuppressWarnings("UnstableApiUsage") 24 | @Mixin(PaperClasspathBuilder.class) 25 | public class PaperClasspathBuilderMixin { 26 | 27 | @Shadow 28 | @Final 29 | private PluginProviderContext context; 30 | 31 | @Inject(method = "addLibrary", at = @At("HEAD")) 32 | public void loadToMixinAgent(ClassPathLibrary classPathLibrary, CallbackInfoReturnable cir) { 33 | if (context.getConfiguration() instanceof MixinPluginMeta mixinPluginMeta && mixinPluginMeta.eclipse$isMixinPlugin()) { 34 | eclipse$buildLibraryPaths(true, classPathLibrary).forEach((path) -> { 35 | try { 36 | MixinJavaAgent.appendToClassPath(path); 37 | } catch (Throwable throwable) { 38 | throw new RuntimeException("Unable to append classpath library to ignite classpath!", throwable); 39 | } 40 | }); 41 | } 42 | } 43 | 44 | @Unique 45 | public List eclipse$buildLibraryPaths(final boolean remap, @NotNull ClassPathLibrary library) { 46 | PaperLibraryStore paperLibraryStore = new PaperLibraryStore(); 47 | library.register(paperLibraryStore); 48 | 49 | List paths = paperLibraryStore.getPaths(); 50 | if (remap && PluginInitializerManager.instance().pluginRemapper != null) { 51 | paths = PluginInitializerManager.instance().pluginRemapper.remapLibraries(paths); 52 | } 53 | return paths; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/PaperPluginMetaMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import io.github.dueris.eclipse.api.Launcher; 4 | import io.github.dueris.eclipse.plugin.access.MixinPluginMeta; 5 | import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.spongepowered.asm.mixin.Mixin; 8 | import org.spongepowered.asm.mixin.Shadow; 9 | 10 | @Mixin(PaperPluginMeta.class) 11 | public abstract class PaperPluginMetaMixin implements MixinPluginMeta { 12 | @Shadow 13 | public abstract @NotNull String getName(); 14 | 15 | @Override 16 | public boolean eclipse$isMixinPlugin() { 17 | return Launcher.getInstance().modEngine().loaded(getName().toLowerCase()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/PaperPluginParentMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import io.github.dueris.eclipse.plugin.access.PluginClassloaderHolder; 4 | import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; 5 | import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.spongepowered.asm.mixin.Final; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | import org.spongepowered.asm.mixin.injection.Inject; 12 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 13 | 14 | /** 15 | * PaperPluginParent Mixin for saving the {@link PaperPluginClassLoader} in the {@link io.papermc.paper.plugin.provider.type.paper.PaperPluginParent.PaperServerPluginProvider} to help fix plugin loading errors 16 | */ 17 | @Mixin(PaperPluginParent.class) 18 | public class PaperPluginParentMixin { 19 | 20 | @Shadow 21 | @Final 22 | private PaperPluginClassLoader classLoader; 23 | 24 | @Inject(method = "createPluginProvider", at = @At("RETURN")) 25 | public void eclipse$setPluginClassloader(PaperPluginParent.PaperBootstrapProvider provider, @NotNull CallbackInfoReturnable cir) { 26 | PaperPluginParent.PaperServerPluginProvider pluginProvider = cir.getReturnValue(); 27 | if (pluginProvider instanceof PluginClassloaderHolder holder) { 28 | holder.eclipse$setPluginClassLoader(classLoader); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/PaperPluginsCommandMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.ModifyExpressionValue; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 5 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 6 | import com.llamalad7.mixinextras.sugar.Share; 7 | import com.llamalad7.mixinextras.sugar.ref.LocalRef; 8 | import io.github.dueris.eclipse.plugin.access.MixinPluginMeta; 9 | import io.papermc.paper.command.PaperPluginsCommand; 10 | import io.papermc.paper.plugin.provider.PluginProvider; 11 | import net.kyori.adventure.text.Component; 12 | import net.kyori.adventure.text.format.TextColor; 13 | import org.bukkit.command.CommandSender; 14 | import org.bukkit.command.defaults.BukkitCommand; 15 | import org.bukkit.plugin.java.JavaPlugin; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.spongepowered.asm.mixin.Mixin; 18 | import org.spongepowered.asm.mixin.Shadow; 19 | import org.spongepowered.asm.mixin.Unique; 20 | import org.spongepowered.asm.mixin.injection.At; 21 | import org.spongepowered.asm.mixin.injection.Inject; 22 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; 23 | 24 | import java.util.List; 25 | import java.util.TreeMap; 26 | 27 | @Mixin(PaperPluginsCommand.class) 28 | public abstract class PaperPluginsCommandMixin extends BukkitCommand { 29 | 30 | @Unique 31 | private static final Component ECLIPSE_HEADER = Component.text("Eclipse Plugins:", TextColor.color(235, 186, 16)); 32 | 33 | protected PaperPluginsCommandMixin(@NotNull String name) { 34 | super(name); 35 | } 36 | 37 | @Shadow 38 | private static List formatProviders(TreeMap> plugins) { 39 | return null; 40 | } 41 | 42 | @Inject(method = "execute", at = @At("HEAD")) 43 | public void eclipse$newTreeMap(CommandSender sender, String currentAlias, String[] args, CallbackInfoReturnable cir, @Share("eclipsePlugins") @NotNull LocalRef>> eclipsePlugins) { 44 | eclipsePlugins.set(new TreeMap<>(String.CASE_INSENSITIVE_ORDER)); 45 | } 46 | 47 | @SuppressWarnings("unchecked") 48 | @WrapOperation(method = "execute", at = @At(value = "INVOKE", target = "Ljava/util/TreeMap;put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", ordinal = 1)) 49 | public V eclipse$provideEclipseTreeMap(TreeMap instance, K key, V value, Operation original, @Share("eclipsePlugins") @NotNull LocalRef>> eclipsePlugins) { 50 | TreeMap eclipseTreeMap = eclipsePlugins.get(); 51 | 52 | String k = (String) key; 53 | PluginProvider v = (PluginProvider) value; 54 | 55 | if (((MixinPluginMeta) v.getMeta()).eclipse$isMixinPlugin()) { 56 | return (V) eclipseTreeMap.put(k, v); 57 | } else { 58 | return original.call(instance, key, value); 59 | } 60 | 61 | } 62 | 63 | @ModifyExpressionValue(method = "execute", at = @At(value = "INVOKE", target = "Ljava/util/TreeMap;size()I", ordinal = 1)) 64 | public int eclipse$includeEclipseTreeMap(int original, @Share("eclipsePlugins") @NotNull LocalRef>> eclipsePlugins) { 65 | return original + eclipsePlugins.get().size(); 66 | } 67 | 68 | @Inject(method = "execute", at = @At(value = "INVOKE", target = "Lorg/bukkit/command/CommandSender;sendMessage(Lnet/kyori/adventure/text/Component;)V", ordinal = 0, shift = At.Shift.AFTER)) 69 | public void eclipse$sendEclipseMessage(CommandSender sender, String currentAlias, String[] args, CallbackInfoReturnable cir, @Share("eclipsePlugins") @NotNull LocalRef>> eclipsePlugins) { 70 | TreeMap> eclipseTreeMap = eclipsePlugins.get(); 71 | 72 | if (!eclipseTreeMap.isEmpty()) { 73 | sender.sendMessage(ECLIPSE_HEADER); 74 | } 75 | 76 | for (Component component : formatProviders(eclipseTreeMap)) { 77 | sender.sendMessage(component); 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/PaperServerPluginProviderMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import io.github.dueris.eclipse.plugin.EclipsePlugin; 6 | import io.github.dueris.eclipse.plugin.access.PluginClassloaderHolder; 7 | import io.papermc.paper.plugin.entrypoint.classloader.PaperPluginClassLoader; 8 | import io.papermc.paper.plugin.provider.configuration.PaperPluginMeta; 9 | import io.papermc.paper.plugin.provider.type.paper.PaperPluginParent; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.spongepowered.asm.mixin.Mixin; 12 | import org.spongepowered.asm.mixin.Unique; 13 | import org.spongepowered.asm.mixin.injection.At; 14 | 15 | /** 16 | * PaperServerPluginProvider Mixin for caching/getting the plugin classloader 17 | */ 18 | @Mixin(PaperPluginParent.PaperServerPluginProvider.class) 19 | public class PaperServerPluginProviderMixin implements PluginClassloaderHolder { 20 | 21 | @Unique 22 | private PaperPluginClassLoader eclipse$paperPluginClassLoader; 23 | 24 | @Override 25 | public PaperPluginClassLoader eclipse$getPluginClassLoader() { 26 | return eclipse$paperPluginClassLoader; 27 | } 28 | 29 | @Override 30 | public void eclipse$setPluginClassLoader(PaperPluginClassLoader loader) { 31 | eclipse$paperPluginClassLoader = loader; 32 | } 33 | 34 | @WrapOperation(method = "createInstance()Lorg/bukkit/plugin/java/JavaPlugin;", at = @At(value = "INVOKE", target = "Lio/papermc/paper/plugin/provider/configuration/PaperPluginMeta;getMainClass()Ljava/lang/String;", ordinal = 0)) 35 | public String eclipse$storePlugin(PaperPluginMeta instance, @NotNull Operation original) { 36 | String main = original.call(instance); 37 | EclipsePlugin.PLUGIN_TO_PROVIDER.put(main, (PaperPluginParent.PaperServerPluginProvider) (Object) this); 38 | return main; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/PluginInitializerManagerMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import io.papermc.paper.plugin.PluginInitializerManager; 4 | import io.papermc.paper.plugin.provider.source.DirectoryProviderSource; 5 | import io.papermc.paper.plugin.util.EntrypointUtil; 6 | import joptsimple.OptionSet; 7 | import org.slf4j.Logger; 8 | import org.spongepowered.asm.mixin.Final; 9 | import org.spongepowered.asm.mixin.Mixin; 10 | import org.spongepowered.asm.mixin.Shadow; 11 | import org.spongepowered.asm.mixin.injection.At; 12 | import org.spongepowered.asm.mixin.injection.Inject; 13 | import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; 14 | 15 | import java.nio.file.Paths; 16 | 17 | @Mixin(PluginInitializerManager.class) 18 | public class PluginInitializerManagerMixin { 19 | 20 | @Shadow 21 | @Final 22 | private static Logger LOGGER; 23 | 24 | @Inject(method = "load", at = @At(value = "INVOKE", target = "Lio/papermc/paper/plugin/util/EntrypointUtil;registerProvidersFromSource(Lio/papermc/paper/plugin/provider/source/ProviderSource;Ljava/lang/Object;)V", ordinal = 0, shift = At.Shift.AFTER)) 25 | private static void eclipse$loadProcessedPlugins(OptionSet optionSet, CallbackInfo ci) { 26 | LOGGER.info("Loading processed mods into plugin entrypoints..."); 27 | EntrypointUtil.registerProvidersFromSource(DirectoryProviderSource.INSTANCE, Paths.get(".").toAbsolutePath() 28 | .resolve("cache") 29 | .resolve(".eclipse") 30 | .resolve("processedMods")); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/PluginRemapperMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import io.github.dueris.eclipse.plugin.util.FileDeleterVisitor; 6 | import io.papermc.paper.pluginremap.PluginRemapper; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.spongepowered.asm.mixin.Mixin; 9 | import org.spongepowered.asm.mixin.Shadow; 10 | import org.spongepowered.asm.mixin.injection.At; 11 | 12 | import java.io.IOException; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | 16 | @Mixin(PluginRemapper.class) 17 | public class PluginRemapperMixin { 18 | @Shadow 19 | private static String PAPER_REMAPPED; 20 | 21 | // This is marked as mutable via wideners 22 | @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Ljava/nio/file/Path;resolve(Ljava/lang/String;)Ljava/nio/file/Path;", ordinal = 0)) 23 | public Path eclipse$changeRemappedDirectory(Path instance, String other, @NotNull Operation original) { 24 | PAPER_REMAPPED = ".eclipse-remapped"; 25 | Path remappedPath = original.call(instance, PAPER_REMAPPED).toAbsolutePath().normalize(); 26 | if (remappedPath.toFile().exists()) { 27 | try { 28 | Files.walkFileTree(remappedPath, new FileDeleterVisitor()); 29 | } catch (IOException e) { 30 | throw new RuntimeException("Unable to clear remapped cache!", e); 31 | } 32 | } 33 | return remappedPath; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/RemappedPluginIndexMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import org.spongepowered.asm.mixin.Mixin; 6 | import org.spongepowered.asm.mixin.injection.At; 7 | 8 | import java.nio.file.Path; 9 | 10 | @Mixin(targets = "io.papermc.paper.pluginremap.RemappedPluginIndex") 11 | public class RemappedPluginIndexMixin { 12 | 13 | @WrapOperation(method = "getAllIfPresent", at = @At(value = "INVOKE", target = "Ljava/nio/file/Files;deleteIfExists(Ljava/nio/file/Path;)Z")) 14 | public boolean eclipse$no(Path path, Operation original) { 15 | return true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/mixin/SysoutCatcherMixin.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.mixin; 2 | 3 | import com.llamalad7.mixinextras.injector.wrapoperation.Operation; 4 | import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 5 | import io.papermc.paper.logging.SysoutCatcher; 6 | import org.spongepowered.asm.mixin.Mixin; 7 | import org.spongepowered.asm.mixin.injection.At; 8 | 9 | import java.io.PrintStream; 10 | 11 | @Mixin(SysoutCatcher.class) 12 | public class SysoutCatcherMixin { 13 | 14 | @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Ljava/lang/System;setOut(Ljava/io/PrintStream;)V")) 15 | public void eclipse$no(PrintStream out, Operation original) { 16 | } 17 | 18 | @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Ljava/lang/System;setErr(Ljava/io/PrintStream;)V")) 19 | public void eclipse$alsoNo(PrintStream err, Operation original) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/util/DependencyLoader.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.util; 2 | 3 | import io.github.dueris.eclipse.loader.Main; 4 | import io.papermc.paper.plugin.loader.PluginClasspathBuilder; 5 | import io.papermc.paper.plugin.loader.PluginLoader; 6 | import io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver; 7 | import org.eclipse.aether.artifact.DefaultArtifact; 8 | import org.eclipse.aether.graph.Dependency; 9 | import org.eclipse.aether.repository.RemoteRepository; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.util.List; 13 | 14 | @SuppressWarnings("UnstableApiUsage") 15 | public class DependencyLoader implements PluginLoader { 16 | 17 | @Override 18 | public void classloader(@NotNull PluginClasspathBuilder classpathBuilder) { 19 | if (Main.BOOTED.get()) { 20 | return; 21 | } 22 | MavenLibraryResolver resolver = new MavenLibraryResolver(); 23 | 24 | maven(resolver, "https://repo.papermc.io/repository/maven-public/"); 25 | maven(resolver, "https://oss.sonatype.org/content/groups/public/"); 26 | maven(resolver, "https://repo.opencollab.dev/main/"); 27 | maven(resolver, "https://repo.extendedclip.com/content/repositories/placeholderapi/"); 28 | maven(resolver, "https://repo.inventivetalent.org/repository/public/"); 29 | maven(resolver, "https://repo.codemc.org/repository/maven-releases/"); 30 | maven(resolver, "https://maven.quiltmc.org/repository/release/"); 31 | maven(resolver, "https://maven.fabricmc.net/"); 32 | 33 | for (String o : List.of("org.ow2.asm:asm:9.7", "org.ow2.asm:asm-util:9.7", "com.github.olivergondza:maven-jdk-tools-wrapper:0.1", 34 | "org.javassist:javassist:3.30.2-GA", "net.bytebuddy:byte-buddy-agent:1.14.19", "io.github.kasukusakura:jvm-self-attach:0.0.1", 35 | "io.github.karlatemp:unsafe-accessor:1.7.0", "org.eclipse.jgit:org.eclipse.jgit:6.10.0.202406032230-r")) { 36 | resolver.addDependency(new Dependency(new DefaultArtifact(o), null)); 37 | } 38 | classpathBuilder.addLibrary(resolver); 39 | } 40 | 41 | private void maven(@NotNull MavenLibraryResolver resolver, String url) { 42 | resolver.addRepository(new RemoteRepository.Builder(url.replace("https://", "") 43 | .split("/")[0], "default", url).build()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/util/FileDeleterVisitor.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.util; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.FileVisitResult; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.SimpleFileVisitor; 8 | import java.nio.file.attribute.BasicFileAttributes; 9 | 10 | public class FileDeleterVisitor extends SimpleFileVisitor { 11 | @Override 12 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 13 | Files.delete(file); // Delete each file 14 | return FileVisitResult.CONTINUE; 15 | } 16 | 17 | @Override 18 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 19 | Files.delete(dir); // Delete the directory after its contents 20 | return FileVisitResult.CONTINUE; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/github/dueris/eclipse/plugin/util/Injectors.java: -------------------------------------------------------------------------------- 1 | package io.github.dueris.eclipse.plugin.util; 2 | 3 | import com.dragoncommissions.mixbukkit.api.shellcode.impl.api.CallbackInfo; 4 | import joptsimple.OptionSet; 5 | 6 | import java.nio.file.Path; 7 | 8 | public class Injectors { 9 | 10 | public static void eclipseLoadWrapper(Path path, OptionSet optionset, CallbackInfo info) { 11 | BootstrapEntrypoint.logger.info("Starting Eclipse bootstrap..."); 12 | BootstrapEntrypoint entrypoint = new BootstrapEntrypoint(); 13 | try { 14 | entrypoint.prepLaunch(); 15 | entrypoint.executePlugin( 16 | BootstrapEntrypoint.CONTEXT.getPluginSource().toFile(), optionset 17 | ); 18 | } catch (Exception ea) { 19 | throw new RuntimeException("Unable to launch eclipse server!", ea); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.github.dueris.eclipse.loader.ember.patch.GamePatch: -------------------------------------------------------------------------------- 1 | io.github.dueris.eclipse.loader.launch.patch.BrandingPatch 2 | io.github.dueris.eclipse.loader.launch.patch.CraftServerNamePatch -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.github.dueris.eclipse.loader.ember.patch.TransformerService: -------------------------------------------------------------------------------- 1 | io.github.dueris.eclipse.loader.launch.transformer.AccessWidenerTransformer 2 | io.github.dueris.eclipse.loader.launch.transformer.ForgeTransformerService 3 | io.github.dueris.eclipse.loader.launch.transformer.MixinTransformer 4 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.spongepowered.asm.service.IGlobalPropertyService: -------------------------------------------------------------------------------- 1 | io.github.dueris.eclipse.loader.launch.property.MixinGlobalPropertyService 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinService: -------------------------------------------------------------------------------- 1 | io.github.dueris.eclipse.loader.ember.EmberMixinService 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.spongepowered.asm.service.IMixinServiceBootstrap: -------------------------------------------------------------------------------- 1 | io.github.dueris.eclipse.loader.ember.mixin.EmberMixinBootstrap 2 | -------------------------------------------------------------------------------- /src/main/resources/eclipse.mixins.json: -------------------------------------------------------------------------------- 1 | { 2 | "mixins": [ 3 | "BootstrapMixin", 4 | "DirectoryProviderSourceMixin", 5 | "FolderRepositorySourceMixin", 6 | "JavaPluginMixin", 7 | "MainMixin", 8 | "ModernPluginLoadingStrategyMixin", 9 | "PackDetectorMixin", 10 | "PaperClasspathBuilderMixin", 11 | "PaperPluginMetaMixin", 12 | "PaperPluginParentMixin", 13 | "PaperPluginsCommandMixin", 14 | "PaperServerPluginProviderMixin", 15 | "PluginInitializerManagerMixin", 16 | "PluginRemapperMixin", 17 | "RemappedPluginIndexMixin", 18 | "SysoutCatcherMixin" 19 | ], 20 | "package": "io.github.dueris.eclipse.plugin.mixin", 21 | "compatibilityLevel": "JAVA_21" 22 | } -------------------------------------------------------------------------------- /src/main/resources/paper-plugin.yml: -------------------------------------------------------------------------------- 1 | name: Eclipse 2 | version: '2.0.4-SNAPSHOT' 3 | main: io.github.dueris.eclipse.plugin.EclipsePlugin 4 | api-version: '1.21' 5 | prefix: EclipseMixin 6 | load: STARTUP 7 | authors: [ Dueris ] 8 | description: Mixin Launcher for Paper servers 9 | 10 | bootstrapper: io.github.dueris.eclipse.plugin.util.BootstrapEntrypoint 11 | loader: io.github.dueris.eclipse.plugin.util.DependencyLoader 12 | has-open-classloader: true 13 | 14 | mixins: [ 15 | "eclipse.mixins.json" 16 | ] 17 | wideners: [ 18 | "eclipse.accesswidener" 19 | ] 20 | 21 | datapack-entry: false -------------------------------------------------------------------------------- /src/main/resources/tinylog.properties: -------------------------------------------------------------------------------- 1 | # https://tinylog.org/v2/configuration/ 2 | # The modEngine uses this to avoid any conflicts with Log4j used in the server environment 3 | writerConsole=console 4 | writerConsole.level=info 5 | writerConsole.format=[{date:HH:mm:ss}] [{thread}/{level}]: {message} 6 | writerFile=rolling file 7 | writerFile.level=info 8 | writerFile.format=[{date:HH:mm:ss}] [{thread}/{level}]: {message} 9 | writerFile.file=logs/eclipse_{date:yyyy-MM-dd}-{count}.log 10 | writerFile.latest=logs/latest_eclipse.log 11 | writerFile.charset=UTF-8 12 | writerFile.convert=gzip 13 | writerFile.policy=daily, startup 14 | writerDebug=rolling file 15 | writerDebug.level=trace 16 | writerDebug.format=[{date:HH:mm:ss}] [{thread}/{level}]: {message} 17 | writerDebug.file=logs/debug_eclipse-{count}.log 18 | writerDebug.latest=logs/debug_mixin.log 19 | writerDebug.charset=UTF-8 20 | writerDebug.convert=gzip 21 | writerDebug.backups=5 22 | writerDebug.policy=startup, size: 200mb 23 | --------------------------------------------------------------------------------