From fb426a9d9fcf15c9212546356cf2bce538d7de22 Mon Sep 17 00:00:00 2001 From: lowercasebtw Date: Thu, 6 Aug 2026 21:02:28 -0400 Subject: [PATCH 1/5] POLYPLUS2-2E, POLYPLUS2-4P, POLYPLUS2-K --- .../client/PolyPlusCrashLogUploader.kt | 1 + .../polyplus/client/PolyPlusSentry.kt | 6 ++ .../polyplus/test/IgnoredCrashReportTest.kt | 62 +++++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusCrashLogUploader.kt b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusCrashLogUploader.kt index 050cd48..8e0d36d 100644 --- a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusCrashLogUploader.kt +++ b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusCrashLogUploader.kt @@ -110,6 +110,7 @@ object PolyPlusCrashLogUploader { val isJvmFatal = file.name.startsWith("hs_err_pid") val body = prepare(file, isJvmFatal) ?: continue val summary = summarize(body, isJvmFatal) + if (!PolyPlusSentry.involvesPolyPlus(body)) continue if (isJvmFatal) { if (NATIVE_OUT_OF_MEMORY.containsMatchIn(body)) continue } else { diff --git a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt index ad6bc94..c995848 100644 --- a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt +++ b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt @@ -463,6 +463,12 @@ object PolyPlusSentry { return body.contains(DELIBERATE_CRASH_CLASS) || OUT_OF_MEMORY.containsMatchIn(body) } + private const val POLYPLUS_PACKAGE = "org.polyfrost.polyplus" + private val POLYPLUS_MIXIN_FRAME = Regex("\\$[a-z]{3}\\d+[$]polyplus[$]") + + internal fun involvesPolyPlus(body: String): Boolean = + body.contains(POLYPLUS_PACKAGE) || POLYPLUS_MIXIN_FRAME.containsMatchIn(body) + private fun isDeliberateCrash(throwable: Throwable): Boolean { var cause: Throwable? = throwable while (cause != null) { diff --git a/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt b/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt index ffe7270..f733e7a 100644 --- a/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt +++ b/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt @@ -122,6 +122,68 @@ class IgnoredCrashReportTest { assertFalse(PolyPlusSentry.isIgnoredCrashReport(summary(GENUINE_REPORT), GENUINE_REPORT)) } + @Test + fun `uploads only crashes PolyPlus appears in`() { + val otherModCrash = """ + ---- Minecraft Crash Report ---- + + Description: Initializing game + + java.lang.RuntimeException: Could not execute entrypoint stage 'main' due to errors, provided by 'controlify' at 'dev.isxander.controlify.ControlifyBootstrap'! + at net.fabricmc.loader.impl.FabricLoaderImpl.invokeEntrypoints(FabricLoaderImpl.java:411) + at dev.isxander.controlify.ControlifyBootstrap.onInitialize(ControlifyBootstrap.java:25) + Caused by: org.spongepowered.asm.mixin.injection.throwables.InjectionError: Critical injection failure: Redirector onKey${'$'}initializeVelocityCipher in krypton.mixins.json:shared.network.pipeline.encryption.ServerLoginNetworkHandlerMixin from mod krypton failed injection check + """.trimIndent() + + val vanillaCrash = """ + ---- Minecraft Crash Report ---- + + Description: Unexpected error + + java.lang.NullPointerException: Cannot invoke "net.minecraft.client.network.ClientPlayerEntity.getInventory()" because "this.client.player" is null + at net.minecraft.client.network.ClientPlayerInteractionManager.syncSelectedSlot(ClientPlayerInteractionManager.java:308) + at net.minecraft.client.MinecraftClient.tick(MinecraftClient.java:1888) + """.trimIndent() + + val driverFatal = """ + # A fatal error has been detected by the Java Runtime Environment: + # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffb6fbe646c + # Problematic frame: + # C [nvoglv64.dll+0xdc646c] + """.trimIndent() + + assertFalse(PolyPlusSentry.involvesPolyPlus(otherModCrash)) + assertFalse(PolyPlusSentry.involvesPolyPlus(vanillaCrash)) + assertFalse(PolyPlusSentry.involvesPolyPlus(driverFatal)) + assertFalse(PolyPlusSentry.involvesPolyPlus(GENUINE_REPORT)) + } + + @Test + fun `keeps crashes our own code and mixins are in`() { + val ourCode = """ + ---- Minecraft Crash Report ---- + + Description: Rendering entity + + java.lang.NullPointerException: bone was null + at org.polyfrost.polyplus.client.cosmetics.render.CosmeticRenderer.render(CosmeticRenderer.kt:39) + at net.minecraft.client.renderer.entity.LivingEntityRenderer.render(LivingEntityRenderer.java:112) + """.trimIndent() + + val ourMixin = """ + ---- Minecraft Crash Report ---- + + Description: Rendering screen + + java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 0 + at net.minecraft.client.gui.screens.ChatScreen.handler${'$'}zzk000${'$'}polyplus${'$'}onKeyPressed(ChatScreen.java:8123) + at net.minecraft.client.gui.screens.ChatScreen.keyPressed(ChatScreen.java:214) + """.trimIndent() + + assertTrue(PolyPlusSentry.involvesPolyPlus(ourCode)) + assertTrue(PolyPlusSentry.involvesPolyPlus(ourMixin)) + } + @Test fun `matches the description alone, as the live path has it`() { assertTrue(PolyPlusSentry.isIgnoredCrashReport("Unexpected error: java.lang.RuntimeException: Crash requested by CrashPatch")) From 14915009db1f5a605aaa3fbdf6fa4c859c0a902d Mon Sep 17 00:00:00 2001 From: lowercasebtw Date: Fri, 7 Aug 2026 02:00:27 -0400 Subject: [PATCH 2/5] Fix more sentry issues Fixes - POLYPLUS2-4X/4Z - POLYPLUS2-M2 - POLYPLUS2-A - POLYPLUS2-A - Fix involvesPolyPlus --- fabric.gradle.kts | 54 +++++++++++++++++-- fabric.obf.gradle.kts | 54 +++++++++++++++++-- gradle/libs.versions.toml | 14 +++++ .../polyplus/client/PolyPlusClient.kt | 2 +- .../polyplus/client/PolyPlusSentry.kt | 39 +++++++++++++- .../client/cosmetics/CosmeticStore.kt | 17 +++--- .../client/emotes/EmoteWheelKeybind.kt | 5 ++ .../gui/preview/PlayerPreviewRenderer.kt | 8 +-- .../polyplus/test/IgnoredCrashReportTest.kt | 45 ++++++++++++++++ 9 files changed, 220 insertions(+), 18 deletions(-) diff --git a/fabric.gradle.kts b/fabric.gradle.kts index 7346ace..02e06e6 100644 --- a/fabric.gradle.kts +++ b/fabric.gradle.kts @@ -127,6 +127,47 @@ tasks.jar { } } +val serializationRelocatedPackage = "org.polyfrost.polyplus.libs.serialization" + +val serializationShade: Configuration by configurations.creating { + isCanBeConsumed = false + isCanBeResolved = true + isTransitive = false +} + +val shadedArchiveBaseName = property("mod.id") as String +val shadedArchiveVersion = version.toString() + +val relocateSerialization = tasks.register("relocateSerialization") { + group = "build" + description = "Rewrites kotlinx.serialization to $serializationRelocatedPackage across the mod" + + from(tasks.jar.map { zipTree(it.archiveFile) }) + configurations = listOf(serializationShade) + relocate("kotlinx.serialization", serializationRelocatedPackage) + duplicatesStrategy = DuplicatesStrategy.INCLUDE + mergeServiceFiles() + + exclude( + "META-INF/MANIFEST.MF", + "META-INF/*.SF", + "META-INF/*.DSA", + "META-INF/*.RSA", + "META-INF/versions/**/module-info.class", + "module-info.class", + ) + + archiveBaseName = shadedArchiveBaseName + archiveVersion = shadedArchiveVersion + archiveClassifier = "shaded" + destinationDirectory = layout.buildDirectory.dir("libs") + + isPreserveFileTimestamps = false + isReproducibleFileOrder = true +} + +tasks.assemble { dependsOn(relocateSerialization) } + dependencies { minecraft("com.mojang:minecraft:${versionCatalog("common$catalogVersion").findVersion("minecraft").get()}") @@ -149,6 +190,7 @@ dependencies { sentryShade(libs.sentry) implementation(files(relocateSentry.flatMap { it.archiveFile })) + serializationShade(libs.bundles.serialization.shade) implementation(libs.bundles.ktor.client) implementation(libs.bundles.ktor.server) implementation(libs.bundles.ktor.serialization) @@ -164,11 +206,15 @@ run { val closure = configurations.detachedConfiguration( *bundledRoots.map { dependencies.create(it) }.toTypedArray() ) + + fun key(group: String?, name: String) = "$group:${name.removeSuffix("-jvm")}" + val relocated = serializationShade.dependencies.map { key(it.group, it.name) }.toSet() + closure.resolvedConfiguration.resolvedArtifacts.forEach { art -> val id = art.moduleVersion.id - if (id.group != "org.jetbrains.kotlin") { - dependencies.include("${id.group}:${id.name}:${id.version}") - } + if (id.group == "org.jetbrains.kotlin") return@forEach + if (key(id.group, id.name) in relocated) return@forEach + dependencies.include("${id.group}:${id.name}:${id.version}") } } @@ -227,7 +273,7 @@ val modrinthId = listOf("oneconfig.publish.modrinth", "publish.modrinth") val modrinthToken = listOf("oneconfig.publish.modrinth.token", "publish.modrinth.token", "modrinth.token") .firstNotNullOfOrNull { findProperty(it) }?.toString()?.takeIf { it.isNotBlank() } val minecraftVersion = modrinthMinecraftVersionOverride[mcVersion] ?: listOf(mcVersion) -val publishJarTaskName = if ("remapJar" in tasks.names) "remapJar" else "jar" +val publishJarTaskName = "relocateSerialization" val changelogs = rootProject.file("CHANGELOG.md").takeIf { it.exists() }?.readText() ?: "No changelog provided." publishMods { diff --git a/fabric.obf.gradle.kts b/fabric.obf.gradle.kts index 8bf5c2f..4f9fda4 100644 --- a/fabric.obf.gradle.kts +++ b/fabric.obf.gradle.kts @@ -128,6 +128,49 @@ tasks.jar { } } +val serializationRelocatedPackage = "org.polyfrost.polyplus.libs.serialization" + +val serializationShade: Configuration by configurations.creating { + isCanBeConsumed = false + isCanBeResolved = true + isTransitive = false +} + +val shadedArchiveBaseName = property("mod.id") as String +val shadedArchiveVersion = version.toString() + +val relocateSerialization = tasks.register("relocateSerialization") { + group = "build" + description = "Rewrites kotlinx.serialization to $serializationRelocatedPackage across the mod" + + from(tasks.jar.map { zipTree(it.archiveFile) }) + configurations = listOf(serializationShade) + relocate("kotlinx.serialization", serializationRelocatedPackage) + duplicatesStrategy = DuplicatesStrategy.INCLUDE + mergeServiceFiles() + + exclude( + "META-INF/MANIFEST.MF", + "META-INF/*.SF", + "META-INF/*.DSA", + "META-INF/*.RSA", + "META-INF/versions/**/module-info.class", + "module-info.class", + ) + + archiveBaseName = shadedArchiveBaseName + archiveVersion = shadedArchiveVersion + archiveClassifier = "shaded" + destinationDirectory = layout.buildDirectory.dir("libs") + + isPreserveFileTimestamps = false + isReproducibleFileOrder = true +} + +tasks.named("remapJar") { + inputFile = relocateSerialization.flatMap { it.archiveFile } +} + dependencies { minecraft("com.mojang:minecraft:${versionCatalog("common$catalogVersion").findVersion("minecraft").get()}") @@ -157,6 +200,7 @@ dependencies { sentryShade(libs.sentry) implementation(files(relocateSentry.flatMap { it.archiveFile })) + serializationShade(libs.bundles.serialization.shade) implementation(libs.bundles.ktor.client) implementation(libs.bundles.ktor.server) implementation(libs.bundles.ktor.serialization) @@ -172,11 +216,15 @@ run { val closure = configurations.detachedConfiguration( *bundledRoots.map { dependencies.create(it) }.toTypedArray() ) + + fun key(group: String?, name: String) = "$group:${name.removeSuffix("-jvm")}" + val relocated = serializationShade.dependencies.map { key(it.group, it.name) }.toSet() + closure.resolvedConfiguration.resolvedArtifacts.forEach { art -> val id = art.moduleVersion.id - if (id.group != "org.jetbrains.kotlin") { - dependencies.include("${id.group}:${id.name}:${id.version}") - } + if (id.group == "org.jetbrains.kotlin") return@forEach + if (key(id.group, id.name) in relocated) return@forEach + dependencies.include("${id.group}:${id.name}:${id.version}") } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2cd10f9..225634d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,7 @@ [versions] kotlin = "2.3.0" kotlinx-serialization = "2.3.0" +kotlinx-serialization-runtime = "1.9.0" atomicfu = "0.27.0" ktor = "3.3.1" sentry = "7.18.0" @@ -22,6 +23,12 @@ ktor-client-content-negotiation = { group = "io.ktor", name = "ktor-client-conte ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" } ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } +kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core-jvm", version.ref = "kotlinx-serialization-runtime" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json-jvm", version.ref = "kotlinx-serialization-runtime" } +kotlinx-serialization-json-io = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json-io-jvm", version.ref = "kotlinx-serialization-runtime" } +ktor-serialization-kotlinx = { group = "io.ktor", name = "ktor-serialization-kotlinx-jvm", version.ref = "ktor" } +ktor-serialization-kotlinx-json-jvm = { group = "io.ktor", name = "ktor-serialization-kotlinx-json-jvm", version.ref = "ktor" } + mixin-extras = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixin-extras" } mixin-squared = { module = "com.github.bawnorton.mixinsquared:mixinsquared-common", version.ref = "mixin-squared" } polymixin = { module = "org.polyfrost:polymixin", version.ref = "polymixin" } @@ -34,6 +41,13 @@ junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "jun ktor-client = ["ktor-client-core", "ktor-client-cio", "ktor-client-content-negotiation"] ktor-server = ["ktor-server-websockets"] ktor-serialization = ["ktor-serialization-kotlinx-json"] +serialization-shade = [ + "kotlinx-serialization-core", + "kotlinx-serialization-json", + "kotlinx-serialization-json-io", + "ktor-serialization-kotlinx", + "ktor-serialization-kotlinx-json-jvm", +] [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } diff --git a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusClient.kt b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusClient.kt index 814cbd9..2555a48 100644 --- a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusClient.kt +++ b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusClient.kt @@ -138,7 +138,7 @@ object PolyPlusClient { //? if >= 1.21.1 step("pet entities") { PetEntities.register() } //? if >= 1.21.1 - EmoteWheelKeybind.register() + step("emote wheel keybind") { EmoteWheelKeybind.register() } step("websocket") { PolyConnection.initialize { diff --git a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt index c995848..f359653 100644 --- a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt +++ b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt @@ -195,7 +195,7 @@ object PolyPlusSentry { isAttachStacktrace = true setBeforeSend { event, _ -> val t = event.throwable - if (t != null && isNeverReported(t)) { + if (t != null && (isNeverReported(t) || isForeignUncaughtException(event, t))) { null } else { rateUncaughtException(event) @@ -277,6 +277,12 @@ object PolyPlusSentry { send(throwable, CrashKind.RUNTIME_ERROR, null, Thread.currentThread()) } + private fun isForeignUncaughtException(event: SentryEvent, throwable: Throwable): Boolean { + val wrapper = event.throwableMechanism as? ExceptionMechanismException ?: return false + if (wrapper.exceptionMechanism?.type != UNCAUGHT_MECHANISM) return false + return !involvesPolyPlus(throwable) + } + private fun rateUncaughtException(event: SentryEvent) { val wrapper = event.throwableMechanism as? ExceptionMechanismException ?: return if (wrapper.exceptionMechanism?.type != UNCAUGHT_MECHANISM) return @@ -469,6 +475,21 @@ object PolyPlusSentry { internal fun involvesPolyPlus(body: String): Boolean = body.contains(POLYPLUS_PACKAGE) || POLYPLUS_MIXIN_FRAME.containsMatchIn(body) + private fun isPolyPlusFrame(frame: StackTraceElement): Boolean = + frame.className.startsWith(POLYPLUS_PACKAGE) || POLYPLUS_MIXIN_FRAME.containsMatchIn(frame.methodName) + + internal fun involvesPolyPlus(throwable: Throwable): Boolean { + var cause: Throwable? = throwable + var hops = 0 + while (cause != null && hops++ < MAX_UNWRAP_DEPTH) { + if (cause.stackTrace.any(::isPolyPlusFrame)) return true + val next = cause.cause + if (next === cause) break + cause = next + } + return false + } + private fun isDeliberateCrash(throwable: Throwable): Boolean { var cause: Throwable? = throwable while (cause != null) { @@ -503,12 +524,28 @@ object PolyPlusSentry { activeHub()?.captureEvent(event) } + private val locallyHandled = ThreadLocal.withInitial { 0 } + + fun handlingLocally(block: () -> T): T { + val depth = locallyHandled.get() + locallyHandled.set(depth + 1) + return try { + block() + } finally { + locallyHandled.set(depth) + } + } + @JvmStatic fun captureCrashReport(title: String?, throwable: Throwable) { if (!PrivacyConsent.allowsOnlineServices()) return initialize() val hub = activeHub() ?: return + if (locallyHandled.get() > 0) return + + if (!involvesPolyPlus(throwable)) return + val description = if (title.isNullOrBlank()) throwable.toString() else "$title: $throwable" if (isIgnoredCrashReport(description) || isNeverReported(throwable)) { PolyPlusCrashLogUploader.recordIgnoredCrash(throwable) diff --git a/src/main/kotlin/org/polyfrost/polyplus/client/cosmetics/CosmeticStore.kt b/src/main/kotlin/org/polyfrost/polyplus/client/cosmetics/CosmeticStore.kt index d10217e..29f461c 100644 --- a/src/main/kotlin/org/polyfrost/polyplus/client/cosmetics/CosmeticStore.kt +++ b/src/main/kotlin/org/polyfrost/polyplus/client/cosmetics/CosmeticStore.kt @@ -37,6 +37,7 @@ object CosmeticStore { types: List = emptyList(), tags: List = emptyList(), collection: Int? = null, + reportFailures: Boolean = true, ): Result = runCatching { PolyPlusClient.HTTP.get("${PolyPlusConfig.apiUrl}/cosmetics/search") { parameter("page", page.coerceAtLeast(1)) @@ -47,7 +48,7 @@ object CosmeticStore { if (tags.isNotEmpty()) parameter("tags", tags.joinToString(",")) if (collection != null) parameter("collection", collection) }.body() - }.onFailure { reportFailure("Failed to search cosmetics", it) } + }.onFailure { if (reportFailures) reportFailure("Failed to search cosmetics", it) } private var cachedStockedTypes: List? = null @@ -58,14 +59,18 @@ object CosmeticStore { val stocked = coroutineScope { types.map { type -> async { - val result = search(page = 1, perPage = 1, types = listOf(type.serializedName)) - if (result.isSuccess) anySucceeded = true - val count = result.getOrNull()?.pagination?.totalItems - type.takeIf { count == null || count > 0 } + val response = search( + page = 1, + perPage = 1, + types = listOf(type.serializedName), + reportFailures = false, + ).getOrNull() ?: return@async null + anySucceeded = true + type.takeIf { response.pagination.totalItems > 0 } } }.awaitAll() }.filterNotNull() - if (!anySucceeded) return stocked + if (!anySucceeded) return types return stocked.also { cachedStockedTypes = it } } diff --git a/src/main/kotlin/org/polyfrost/polyplus/client/emotes/EmoteWheelKeybind.kt b/src/main/kotlin/org/polyfrost/polyplus/client/emotes/EmoteWheelKeybind.kt index c32f307..ce81f71 100644 --- a/src/main/kotlin/org/polyfrost/polyplus/client/emotes/EmoteWheelKeybind.kt +++ b/src/main/kotlin/org/polyfrost/polyplus/client/emotes/EmoteWheelKeybind.kt @@ -15,11 +15,14 @@ import org.polyfrost.oneconfig.api.event.v1.eventHandler import org.polyfrost.oneconfig.api.event.v1.events.TickEvent import org.polyfrost.polyplus.PolyPlusConstants import org.polyfrost.polyplus.client.gui.EmoteWheelScreen +import java.util.concurrent.atomic.AtomicBoolean object EmoteWheelKeybind { private val logger = LogManager.getLogger("polyplus/emote-wheel") private lateinit var keyMapping: KeyMapping + private val registered = AtomicBoolean(false) + fun isHeld(): Boolean { val key = InputConstants.getKey(keyMapping.saveString()) if (key.type == InputConstants.Type.MOUSE) { @@ -35,6 +38,8 @@ object EmoteWheelKeybind { } fun register() { + if (!registered.compareAndSet(false, true)) return + val mapping = KeyMapping( "key.polyplus.emote_wheel", InputConstants.Type.KEYSYM, diff --git a/src/main/kotlin/org/polyfrost/polyplus/client/gui/preview/PlayerPreviewRenderer.kt b/src/main/kotlin/org/polyfrost/polyplus/client/gui/preview/PlayerPreviewRenderer.kt index a509e7f..fef63e0 100644 --- a/src/main/kotlin/org/polyfrost/polyplus/client/gui/preview/PlayerPreviewRenderer.kt +++ b/src/main/kotlin/org/polyfrost/polyplus/client/gui/preview/PlayerPreviewRenderer.kt @@ -368,9 +368,11 @@ object PlayerPreviewRenderer { previewCape = capeOverride(source)?.let { ClientAsset.ResourceTexture(it).texturePath() } renderingPreview = true try { - val level = mc.level - if (level != null && mc.cameraEntity != null) renderEntity(mc, level, source, yawDeg, w, h, modelScale, verticalAnchor) - else renderDirect(mc, source, yawDeg, w, h, modelScale, verticalAnchor) + org.polyfrost.polyplus.client.PolyPlusSentry.handlingLocally { + val level = mc.level + if (level != null && mc.cameraEntity != null) renderEntity(mc, level, source, yawDeg, w, h, modelScale, verticalAnchor) + else renderDirect(mc, source, yawDeg, w, h, modelScale, verticalAnchor) + } } catch (t: Throwable) { LOG.error("[preview] entity submit failed; skipping frame", t) } finally { diff --git a/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt b/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt index f733e7a..e66e6ca 100644 --- a/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt +++ b/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt @@ -103,6 +103,13 @@ class IgnoredCrashReportTest { return "$description: $throwable" } + private fun throwableWith(vararg frames: Pair): Throwable = + RuntimeException("boom").apply { + stackTrace = frames + .map { (className, methodName) -> StackTraceElement(className, methodName, null, -1) } + .toTypedArray() + } + @Test fun `ignores crashes the player asked for`() { assertTrue(PolyPlusSentry.isIgnoredCrashReport(summary(CRASHPATCH_REPORT), CRASHPATCH_REPORT)) @@ -184,6 +191,44 @@ class IgnoredCrashReportTest { assertTrue(PolyPlusSentry.involvesPolyPlus(ourMixin)) } + @Test + fun `reaches the same verdict from a live throwable`() { + val foreign = throwableWith( + "tomeko.legacyskyblock.neu.PetFetcher" to "getIcon", + "java.util.concurrent.ThreadPoolExecutor" to "runWorker", + ) + val ourCode = throwableWith( + "org.polyfrost.polyplus.client.cosmetics.render.CosmeticRenderer" to "render", + "net.minecraft.client.renderer.entity.LivingEntityRenderer" to "render", + ) + val ourMixin = throwableWith( + "net.minecraft.client.gui.screens.ChatScreen" to "handler\$zzk000\$polyplus\$onKeyPressed", + "net.minecraft.client.gui.screens.ChatScreen" to "keyPressed", + ) + + assertFalse(PolyPlusSentry.involvesPolyPlus(foreign)) + assertTrue(PolyPlusSentry.involvesPolyPlus(ourCode)) + assertTrue(PolyPlusSentry.involvesPolyPlus(ourMixin)) + } + + @Test + fun `finds us further down the cause chain`() { + val ours = throwableWith("org.polyfrost.polyplus.client.cosmetics.CosmeticCatalog" to "refreshCatalog") + val wrapped = throwableWith("java.util.concurrent.CompletableFuture" to "wrapInCompletionException") + .initCause(ours) + + assertTrue(PolyPlusSentry.involvesPolyPlus(wrapped)) + } + + @Test + fun `terminates on a self-referencing cause chain`() { + val foreign = throwableWith("com.ishland.c2me.common.CheckedThreadLocalRandom" to "handleNotOwner") + val outer = throwableWith("net.minecraft.client.Minecraft" to "runTick").initCause(foreign) + foreign.initCause(outer) + + assertFalse(PolyPlusSentry.involvesPolyPlus(outer)) + } + @Test fun `matches the description alone, as the live path has it`() { assertTrue(PolyPlusSentry.isIgnoredCrashReport("Unexpected error: java.lang.RuntimeException: Crash requested by CrashPatch")) From f4dab4c651b91c9ffe7ab0c71e877028d064fbd5 Mon Sep 17 00:00:00 2001 From: lowercasebtw Date: Fri, 7 Aug 2026 23:50:45 -0400 Subject: [PATCH 3/5] Undo involvesPolyPlus stuff --- .../client/PolyPlusCrashLogUploader.kt | 1 - .../polyplus/client/PolyPlusSentry.kt | 31 +---- .../polyplus/test/IgnoredCrashReportTest.kt | 107 ------------------ 3 files changed, 1 insertion(+), 138 deletions(-) diff --git a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusCrashLogUploader.kt b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusCrashLogUploader.kt index 8e0d36d..050cd48 100644 --- a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusCrashLogUploader.kt +++ b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusCrashLogUploader.kt @@ -110,7 +110,6 @@ object PolyPlusCrashLogUploader { val isJvmFatal = file.name.startsWith("hs_err_pid") val body = prepare(file, isJvmFatal) ?: continue val summary = summarize(body, isJvmFatal) - if (!PolyPlusSentry.involvesPolyPlus(body)) continue if (isJvmFatal) { if (NATIVE_OUT_OF_MEMORY.containsMatchIn(body)) continue } else { diff --git a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt index f359653..ebbd977 100644 --- a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt +++ b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt @@ -195,7 +195,7 @@ object PolyPlusSentry { isAttachStacktrace = true setBeforeSend { event, _ -> val t = event.throwable - if (t != null && (isNeverReported(t) || isForeignUncaughtException(event, t))) { + if (t != null && isNeverReported(t)) { null } else { rateUncaughtException(event) @@ -277,12 +277,6 @@ object PolyPlusSentry { send(throwable, CrashKind.RUNTIME_ERROR, null, Thread.currentThread()) } - private fun isForeignUncaughtException(event: SentryEvent, throwable: Throwable): Boolean { - val wrapper = event.throwableMechanism as? ExceptionMechanismException ?: return false - if (wrapper.exceptionMechanism?.type != UNCAUGHT_MECHANISM) return false - return !involvesPolyPlus(throwable) - } - private fun rateUncaughtException(event: SentryEvent) { val wrapper = event.throwableMechanism as? ExceptionMechanismException ?: return if (wrapper.exceptionMechanism?.type != UNCAUGHT_MECHANISM) return @@ -469,27 +463,6 @@ object PolyPlusSentry { return body.contains(DELIBERATE_CRASH_CLASS) || OUT_OF_MEMORY.containsMatchIn(body) } - private const val POLYPLUS_PACKAGE = "org.polyfrost.polyplus" - private val POLYPLUS_MIXIN_FRAME = Regex("\\$[a-z]{3}\\d+[$]polyplus[$]") - - internal fun involvesPolyPlus(body: String): Boolean = - body.contains(POLYPLUS_PACKAGE) || POLYPLUS_MIXIN_FRAME.containsMatchIn(body) - - private fun isPolyPlusFrame(frame: StackTraceElement): Boolean = - frame.className.startsWith(POLYPLUS_PACKAGE) || POLYPLUS_MIXIN_FRAME.containsMatchIn(frame.methodName) - - internal fun involvesPolyPlus(throwable: Throwable): Boolean { - var cause: Throwable? = throwable - var hops = 0 - while (cause != null && hops++ < MAX_UNWRAP_DEPTH) { - if (cause.stackTrace.any(::isPolyPlusFrame)) return true - val next = cause.cause - if (next === cause) break - cause = next - } - return false - } - private fun isDeliberateCrash(throwable: Throwable): Boolean { var cause: Throwable? = throwable while (cause != null) { @@ -544,8 +517,6 @@ object PolyPlusSentry { if (locallyHandled.get() > 0) return - if (!involvesPolyPlus(throwable)) return - val description = if (title.isNullOrBlank()) throwable.toString() else "$title: $throwable" if (isIgnoredCrashReport(description) || isNeverReported(throwable)) { PolyPlusCrashLogUploader.recordIgnoredCrash(throwable) diff --git a/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt b/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt index e66e6ca..ffe7270 100644 --- a/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt +++ b/src/test/kotlin/org/polyfrost/polyplus/test/IgnoredCrashReportTest.kt @@ -103,13 +103,6 @@ class IgnoredCrashReportTest { return "$description: $throwable" } - private fun throwableWith(vararg frames: Pair): Throwable = - RuntimeException("boom").apply { - stackTrace = frames - .map { (className, methodName) -> StackTraceElement(className, methodName, null, -1) } - .toTypedArray() - } - @Test fun `ignores crashes the player asked for`() { assertTrue(PolyPlusSentry.isIgnoredCrashReport(summary(CRASHPATCH_REPORT), CRASHPATCH_REPORT)) @@ -129,106 +122,6 @@ class IgnoredCrashReportTest { assertFalse(PolyPlusSentry.isIgnoredCrashReport(summary(GENUINE_REPORT), GENUINE_REPORT)) } - @Test - fun `uploads only crashes PolyPlus appears in`() { - val otherModCrash = """ - ---- Minecraft Crash Report ---- - - Description: Initializing game - - java.lang.RuntimeException: Could not execute entrypoint stage 'main' due to errors, provided by 'controlify' at 'dev.isxander.controlify.ControlifyBootstrap'! - at net.fabricmc.loader.impl.FabricLoaderImpl.invokeEntrypoints(FabricLoaderImpl.java:411) - at dev.isxander.controlify.ControlifyBootstrap.onInitialize(ControlifyBootstrap.java:25) - Caused by: org.spongepowered.asm.mixin.injection.throwables.InjectionError: Critical injection failure: Redirector onKey${'$'}initializeVelocityCipher in krypton.mixins.json:shared.network.pipeline.encryption.ServerLoginNetworkHandlerMixin from mod krypton failed injection check - """.trimIndent() - - val vanillaCrash = """ - ---- Minecraft Crash Report ---- - - Description: Unexpected error - - java.lang.NullPointerException: Cannot invoke "net.minecraft.client.network.ClientPlayerEntity.getInventory()" because "this.client.player" is null - at net.minecraft.client.network.ClientPlayerInteractionManager.syncSelectedSlot(ClientPlayerInteractionManager.java:308) - at net.minecraft.client.MinecraftClient.tick(MinecraftClient.java:1888) - """.trimIndent() - - val driverFatal = """ - # A fatal error has been detected by the Java Runtime Environment: - # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffb6fbe646c - # Problematic frame: - # C [nvoglv64.dll+0xdc646c] - """.trimIndent() - - assertFalse(PolyPlusSentry.involvesPolyPlus(otherModCrash)) - assertFalse(PolyPlusSentry.involvesPolyPlus(vanillaCrash)) - assertFalse(PolyPlusSentry.involvesPolyPlus(driverFatal)) - assertFalse(PolyPlusSentry.involvesPolyPlus(GENUINE_REPORT)) - } - - @Test - fun `keeps crashes our own code and mixins are in`() { - val ourCode = """ - ---- Minecraft Crash Report ---- - - Description: Rendering entity - - java.lang.NullPointerException: bone was null - at org.polyfrost.polyplus.client.cosmetics.render.CosmeticRenderer.render(CosmeticRenderer.kt:39) - at net.minecraft.client.renderer.entity.LivingEntityRenderer.render(LivingEntityRenderer.java:112) - """.trimIndent() - - val ourMixin = """ - ---- Minecraft Crash Report ---- - - Description: Rendering screen - - java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 0 - at net.minecraft.client.gui.screens.ChatScreen.handler${'$'}zzk000${'$'}polyplus${'$'}onKeyPressed(ChatScreen.java:8123) - at net.minecraft.client.gui.screens.ChatScreen.keyPressed(ChatScreen.java:214) - """.trimIndent() - - assertTrue(PolyPlusSentry.involvesPolyPlus(ourCode)) - assertTrue(PolyPlusSentry.involvesPolyPlus(ourMixin)) - } - - @Test - fun `reaches the same verdict from a live throwable`() { - val foreign = throwableWith( - "tomeko.legacyskyblock.neu.PetFetcher" to "getIcon", - "java.util.concurrent.ThreadPoolExecutor" to "runWorker", - ) - val ourCode = throwableWith( - "org.polyfrost.polyplus.client.cosmetics.render.CosmeticRenderer" to "render", - "net.minecraft.client.renderer.entity.LivingEntityRenderer" to "render", - ) - val ourMixin = throwableWith( - "net.minecraft.client.gui.screens.ChatScreen" to "handler\$zzk000\$polyplus\$onKeyPressed", - "net.minecraft.client.gui.screens.ChatScreen" to "keyPressed", - ) - - assertFalse(PolyPlusSentry.involvesPolyPlus(foreign)) - assertTrue(PolyPlusSentry.involvesPolyPlus(ourCode)) - assertTrue(PolyPlusSentry.involvesPolyPlus(ourMixin)) - } - - @Test - fun `finds us further down the cause chain`() { - val ours = throwableWith("org.polyfrost.polyplus.client.cosmetics.CosmeticCatalog" to "refreshCatalog") - val wrapped = throwableWith("java.util.concurrent.CompletableFuture" to "wrapInCompletionException") - .initCause(ours) - - assertTrue(PolyPlusSentry.involvesPolyPlus(wrapped)) - } - - @Test - fun `terminates on a self-referencing cause chain`() { - val foreign = throwableWith("com.ishland.c2me.common.CheckedThreadLocalRandom" to "handleNotOwner") - val outer = throwableWith("net.minecraft.client.Minecraft" to "runTick").initCause(foreign) - foreign.initCause(outer) - - assertFalse(PolyPlusSentry.involvesPolyPlus(outer)) - } - @Test fun `matches the description alone, as the live path has it`() { assertTrue(PolyPlusSentry.isIgnoredCrashReport("Unexpected error: java.lang.RuntimeException: Crash requested by CrashPatch")) From c5c60185ebd5b931ebb0fd9f3267cba101b2cef3 Mon Sep 17 00:00:00 2001 From: lowercasebtw Date: Sat, 8 Aug 2026 01:02:08 -0400 Subject: [PATCH 4/5] Update kotlinx & Don't shade/relocate it --- fabric.gradle.kts | 56 ++++++------------------------ fabric.obf.gradle.kts | 56 ++++++------------------------ gradle/fabric.versions.toml | 2 ++ gradle/libs.versions.toml | 8 ++--- src/main/resources/fabric.mod.json | 2 +- 5 files changed, 26 insertions(+), 98 deletions(-) diff --git a/fabric.gradle.kts b/fabric.gradle.kts index 02e06e6..d8d189b 100644 --- a/fabric.gradle.kts +++ b/fabric.gradle.kts @@ -127,46 +127,11 @@ tasks.jar { } } -val serializationRelocatedPackage = "org.polyfrost.polyplus.libs.serialization" - -val serializationShade: Configuration by configurations.creating { - isCanBeConsumed = false - isCanBeResolved = true - isTransitive = false -} - -val shadedArchiveBaseName = property("mod.id") as String -val shadedArchiveVersion = version.toString() - -val relocateSerialization = tasks.register("relocateSerialization") { - group = "build" - description = "Rewrites kotlinx.serialization to $serializationRelocatedPackage across the mod" - - from(tasks.jar.map { zipTree(it.archiveFile) }) - configurations = listOf(serializationShade) - relocate("kotlinx.serialization", serializationRelocatedPackage) - duplicatesStrategy = DuplicatesStrategy.INCLUDE - mergeServiceFiles() - - exclude( - "META-INF/MANIFEST.MF", - "META-INF/*.SF", - "META-INF/*.DSA", - "META-INF/*.RSA", - "META-INF/versions/**/module-info.class", - "module-info.class", - ) - - archiveBaseName = shadedArchiveBaseName - archiveVersion = shadedArchiveVersion - archiveClassifier = "shaded" - destinationDirectory = layout.buildDirectory.dir("libs") - - isPreserveFileTimestamps = false - isReproducibleFileOrder = true -} - -tasks.assemble { dependsOn(relocateSerialization) } +val serializationProvidedByFlk = setOf( + "org.jetbrains.kotlinx:kotlinx-serialization-core", + "org.jetbrains.kotlinx:kotlinx-serialization-json", + "org.jetbrains.kotlinx:kotlinx-serialization-cbor", +) dependencies { minecraft("com.mojang:minecraft:${versionCatalog("common$catalogVersion").findVersion("minecraft").get()}") @@ -180,6 +145,7 @@ dependencies { catalogLib("fabric-api")?.let { implementation(it) { isTransitive = true } } catalogLib("fabric-loader")?.let { implementation(it) { isTransitive = true } } + catalogLib("fabric-language-kotlin")?.let { implementation(it) { isTransitive = false } } catalogLib("sodium")?.let { compileOnly(it) { isTransitive = false } } @@ -190,7 +156,7 @@ dependencies { sentryShade(libs.sentry) implementation(files(relocateSentry.flatMap { it.archiveFile })) - serializationShade(libs.bundles.serialization.shade) + implementation(libs.bundles.serialization) implementation(libs.bundles.ktor.client) implementation(libs.bundles.ktor.server) implementation(libs.bundles.ktor.serialization) @@ -202,18 +168,18 @@ dependencies { run { val bundledRoots = libs.bundles.ktor.client.get() + libs.bundles.ktor.server.get() + - libs.bundles.ktor.serialization.get() + libs.bundles.ktor.serialization.get() + + libs.bundles.serialization.get() val closure = configurations.detachedConfiguration( *bundledRoots.map { dependencies.create(it) }.toTypedArray() ) fun key(group: String?, name: String) = "$group:${name.removeSuffix("-jvm")}" - val relocated = serializationShade.dependencies.map { key(it.group, it.name) }.toSet() closure.resolvedConfiguration.resolvedArtifacts.forEach { art -> val id = art.moduleVersion.id if (id.group == "org.jetbrains.kotlin") return@forEach - if (key(id.group, id.name) in relocated) return@forEach + if (key(id.group, id.name) in serializationProvidedByFlk) return@forEach dependencies.include("${id.group}:${id.name}:${id.version}") } } @@ -273,7 +239,7 @@ val modrinthId = listOf("oneconfig.publish.modrinth", "publish.modrinth") val modrinthToken = listOf("oneconfig.publish.modrinth.token", "publish.modrinth.token", "modrinth.token") .firstNotNullOfOrNull { findProperty(it) }?.toString()?.takeIf { it.isNotBlank() } val minecraftVersion = modrinthMinecraftVersionOverride[mcVersion] ?: listOf(mcVersion) -val publishJarTaskName = "relocateSerialization" +val publishJarTaskName = if ("remapJar" in tasks.names) "remapJar" else "jar" val changelogs = rootProject.file("CHANGELOG.md").takeIf { it.exists() }?.readText() ?: "No changelog provided." publishMods { diff --git a/fabric.obf.gradle.kts b/fabric.obf.gradle.kts index 4f9fda4..ed02a60 100644 --- a/fabric.obf.gradle.kts +++ b/fabric.obf.gradle.kts @@ -128,48 +128,11 @@ tasks.jar { } } -val serializationRelocatedPackage = "org.polyfrost.polyplus.libs.serialization" - -val serializationShade: Configuration by configurations.creating { - isCanBeConsumed = false - isCanBeResolved = true - isTransitive = false -} - -val shadedArchiveBaseName = property("mod.id") as String -val shadedArchiveVersion = version.toString() - -val relocateSerialization = tasks.register("relocateSerialization") { - group = "build" - description = "Rewrites kotlinx.serialization to $serializationRelocatedPackage across the mod" - - from(tasks.jar.map { zipTree(it.archiveFile) }) - configurations = listOf(serializationShade) - relocate("kotlinx.serialization", serializationRelocatedPackage) - duplicatesStrategy = DuplicatesStrategy.INCLUDE - mergeServiceFiles() - - exclude( - "META-INF/MANIFEST.MF", - "META-INF/*.SF", - "META-INF/*.DSA", - "META-INF/*.RSA", - "META-INF/versions/**/module-info.class", - "module-info.class", - ) - - archiveBaseName = shadedArchiveBaseName - archiveVersion = shadedArchiveVersion - archiveClassifier = "shaded" - destinationDirectory = layout.buildDirectory.dir("libs") - - isPreserveFileTimestamps = false - isReproducibleFileOrder = true -} - -tasks.named("remapJar") { - inputFile = relocateSerialization.flatMap { it.archiveFile } -} +val serializationProvidedByFlk = setOf( + "org.jetbrains.kotlinx:kotlinx-serialization-core", + "org.jetbrains.kotlinx:kotlinx-serialization-json", + "org.jetbrains.kotlinx:kotlinx-serialization-cbor", +) dependencies { minecraft("com.mojang:minecraft:${versionCatalog("common$catalogVersion").findVersion("minecraft").get()}") @@ -190,6 +153,7 @@ dependencies { catalogLib("fabric-api")?.let { modImplementation(it) { isTransitive = true } } catalogLib("fabric-loader")?.let { modImplementation(it) { isTransitive = true } } + catalogLib("fabric-language-kotlin")?.let { modImplementation(it) { isTransitive = false } } catalogLib("sodium")?.let { modCompileOnly(it) { isTransitive = false } } @@ -200,7 +164,7 @@ dependencies { sentryShade(libs.sentry) implementation(files(relocateSentry.flatMap { it.archiveFile })) - serializationShade(libs.bundles.serialization.shade) + implementation(libs.bundles.serialization) implementation(libs.bundles.ktor.client) implementation(libs.bundles.ktor.server) implementation(libs.bundles.ktor.serialization) @@ -212,18 +176,18 @@ dependencies { run { val bundledRoots = libs.bundles.ktor.client.get() + libs.bundles.ktor.server.get() + - libs.bundles.ktor.serialization.get() + libs.bundles.ktor.serialization.get() + + libs.bundles.serialization.get() val closure = configurations.detachedConfiguration( *bundledRoots.map { dependencies.create(it) }.toTypedArray() ) fun key(group: String?, name: String) = "$group:${name.removeSuffix("-jvm")}" - val relocated = serializationShade.dependencies.map { key(it.group, it.name) }.toSet() closure.resolvedConfiguration.resolvedArtifacts.forEach { art -> val id = art.moduleVersion.id if (id.group == "org.jetbrains.kotlin") return@forEach - if (key(id.group, id.name) in relocated) return@forEach + if (key(id.group, id.name) in serializationProvidedByFlk) return@forEach dependencies.include("${id.group}:${id.name}:${id.version}") } } diff --git a/gradle/fabric.versions.toml b/gradle/fabric.versions.toml index 99ce49f..4b5d045 100644 --- a/gradle/fabric.versions.toml +++ b/gradle/fabric.versions.toml @@ -1,10 +1,12 @@ [versions] loom = "1.16-SNAPSHOT" loader = "0.19.2" +language-kotlin = "1.13.13+kotlin.2.4.10" [libraries] fabric-loader = { module = "net.fabricmc:fabric-loader", version.ref = "loader" } fabric-loader-junit = { module = "net.fabricmc:fabric-loader-junit", version.ref = "loader" } +fabric-language-kotlin = { module = "net.fabricmc:fabric-language-kotlin", version.ref = "language-kotlin" } [plugins] loom-remap = {id = "net.fabricmc.fabric-loom-remap", version.ref = "loom"} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 225634d..9b1aa9a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "2.3.0" kotlinx-serialization = "2.3.0" -kotlinx-serialization-runtime = "1.9.0" +kotlinx-serialization-runtime = "1.11.0" atomicfu = "0.27.0" ktor = "3.3.1" sentry = "7.18.0" @@ -26,8 +26,6 @@ ktor-serialization-kotlinx-json = { group = "io.ktor", name = "ktor-serializatio kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core-jvm", version.ref = "kotlinx-serialization-runtime" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json-jvm", version.ref = "kotlinx-serialization-runtime" } kotlinx-serialization-json-io = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json-io-jvm", version.ref = "kotlinx-serialization-runtime" } -ktor-serialization-kotlinx = { group = "io.ktor", name = "ktor-serialization-kotlinx-jvm", version.ref = "ktor" } -ktor-serialization-kotlinx-json-jvm = { group = "io.ktor", name = "ktor-serialization-kotlinx-json-jvm", version.ref = "ktor" } mixin-extras = { module = "io.github.llamalad7:mixinextras-common", version.ref = "mixin-extras" } mixin-squared = { module = "com.github.bawnorton.mixinsquared:mixinsquared-common", version.ref = "mixin-squared" } @@ -41,12 +39,10 @@ junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "jun ktor-client = ["ktor-client-core", "ktor-client-cio", "ktor-client-content-negotiation"] ktor-server = ["ktor-server-websockets"] ktor-serialization = ["ktor-serialization-kotlinx-json"] -serialization-shade = [ +serialization = [ "kotlinx-serialization-core", "kotlinx-serialization-json", "kotlinx-serialization-json-io", - "ktor-serialization-kotlinx", - "ktor-serialization-kotlinx-json-jvm", ] [plugins] diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index cdcf6f3..ccb79d4 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,7 +24,7 @@ "fabric-api": "*", "fabricloader": ">=0.16.0", "java": ">=21", - "fabric-language-kotlin": ">=1.13.8", + "fabric-language-kotlin": ">=1.13.13", "oneconfigv1": ">=1.1.2" } } From a45d040acc009f967e263a522c05660582060be0 Mon Sep 17 00:00:00 2001 From: lowercasebtw Date: Sat, 8 Aug 2026 01:16:14 -0400 Subject: [PATCH 5/5] Resolve Merge Mistake --- .../org/polyfrost/polyplus/client/PolyPlusSentry.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt index 16c3012..1143d6e 100644 --- a/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt +++ b/src/main/kotlin/org/polyfrost/polyplus/client/PolyPlusSentry.kt @@ -501,6 +501,18 @@ object PolyPlusSentry { return false } + private val locallyHandled = ThreadLocal.withInitial { 0 } + + fun handlingLocally(block: () -> T): T { + val depth = locallyHandled.get() + locallyHandled.set(depth + 1) + return try { + block() + } finally { + locallyHandled.set(depth) + } + } + @JvmStatic fun captureCrashReport(title: String?, throwable: Throwable) { if (!PrivacyConsent.allowsOnlineServices()) return