Tags
Creators
Details
All versions
4.0.0+forge
Release
CatSkinC 4.0.0 (Forge)16 hours ago 11
Uploaded by
Compatibility
Minecraft: Java Edition
1.20.1
Platform
Forge
Supported environments
Client and server
Changes
Added
- Myopia skin LOD for CatSkinC-managed remote skins. When enabled, distant players use lazily cached, pixel-perfect half, quarter, or eighth-resolution copies while GUI, inventory, and tab-list skins retain full resolution.
- A Myopia settings screen with an enabled toggle, a 4–128 block base-distance control, Normal and Panicked modes, and links to Minecraft's keybinding menu.
- Default keybindings for increasing and decreasing the Myopia range with the
Up and Down arrow keys. All settings are persisted in
catskinc.json. - WebSocket client using native
java.net.http.WebSocket(Java 11+) for high-performance real-time skin sync. - Automatic protocol negotiation — client sends
x-catskinc-protocol: 3header on WebSocket handshake. - Resilient connection management:
- Exponential backoff reconnection (1.5s → 60s max).
- Shared circuit breaker across REST, SSE, and WebSocket (unified failure tracking).
- Automatic fallback to SSE when WebSocket fails or protocol is unsupported.
- Session-token authentication on WebSocket with auto-subscription to local player UUID.
- Protocol version verification in welcome message — closes connection on mismatch.
- In-game toast notifications for WebSocket-originated events (skin updates, server messages).
Changed
- Join flow now attempts WebSocket first, falls back to SSE on failure.
- Shared circuit breaker state between
ServerApiClientandWebSocketClient— REST/SSE/WS failures all contribute to the same breaker. - Heartbeat handling — application-level JSON ping/pong (30s) replaces binary WebSocket ping frames for consistency.
- Managed-skin texture cleanup now also releases all associated Myopia GPU textures and resets per-player LOD state.
Fixed
- Broadcast framing — WebSocket clients now receive raw JSON (SSE
data:prefix stripped server-side). - Handshake protocol header — added
X-CatSkinC-Protocolto WebSocket upgrade request. - Auth race — session token acquired synchronously before WebSocket connection attempt.
- Protocol defense — client verifies
protocol_versionin welcome message, closes on mismatch. - Fallback trigger — SSE fallback now activates on abnormal post-connect disconnections, not just initial connect failure.
- Toast handler — implemented proper
Toasts.info()with translatable titles. - Select-Fetch race eliminated:
selectSkin()now chainsthenRun(refresh)so the POST completes before the GET, eliminating the 4-8s skin delay after upload. - Pending selections cache:
PENDING_SELECTIONSreturns the uploaded skin URL immediately after select, avoiding old-skin flashing. - LAST_CHECK on success only: failed fetches no longer reset the 5s poll timer, preventing a 15s blackout window.
- Fast-retry after null skin: retries fetch in 2s when the server has no cached skin yet, reducing initial sync from 15s to 2s.
- Poll interval reduced: 15s → 5s for faster periodic sync.
- SSE clear-skin handling: clears
BASE_CACHE/TALKING_CACHEon null URL, so players properly revert to the default skin. - SSE reconnect delay: added 1.5s–60s exponential backoff on stream close (was instant reconnect), preventing server flooding.
- Circuit breaker in SSE: SSE checks
circuitOpenUntilMsbefore connecting, eliminating useless retries during outage. - AtomicInteger:
consecutiveFailuresswitched toAtomicIntegerfor correct failure counting. - Always destroy old textures: removed identity check gate on texture release to prevent GPU memory leaks.
- Fixed HTTP 401 on skin downloads: Added
signDownloadUrl()method that appends?exp=&sig=HMAC-signed query parameters to download URLs using the client'srequestSigningKey. This matches the server'sverify_download_signature()check whenENFORCE_SIGNED_DOWNLOADS=true. - Replaced per-player TextureManager registrations with a single shared dynamic texture: CatSkinC no longer registers per-player textures (
catskinc:remote/<uuid>) with the vanillaTextureManager. Instead, downloadedNativeImageobjects are cached inSKIN_IMAGESand pixel-copied into one sharedcatskinc:dynamic/activetexture on each render frame. This prevents F3+S debug dumps from saving every player's skin to disk. - Broadcast framing — WebSocket clients now receive raw JSON (SSE
data:prefix stripped server-side). - Handshake protocol header — added
X-CatSkinC-Protocolto WebSocket upgrade request. - Auth race — session token acquired synchronously before WebSocket connection attempt.
- Protocol defense — client verifies
protocol_versionin welcome message, closes on mismatch. - Fallback trigger — SSE fallback now activates on abnormal post-connect disconnections, not just initial connect failure.
- Toast handler — implemented proper
Toasts.info()with translatable titles. - Select-Fetch race eliminated:
selectSkin()now chainsthenRun(refresh)so the POST completes before the GET, eliminating the 4-8s skin delay after upload. - Pending selections cache:
PENDING_SELECTIONSreturns the uploaded skin URL immediately after select, avoiding old-skin flashing. - LAST_CHECK on success only: failed fetches no longer reset the 5s poll timer, preventing a 15s blackout window.
- Fast-retry after null skin: retries fetch in 2s when the server has no cached skin yet, reducing initial sync from 15s to 2s.
- Poll interval reduced: 15s → 5s for faster periodic sync.
- SSE clear-skin handling: clears
BASE_CACHE/TALKING_CACHEon null URL, so players properly revert to the default skin. - SSE reconnect delay: added 1.5s–60s exponential backoff on stream close (was instant reconnect), preventing server flooding.
- Circuit breaker in SSE: SSE checks
circuitOpenUntilMsbefore connecting, eliminating useless retries during outage. - AtomicInteger:
consecutiveFailuresswitched toAtomicIntegerfor correct failure counting. - Always destroy old textures: removed identity check gate on texture release to prevent GPU memory leaks.
- Fixed HTTP 401 on skin downloads: Added
signDownloadUrl()method that appends?exp=&sig=HMAC-signed query parameters to download URLs using the client'srequestSigningKey. This matches the server'sverify_download_signature()check whenENFORCE_SIGNED_DOWNLOADS=true. - Replaced per-player TextureManager registrations with a single shared dynamic texture: CatSkinC no longer registers per-player textures (
catskinc:remote/<uuid>) with the vanillaTextureManager. Instead, downloadedNativeImageobjects are cached inSKIN_IMAGESand pixel-copied into one sharedcatskinc:dynamic/activetexture on each render frame. This prevents F3+S debug dumps from saving every player's skin to disk. - Security: Request signing (HMAC-SHA256) applied to WebSocket upgrade request.
- Hardened transitive dependencies against Dependabot advisories. Added
resolutionStrategy { force ... }constraints inbuild.gradleto pin patched versions of vulnerable transitive libraries on both the project and buildscript classpaths:- Netty →
4.1.136.Final(fixes GHSA-558v-64gr-wgg4 Bzip2Decoder infinite loop and related netty advisories). - Log4j →
2.25.4(fixes Log4j TLS hostname verification, XML escaping, and related advisories). - Guava →
32.0.0-jre(fixes temp-dir and information-disclosure advisories). - Commons IO →
2.16.1, Commons Compress →1.26.0, Commons Lang3 →3.18.0. - Buildscript classpath: Commons Text →
1.12.0, Commons Beanutils →1.11.0, Plexus Utils →3.6.1.
- Netty →
- These libraries are Minecraft-provided runtime/build-tooling dependencies and are not bundled into the shipped mod JAR, so the end-user artifact is unaffected; the constraints keep the dev runtime and CI dependency graph clean.
Projects on Modrinth are automatically available through a Maven repository for use with JVM build tools such as Gradle. To learn more about the Modrinth Maven API, click here.
Note: When available, you should use the creator's maven repo instead as it will have transitive dependency information that the Modrinth Maven API does not. You may also end up with duplicate dependencies if you use a mix of Modrinth and non-Modrinth Maven repositories for your dependencies, because the group identifier will be different when served through the Modrinth Maven API.
Maven coordinates:
Version ID:
build.gradle:
repositories {
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
// forRepositories(fg.repository) // Uncomment when using ForgeGradle
filter {
includeGroup "maven.modrinth"
}
}
}
// Standard Gradle dependency
dependencies {
implementation "maven.modrinth:HQ1Bl3VC:Zfbmnq5x"
}
// Legacy Loom dependency
dependencies {
modImplementation "maven.modrinth:HQ1Bl3VC:Zfbmnq5x"
}

