Skip to content

Commit ab257f0

Browse files
Nep NepAvanatiker
andauthored
Add mixin support to plugins (#218)
* Initial take on plugin mixins * Reminder * Removed hot_reload flag. Added mixin path list. Updated dependencies Co-authored-by: Constructor <fractalminds@protonmail.com>
1 parent c61eed0 commit ab257f0

File tree

7 files changed

+56
-27
lines changed

7 files changed

+56
-27
lines changed

src/main/kotlin/com/lambda/client/LambdaCoreMod.kt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package com.lambda.client
22

3+
import com.lambda.client.plugin.PluginManager
34
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin
4-
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.MCVersion
55
import org.apache.logging.log4j.LogManager
66
import org.spongepowered.asm.launch.MixinBootstrap
77
import org.spongepowered.asm.mixin.MixinEnvironment
88
import org.spongepowered.asm.mixin.Mixins
99

1010
@IFMLLoadingPlugin.Name("LambdaCoreMod")
11-
@MCVersion("1.12.2")
11+
@IFMLLoadingPlugin.MCVersion("1.12.2")
1212
class LambdaCoreMod : IFMLLoadingPlugin {
1313
override fun getASMTransformerClass(): Array<String> {
1414
return emptyArray()
@@ -22,7 +22,6 @@ class LambdaCoreMod : IFMLLoadingPlugin {
2222
return null
2323
}
2424

25-
2625
override fun injectData(data: Map<String, Any>) {}
2726

2827
override fun getAccessTransformerClass(): String? {
@@ -34,6 +33,14 @@ class LambdaCoreMod : IFMLLoadingPlugin {
3433

3534
MixinBootstrap.init()
3635
Mixins.addConfigurations("mixins.lambda.json", "mixins.baritone.json")
36+
37+
PluginManager.getLoaders()
38+
.filter { it.info.mixins.isNotEmpty() }
39+
.forEach {
40+
logger.info("Initialised mixins of ${it.info.name}.")
41+
Mixins.addConfigurations(*it.info.mixins)
42+
}
43+
3744
MixinEnvironment.getDefaultEnvironment().obfuscationContext = "searge"
3845
logger.info("Lambda and Baritone mixins initialised.")
3946
}

src/main/kotlin/com/lambda/client/gui/clickgui/component/PluginButton.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class PluginButton(var plugin: Plugin, var file: File) : BooleanSlider(plugin.na
4040
super.onClick(mousePos, buttonId)
4141
if (buttonId == 0) {
4242
if (isLoaded) {
43-
PluginManager.unload(plugin)
43+
if (!PluginManager.unload(plugin)) return
4444
isLoaded = false
4545
} else {
4646
PluginManager.load(PluginLoader(file))

src/main/kotlin/com/lambda/client/plugin/PluginError.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@ internal enum class PluginError {
3535
companion object {
3636
private var latestErrors: ArrayList<Pair<PluginLoader, PluginError>>? = null
3737

38-
fun log(message: String?, throwable: Throwable? = null) {
38+
fun log(message: String?, shouldChat: Boolean = true, throwable: Throwable? = null) {
3939
message?.let {
40-
MessageSendHelper.sendErrorMessage("[Plugin Manager] $it")
40+
// DO NOT ACCESS MINECRAFT IN COREMODSPACE
41+
if (shouldChat) {
42+
MessageSendHelper.sendErrorMessage("[Plugin Manager] $it")
43+
}
4144

4245
if (throwable != null) {
4346
LambdaMod.LOG.error(message, throwable)

src/main/kotlin/com/lambda/client/plugin/PluginInfo.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class PluginInfo private constructor(
1515
@SerializedName("min_api_version") private val minApiVersion0: String?,
1616
@SerializedName("required_plugins") private val requiredPlugins0: Array<String>?,
1717
@SerializedName("main_class") private val mainClass0: String?,
18-
@SerializedName("hot_reload") private val hotReload0: Boolean?
18+
@SerializedName("mixins") private val mixins0: Array<String>?
1919
) : Nameable {
2020

2121
/** The name of the plugin, will be used as both an identifier and a display name */
@@ -43,7 +43,7 @@ class PluginInfo private constructor(
4343
val mainClass: String get() = mainClass0.nonBlank("main_class")
4444

4545
/** Whether this plugin can be hot reloaded or not, this should be false if the plugin uses mixin */
46-
val hotReload: Boolean get() = hotReload0 ?: true
46+
val mixins: Array<String> get() = mixins0 ?: mixinsNull
4747

4848
private fun String?.nonBlank(name: String = "String") =
4949
when {
@@ -71,6 +71,7 @@ class PluginInfo private constructor(
7171
private const val descriptionNull: String = "No Description"
7272
private const val urlNull: String = "No Url"
7373
private val requiredPluginsNull: Array<String> = emptyArray()
74+
private val mixinsNull: Array<String> = emptyArray()
7475

7576
private val gson = Gson()
7677

src/main/kotlin/com/lambda/client/plugin/PluginLoader.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.lambda.client.LambdaMod
66
import com.lambda.client.plugin.api.Plugin
77
import com.lambda.commons.interfaces.Nameable
88
import com.lambda.commons.utils.ClassUtils.instance
9+
import net.minecraft.launchwrapper.Launch
910
import java.io.File
1011
import java.io.FileNotFoundException
1112
import java.lang.reflect.Type
@@ -18,7 +19,7 @@ class PluginLoader(
1819

1920
override val name: String get() = info.name
2021

21-
private val loader = PluginClassLoader(JarFile(file), this.javaClass.classLoader)
22+
private var loader: ClassLoader = PluginClassLoader(JarFile(file), this.javaClass.classLoader)
2223
val info: PluginInfo = loader.getResourceAsStream("plugin_info.json")?.let {
2324
PluginInfo.fromStream(it)
2425
} ?: throw FileNotFoundException("plugin_info.json not found in jar ${file.name}!")
@@ -27,6 +28,13 @@ class PluginLoader(
2728
// This will trigger the null checks in PluginInfo
2829
// In order to make sure all required infos are present
2930
info.toString()
31+
32+
if (info.mixins.isNotEmpty()) {
33+
Launch.classLoader.addURL(file.toURI().toURL())
34+
// May not be necessary, a consistency thing for now
35+
closeWithoutCheck()
36+
loader = Launch.classLoader
37+
}
3038
}
3139

3240
fun verify(): Boolean {
@@ -42,14 +50,14 @@ class PluginLoader(
4250
toString()
4351
}
4452

45-
// ToDo: Do no spam when in Lambda menu
53+
// ToDo: Use plugin database to verify trusted files
4654
// LambdaMod.LOG.info("SHA-256 checksum for ${file.name}: $result")
4755

4856
return checksumSets.contains(result)
4957
}
5058

5159
fun load(): Plugin {
52-
if (LambdaMod.ready && !info.hotReload) {
60+
if (LambdaMod.ready && info.mixins.isNotEmpty()) {
5361
throw IllegalAccessException("Plugin $this cannot be hot reloaded!")
5462
}
5563

@@ -68,13 +76,19 @@ class PluginLoader(
6876
}
6977

7078
fun close() {
71-
loader.close()
79+
if (info.mixins.isEmpty()) {
80+
closeWithoutCheck()
81+
}
7282
}
7383

7484
override fun toString(): String {
7585
return "${runCatching { info.name }.getOrDefault("Unknown Plugin")}(${file.name})"
7686
}
7787

88+
private fun closeWithoutCheck() {
89+
(loader as PluginClassLoader).close()
90+
}
91+
7892
private companion object {
7993
val sha256: MessageDigest = MessageDigest.getInstance("SHA-256")
8094
val type: Type = object : TypeToken<HashSet<String>>() {}.type

src/main/kotlin/com/lambda/client/plugin/PluginManager.kt

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ internal object PluginManager : AsyncLoader<List<PluginLoader>> {
6969

7070
for (loader in loaders) {
7171
// Hot reload check, the error shouldn't be show when reload in game
72-
if (LambdaMod.ready && !loader.info.hotReload) {
72+
if (LambdaMod.ready && loader.info.mixins.isNotEmpty()) {
7373
invalids.add(loader)
7474
}
7575

@@ -139,7 +139,7 @@ internal object PluginManager : AsyncLoader<List<PluginLoader>> {
139139

140140
fun load(loader: PluginLoader) {
141141
synchronized(this) {
142-
val hotReload = LambdaMod.ready && !loader.info.hotReload
142+
val hotReload = LambdaMod.ready && loader.info.mixins.isNotEmpty()
143143
val duplicate = loadedPlugins.containsName(loader.name)
144144
val unsupported = DefaultArtifactVersion(loader.info.minApiVersion) > lambdaVersion
145145
val missing = !loadedPlugins.containsNames(loader.info.requiredPlugins)
@@ -160,28 +160,29 @@ internal object PluginManager : AsyncLoader<List<PluginLoader>> {
160160
val plugin = runCatching(loader::load).getOrElse {
161161
when (it) {
162162
is ClassNotFoundException -> {
163-
PluginError.log("Main class not found in plugin $loader", it)
163+
PluginError.log("Main class not found in plugin $loader", throwable = it)
164164
}
165165
is IllegalAccessException -> {
166-
PluginError.log(it.message, it)
166+
PluginError.log(it.message, throwable = it)
167167
}
168168
else -> {
169-
PluginError.log("Failed to load plugin $loader", it)
169+
PluginError.log("Failed to load plugin $loader", throwable = it)
170170
}
171171
}
172172
return
173173
}
174174

175+
val hotReload = plugin.mixins.isEmpty()
175176
try {
176177
plugin.onLoad()
177178
} catch (e: NoSuchFieldError) {
178-
PluginError.log("Failed to load plugin $loader (NoSuchFieldError)", e)
179+
PluginError.log("Failed to load plugin $loader (NoSuchFieldError)", hotReload, e)
179180
return
180181
} catch (e: NoSuchMethodError) {
181-
PluginError.log("Failed to load plugin $loader (NoSuchMethodError)", e)
182+
PluginError.log("Failed to load plugin $loader (NoSuchMethodError)", hotReload, e)
182183
return
183184
} catch (e: NoClassDefFoundError) {
184-
PluginError.log("Failed to load plugin $loader (NoClassDefFoundError)", e)
185+
PluginError.log("Failed to load plugin $loader (NoClassDefFoundError)", hotReload, e)
185186
return
186187
}
187188

@@ -202,22 +203,24 @@ internal object PluginManager : AsyncLoader<List<PluginLoader>> {
202203
}
203204

204205
fun unloadAll() {
205-
loadedPlugins.filter { it.hotReload }.forEach(PluginManager::unloadWithoutCheck)
206+
loadedPlugins.filter { it.mixins.isEmpty() }.forEach(PluginManager::unloadWithoutCheck)
206207

207208
LambdaMod.LOG.info("Unloaded all plugins!")
208209
}
209210

210-
fun unload(plugin: Plugin) {
211+
fun unload(plugin: Plugin): Boolean {
211212
if (loadedPlugins.any { it.requiredPlugins.contains(plugin.name) }) {
212213
throw IllegalArgumentException("Plugin $plugin is required by another plugin!")
213214
}
214215

215-
unloadWithoutCheck(plugin)
216+
return unloadWithoutCheck(plugin)
216217
}
217218

218-
private fun unloadWithoutCheck(plugin: Plugin) {
219-
if (!plugin.hotReload) {
220-
throw IllegalArgumentException("Plugin $plugin cannot be hot reloaded!")
219+
private fun unloadWithoutCheck(plugin: Plugin): Boolean {
220+
// Necessary because of plugin GUI
221+
if (plugin.mixins.isNotEmpty()) {
222+
PluginError.log("Plugin ${plugin.name} cannot be hot reloaded!")
223+
return false
221224
}
222225

223226
synchronized(this) {
@@ -234,5 +237,6 @@ internal object PluginManager : AsyncLoader<List<PluginLoader>> {
234237

235238
LambdaMod.LOG.info("Unloaded plugin ${plugin.name} v${plugin.version}")
236239
MessageSendHelper.sendChatMessage("[Plugin Manager] ${LambdaClickGui.printInfo(plugin.name, plugin.version)} unloaded.")
240+
return true
237241
}
238242
}

src/main/kotlin/com/lambda/client/plugin/api/Plugin.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ open class Plugin : Nameable {
3232
val authors: Array<String> get() = info.authors
3333
val requiredPlugins: Array<String> get() = info.requiredPlugins
3434
val url: String get() = info.url
35-
val hotReload: Boolean get() = info.hotReload
35+
val mixins: Array<String> get() = info.mixins
3636

3737
/**
3838
* Config for the plugin

0 commit comments

Comments
 (0)