aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authornea <romangraef@gmail.com>2022-07-29 20:51:53 +0200
committernea <romangraef@gmail.com>2022-07-29 20:51:53 +0200
commitf4bf70032a45b4daa7805ca38f2d820645dc9a6b (patch)
treedc91fa8e13b78ac9a7fcda24a26894e430f7fc84 /src
parent386ee78026801bf50d9cba24de541ed087f145d6 (diff)
downloadfirmament-f4bf70032a45b4daa7805ca38f2d820645dc9a6b.tar.gz
firmament-f4bf70032a45b4daa7805ca38f2d820645dc9a6b.tar.bz2
firmament-f4bf70032a45b4daa7805ca38f2d820645dc9a6b.zip
no arch
Diffstat (limited to 'src')
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt18
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt38
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/rei/SBItemEntryDefinition.kt98
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt75
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt17
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/util/ItemUtil.kt23
-rw-r--r--src/main/kotlin/moe/nea/notenoughupdates/util/LegacyTagParser.kt232
-rw-r--r--src/main/resources/fabric.mod.json36
-rw-r--r--src/main/resources/notenoughupdates.accesswidener1
-rw-r--r--src/main/resources/notenoughupdates.mixins.json12
10 files changed, 550 insertions, 0 deletions
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt b/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt
new file mode 100644
index 0000000..79b8819
--- /dev/null
+++ b/src/main/kotlin/moe/nea/notenoughupdates/NotEnoughUpdates.kt
@@ -0,0 +1,18 @@
+package moe.nea.notenoughupdates
+
+import io.github.moulberry.repo.NEURepository
+import net.fabricmc.api.ModInitializer
+import java.nio.file.Path
+
+object NotEnoughUpdates : ModInitializer {
+ val DATA_DIR = Path.of(".notenoughupdates")
+
+ const val MOD_ID = "notenoughupdates"
+
+ val neuRepo = NEURepository.of(Path.of("NotEnoughUpdates-REPO")).also {
+ it.reload()
+ }
+
+ override fun onInitialize() {
+ }
+}
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt b/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt
new file mode 100644
index 0000000..cc4b0f1
--- /dev/null
+++ b/src/main/kotlin/moe/nea/notenoughupdates/rei/NEUReiPlugin.kt
@@ -0,0 +1,38 @@
+package moe.nea.notenoughupdates.rei
+
+import io.github.moulberry.repo.data.NEUItem
+import me.shedaniel.rei.api.client.plugins.REIClientPlugin
+import me.shedaniel.rei.api.client.registry.entry.EntryRegistry
+import me.shedaniel.rei.api.common.entry.EntryStack
+import me.shedaniel.rei.api.common.entry.type.EntryTypeRegistry
+import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes
+import moe.nea.notenoughupdates.NotEnoughUpdates.neuRepo
+import moe.nea.notenoughupdates.repo.ItemCache.asItemStack
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.world.item.ItemStack
+
+
+class NEUReiPlugin : REIClientPlugin {
+
+ companion object {
+
+ fun EntryStack<NEUItem>.asItemEntry(): EntryStack<ItemStack> {
+ return EntryStack.of(VanillaEntryTypes.ITEM, value.asItemStack())
+ }
+
+
+ val SKYBLOCK_ITEM_TYPE_ID = ResourceLocation("notenoughupdates", "skyblockitems")
+ }
+
+ override fun registerEntryTypes(registry: EntryTypeRegistry) {
+ registry.register(SKYBLOCK_ITEM_TYPE_ID, SBItemEntryDefinition)
+ }
+
+
+ override fun registerEntries(registry: EntryRegistry) {
+ neuRepo.items.items.values.forEach {
+ if (!it.isVanilla)
+ registry.addEntry(EntryStack.of(SBItemEntryDefinition, it))
+ }
+ }
+}
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/rei/SBItemEntryDefinition.kt b/src/main/kotlin/moe/nea/notenoughupdates/rei/SBItemEntryDefinition.kt
new file mode 100644
index 0000000..6726b4f
--- /dev/null
+++ b/src/main/kotlin/moe/nea/notenoughupdates/rei/SBItemEntryDefinition.kt
@@ -0,0 +1,98 @@
+package moe.nea.notenoughupdates.rei
+
+import com.mojang.blaze3d.vertex.PoseStack
+import io.github.moulberry.repo.data.NEUItem
+import me.shedaniel.math.Point
+import me.shedaniel.math.Rectangle
+import me.shedaniel.rei.api.client.entry.renderer.EntryRenderer
+import me.shedaniel.rei.api.client.gui.widgets.Tooltip
+import me.shedaniel.rei.api.client.gui.widgets.TooltipContext
+import me.shedaniel.rei.api.common.entry.EntrySerializer
+import me.shedaniel.rei.api.common.entry.EntryStack
+import me.shedaniel.rei.api.common.entry.comparison.ComparisonContext
+import me.shedaniel.rei.api.common.entry.type.EntryDefinition
+import me.shedaniel.rei.api.common.entry.type.EntryType
+import me.shedaniel.rei.api.common.entry.type.VanillaEntryTypes
+import moe.nea.notenoughupdates.rei.NEUReiPlugin.Companion.asItemEntry
+import moe.nea.notenoughupdates.repo.ItemCache.asItemStack
+import moe.nea.notenoughupdates.repo.ItemCache.getResourceLocation
+import net.minecraft.network.chat.Component
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.tags.TagKey
+import net.minecraft.world.item.ItemStack
+import java.util.stream.Stream
+
+object SBItemEntryDefinition : EntryDefinition<NEUItem> {
+ override fun equals(o1: NEUItem?, o2: NEUItem?, context: ComparisonContext?): Boolean {
+ return o1 == o2
+ }
+
+ override fun cheatsAs(entry: EntryStack<NEUItem>?, value: NEUItem?): ItemStack? {
+ return value?.asItemStack()
+ }
+
+ override fun getValueType(): Class<NEUItem> = NEUItem::class.java
+ override fun getType(): EntryType<NEUItem> =
+ EntryType.deferred(NEUReiPlugin.SKYBLOCK_ITEM_TYPE_ID)
+
+ override fun getRenderer(): EntryRenderer<NEUItem> = object : EntryRenderer<NEUItem> {
+ override fun render(
+ entry: EntryStack<NEUItem>,
+ matrices: PoseStack,
+ bounds: Rectangle,
+ mouseX: Int,
+ mouseY: Int,
+ delta: Float
+ ) {
+ VanillaEntryTypes.ITEM.definition.renderer
+ .render(
+ entry.asItemEntry(),
+ matrices, bounds, mouseX, mouseY, delta
+ )
+ }
+
+ override fun getTooltip(entry: EntryStack<NEUItem>, tooltipContext: TooltipContext): Tooltip? {
+ return VanillaEntryTypes.ITEM.definition.renderer
+ .getTooltip(entry.asItemEntry(), tooltipContext)
+ }
+
+ }
+
+ override fun getSerializer(): EntrySerializer<NEUItem>? {
+ return null
+ }
+
+ override fun getTagsFor(entry: EntryStack<NEUItem>?, value: NEUItem?): Stream<out TagKey<*>> {
+ return Stream.empty()
+ }
+
+ override fun asFormattedText(entry: EntryStack<NEUItem>, value: NEUItem): Component {
+ return VanillaEntryTypes.ITEM.definition.asFormattedText(entry.asItemEntry(), value.asItemStack())
+ }
+
+ override fun hash(entry: EntryStack<NEUItem>, value: NEUItem, context: ComparisonContext): Long {
+ return value.skyblockItemId.hashCode().toLong()
+ }
+
+ override fun wildcard(entry: EntryStack<NEUItem>, value: NEUItem): NEUItem {
+ return value
+ }
+
+ override fun normalize(entry: EntryStack<NEUItem>, value: NEUItem): NEUItem {
+ return value
+ }
+
+ override fun copy(entry: EntryStack<NEUItem>?, value: NEUItem): NEUItem {
+ return value
+ }
+
+ override fun isEmpty(entry: EntryStack<NEUItem>?, value: NEUItem?): Boolean {
+ return false
+ }
+
+ override fun getIdentifier(entry: EntryStack<NEUItem>?, value: NEUItem): ResourceLocation {
+ return value.getResourceLocation()
+ }
+
+
+}
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt b/src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt
new file mode 100644
index 0000000..aa93fec
--- /dev/null
+++ b/src/main/kotlin/moe/nea/notenoughupdates/repo/ItemCache.kt
@@ -0,0 +1,75 @@
+package moe.nea.notenoughupdates.repo
+
+import com.mojang.serialization.Dynamic
+import io.github.moulberry.repo.IReloadable
+import io.github.moulberry.repo.NEURepository
+import io.github.moulberry.repo.data.NEUItem
+import moe.nea.notenoughupdates.util.LegacyTagParser
+import moe.nea.notenoughupdates.util.appendLore
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.nbt.NbtOps
+import net.minecraft.network.chat.Component
+import net.minecraft.resources.ResourceLocation
+import net.minecraft.util.datafix.DataFixers
+import net.minecraft.util.datafix.fixes.References
+import net.minecraft.world.item.ItemStack
+import net.minecraft.world.item.Items
+import java.util.concurrent.ConcurrentHashMap
+
+object ItemCache : IReloadable {
+ val cache: MutableMap<String, ItemStack> = ConcurrentHashMap()
+ val df = DataFixers.getDataFixer()
+ var isFlawless = true
+
+ private fun NEUItem.get10809CompoundTag(): CompoundTag = CompoundTag().apply {
+ put("tag", LegacyTagParser.parse(nbttag))
+ putString("id", minecraftItemId)
+ putByte("Count", 1)
+ putShort("Damage", damage.toShort())
+ }
+
+ private fun CompoundTag.transformFrom10809ToModern(): CompoundTag? =
+ try {
+ df.update(
+ References.ITEM_STACK,
+ Dynamic(NbtOps.INSTANCE, this),
+ -1,
+ 2975
+ ).value as CompoundTag
+ } catch (e: Exception) {
+ e.printStackTrace()
+ isFlawless = false
+ null
+ }
+
+ private fun NEUItem.asItemStackNow(): ItemStack {
+ val oldItemTag = get10809CompoundTag()
+ val modernItemTag = oldItemTag.transformFrom10809ToModern()
+ ?: return ItemStack(Items.PAINTING).apply {
+ setHoverName(Component.literal(this@asItemStackNow.displayName))
+ appendLore(listOf(Component.literal("Exception rendering item: $skyblockItemId")))
+ }
+ val itemInstance = ItemStack.of(modernItemTag)
+ if (itemInstance.tag?.contains("Enchantments") == true) {
+ itemInstance.enchantmentTags.add(CompoundTag())
+ }
+ return itemInstance
+ }
+
+ fun NEUItem.asItemStack(): ItemStack {
+ var s = cache[this.skyblockItemId]
+ if (s == null) {
+ s = asItemStackNow()
+ cache[this.skyblockItemId] = s
+ }
+ return s
+ }
+
+ fun NEUItem.getResourceLocation() =
+ ResourceLocation("skyblockitem", skyblockItemId.lowercase().replace(";", "__"))
+
+
+ override fun reload(repository: NEURepository) {
+ cache.clear()
+ }
+}
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt
new file mode 100644
index 0000000..47b2878
--- /dev/null
+++ b/src/main/kotlin/moe/nea/notenoughupdates/repo/RepoDownloadManager.kt
@@ -0,0 +1,17 @@
+package moe.nea.notenoughupdates.repo
+
+import moe.nea.notenoughupdates.NotEnoughUpdates
+
+object RepoDownloadManager {
+
+ val repoSavedLocation = NotEnoughUpdates.DATA_DIR.resolve("repo-extracted")
+ val repoMetadataLocation = NotEnoughUpdates.DATA_DIR.resolve("loaded-repo.json")
+
+ data class RepoMetadata(
+ var latestCommit: String,
+ var user: String,
+ var repository: String,
+ var branch: String,
+ )
+
+}
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/ItemUtil.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/ItemUtil.kt
new file mode 100644
index 0000000..d5b8881
--- /dev/null
+++ b/src/main/kotlin/moe/nea/notenoughupdates/util/ItemUtil.kt
@@ -0,0 +1,23 @@
+package moe.nea.notenoughupdates.util
+
+import net.minecraft.nbt.CompoundTag
+import net.minecraft.nbt.ListTag
+import net.minecraft.nbt.StringTag
+import net.minecraft.network.chat.Component
+import net.minecraft.world.item.ItemStack
+
+fun ItemStack.appendLore(args: List<Component>) {
+ val compoundTag = getOrCreateTagElement("display")
+ val loreList = compoundTag.getOrCreateList("Lore", StringTag.TAG_STRING)
+ for (arg in args) {
+ loreList.add(StringTag.valueOf(Component.Serializer.toJson(arg)))
+ }
+}
+
+fun CompoundTag.getOrCreateList(label: String, tag: Byte): ListTag = getList(label, tag.toInt()).also {
+ put(label, it)
+}
+
+fun CompoundTag.getOrCreateCompoundTag(label: String): CompoundTag = getCompound(label).also {
+ put(label, it)
+}
diff --git a/src/main/kotlin/moe/nea/notenoughupdates/util/LegacyTagParser.kt b/src/main/kotlin/moe/nea/notenoughupdates/util/LegacyTagParser.kt
new file mode 100644
index 0000000..a4ec7e1
--- /dev/null
+++ b/src/main/kotlin/moe/nea/notenoughupdates/util/LegacyTagParser.kt
@@ -0,0 +1,232 @@
+package moe.nea.notenoughupdates.util
+
+import net.minecraft.nbt.*
+import java.util.*
+
+class LegacyTagParser private constructor(string: String) {
+ data class TagParsingException(val baseString: String, val offset: Int, val mes0: String) :
+ Exception("$mes0 at $offset in `$baseString`.")
+
+ class StringRacer(val backing: String) {
+ var idx = 0
+ val stack = Stack<Int>()
+
+ fun pushState() {
+ stack.push(idx)
+ }
+
+ fun popState() {
+ idx = stack.pop()
+ }
+
+ fun discardState() {
+ stack.pop()
+ }
+
+ fun peek(count: Int): String {
+ return backing.substring(minOf(idx, backing.length), minOf(idx + count, backing.length))
+ }
+
+ fun finished(): Boolean {
+ return peek(1).isEmpty()
+ }
+
+ fun peekReq(count: Int): String? {
+ val p = peek(count)
+ if (p.length != count)
+ return null
+ return p
+ }
+
+ fun consumeCountReq(count: Int): String? {
+ val p = peekReq(count)
+ if (p != null)
+ idx += count
+ return p
+ }
+
+ fun tryConsume(string: String): Boolean {
+ val p = peek(string.length)
+ if (p != string)
+ return false
+ idx += p.length
+ return true
+ }
+
+ fun consumeWhile(shouldConsumeThisString: (String) -> Boolean): String {
+ var lastString: String = ""
+ while (true) {
+ val nextString = lastString + peek(1)
+ if (!shouldConsumeThisString(nextString)) {
+ return lastString
+ }
+ idx++
+ lastString = nextString
+ }
+ }
+
+ fun expect(search: String, errorMessage: String) {
+ if (!tryConsume(search))
+ error(errorMessage)
+ }
+
+ fun error(errorMessage: String): Nothing {
+ throw TagParsingException(backing, idx, errorMessage)
+ }
+
+ }
+
+ val racer = StringRacer(string)
+ val baseTag = parseTag()
+
+ companion object {
+ val digitRange = '0'..'9'
+ fun parse(string: String): CompoundTag {
+ return LegacyTagParser(string).baseTag
+ }
+ }
+
+ fun skipWhitespace() {
+ racer.consumeWhile { Character.isWhitespace(it.last()) } // Only check last since other chars are always checked before.
+ }
+
+ fun parseTag(): CompoundTag {
+ skipWhitespace()
+ racer.expect("{", "Expected '{’ at start of tag")
+ skipWhitespace()
+ val tag = CompoundTag()
+ while (!racer.tryConsume("}")) {
+ skipWhitespace()
+ val lhs = parseIdentifier()
+ skipWhitespace()
+ racer.expect(":", "Expected ':' after identifier in tag")
+ skipWhitespace()
+ val rhs = parseAny()
+ tag.put(lhs, rhs)
+ racer.tryConsume(",")
+ skipWhitespace()
+ }
+ return tag
+ }
+
+ private fun parseAny(): Tag {
+ skipWhitespace()
+ val nextChar = racer.peekReq(1) ?: racer.error("Expected new object, found EOF")
+ return when {
+ nextChar == "{" -> parseTag()
+ nextChar == "[" -> parseList()
+ nextChar == "\"" -> parseStringTag()
+ nextChar.first() in (digitRange) -> parseNumericTag()
+ else -> racer.error("Unexpected token found. Expected start of new element")
+ }
+ }
+
+ fun parseList(): ListTag {
+ skipWhitespace()
+ racer.expect("[", "Expected '[' at start of tag")
+ skipWhitespace()
+ val list = ListTag()
+ while (!racer.tryConsume("]")) {
+ skipWhitespace()
+ racer.pushState()
+ val lhs = racer.consumeWhile { it.all { it in digitRange } }
+ skipWhitespace()
+ if (!racer.tryConsume(":") || lhs.isEmpty()) { // No prefixed 0:
+ racer.popState()
+ list.add(parseAny()) // Reparse our number (or not a number) as actual tag
+ } else {
+ racer.discardState()
+ skipWhitespace()
+ list.add(parseAny()) // Ignore prefix indexes. They should not be generated out of order by any vanilla implementation (which is what NEU should export). Instead append where it appears in order.
+ }
+ skipWhitespace()
+ racer.tryConsume(",")
+ }
+ return list
+ }
+
+ fun parseQuotedString(): String {
+ skipWhitespace()
+ racer.expect("\"", "Expected '\"' at string start")
+ val sb = StringBuilder()
+ while (true) {
+ when (val peek = racer.consumeCountReq(1)) {
+ "\"" -> break
+ "\\" -> {
+ val escaped = racer.consumeCountReq(1) ?: racer.error("Unfinished backslash escape")
+ if (escaped != "\"" && escaped != "\\") {
+ // Surprisingly i couldn't find unicode escapes to be generated by the original minecraft 1.8.9 implementation
+ racer.idx--
+ racer.error("Invalid backslash escape '$escaped'")
+ }
+ sb.append(escaped)
+ }
+ null -> racer.error("Unfinished string")
+ else -> {
+ sb.append(peek)
+ }
+ }
+ }
+ return sb.toString()
+ }
+
+ fun parseStringTag(): StringTag {
+ return StringTag.valueOf(parseQuotedString())
+ }
+
+ object Patterns {
+ val DOUBLE = "([-+]?[0-9]*\\.?[0-9]+)[d|D]".toRegex()
+ val FLOAT = "([-+]?[0-9]*\\.?[0-9]+)[f|F]".toRegex()
+ val BYTE = "([-+]?[0-9]+)[b|B]".toRegex()
+ val LONG = "([-+]?[0-9]+)[l|L]".toRegex()
+ val SHORT = "([-+]?[0-9]+)[s|S]".toRegex()
+ val INTEGER = "([-+]?[0-9]+)".toRegex()
+ val DOUBLE_UNTYPED = "([-+]?[0-9]*\\.?[0-9]+)".toRegex()
+ val ROUGH_PATTERN = "[-+]?[0-9]*\\.?[0-9]+[dDbBfFlLsS]?".toRegex()
+ }
+
+ fun parseNumericTag(): NumericTag {
+ skipWhitespace()
+ val textForm = racer.consumeWhile { Patterns.ROUGH_PATTERN.matchEntire(it) != null }
+ if (textForm.isEmpty()) {
+ racer.error("Expected numeric tag (starting with either -, +, . or a digit")
+ }
+ val doubleMatch = Patterns.DOUBLE.matchEntire(textForm) ?: Patterns.DOUBLE_UNTYPED.matchEntire(textForm)
+ if (doubleMatch != null) {
+ return DoubleTag.valueOf(doubleMatch.groups[1]!!.value.toDouble())
+ }
+ val floatMatch = Patterns.FLOAT.matchEntire(textForm)
+ if (floatMatch != null) {
+ return FloatTag.valueOf(floatMatch.groups[1]!!.value.toFloat())
+ }
+ val byteMatch = Patterns.BYTE.matchEntire(textForm)
+ if (byteMatch != null) {
+ return ByteTag.valueOf(byteMatch.groups[1]!!.value.toByte())
+ }
+ val longMatch = Patterns.LONG.matchEntire(textForm)
+ if (longMatch != null) {
+ return LongTag.valueOf(longMatch.groups[1]!!.value.toLong())
+ }
+ val shortMatch = Patterns.SHORT.matchEntire(textForm)
+ if (shortMatch != null) {
+ return ShortTag.valueOf(shortMatch.groups[1]!!.value.toShort())
+ }
+ val integerMatch = Patterns.INTEGER.matchEntire(textForm)
+ if (integerMatch != null) {
+ return IntTag.valueOf(integerMatch.groups[1]!!.value.toInt())
+ }
+ throw IllegalStateException("Could not properly parse numeric tag '$textForm', despite passing rough verification. This is a bug in the LegacyTagParser")
+ }
+
+ private fun parseIdentifier(): String {
+ skipWhitespace()
+ if (racer.peek(1) == "\"") {
+ return parseQuotedString()
+ }
+ return racer.consumeWhile {
+ val x = it.last()
+ x != ':' && !Character.isWhitespace(x)
+ }
+ }
+
+}
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
new file mode 100644
index 0000000..c739445
--- /dev/null
+++ b/src/main/resources/fabric.mod.json
@@ -0,0 +1,36 @@
+{
+ "schemaVersion": 1,
+ "id": "notenoughupdates",
+ "version": "${version}",
+ "name": "Not Enough Updates",
+ "description": "Not Enough Updates - A mod for Hypixel Skyblock",
+ "authors": [
+ "nea89"
+ ],
+ "contact": {
+ "homepage": "https://github.com/romangraef/TODO",
+ "sources": "https://github.com/romangraef/TODO"
+ },
+ "license": "LGPL-3.0",
+ "icon": "assets/notenoughupdates/icon.png",
+ "environment": "client",
+ "entrypoints": {
+ "main": [
+ {
+ "adapter": "kotlin",
+ "value": "moe.nea.notenoughupdates.NotEnoughUpdates"
+ }
+ ],
+ "rei": [
+ "moe.nea.notenoughupdates.rei.NEUReiPlugin"
+ ]
+ },
+ "mixins": [
+ "notenoughupdates.mixins.json"
+ ],
+ "depends": {
+ "fabric": "*",
+ "fabric-language-kotlin": ">=1.8.2+kotlin.1.7.10",
+ "minecraft": ">=1.18.2"
+ }
+}
diff --git a/src/main/resources/notenoughupdates.accesswidener b/src/main/resources/notenoughupdates.accesswidener
new file mode 100644
index 0000000..236e6b1
--- /dev/null
+++ b/src/main/resources/notenoughupdates.accesswidener
@@ -0,0 +1 @@
+accessWidener v2 named
diff --git a/src/main/resources/notenoughupdates.mixins.json b/src/main/resources/notenoughupdates.mixins.json
new file mode 100644
index 0000000..3067bcc
--- /dev/null
+++ b/src/main/resources/notenoughupdates.mixins.json
@@ -0,0 +1,12 @@
+{
+ "required": true,
+ "package": "moe.nea.notenoughupdates.mixins",
+ "compatibilityLevel": "JAVA_16",
+ "client": [
+ ],
+ "mixins": [
+ ],
+ "injectors": {
+ "defaultRequire": 1
+ }
+}