No description
Find a file
ReisMiner 09705fa4f7
All checks were successful
build / build (26.1.2) (push) Successful in 1m24s
build / build (26.3) (push) Successful in 1m22s
stuff
2026-10-16 15:17:39 +02:00
.forgejo/workflows stuff 2026-10-16 15:17:39 +02:00
docs switch to websockets 2026-10-06 13:19:33 +02:00
examplemod stuff 2026-10-16 15:17:39 +02:00
gradle stuff 2026-10-16 15:17:39 +02:00
protocol switch to websockets 2026-10-06 13:19:33 +02:00
src stuff 2026-10-16 15:17:39 +02:00
syncserver switch to websockets 2026-10-06 13:19:33 +02:00
tools basic emote stuff 2026-10-06 11:54:06 +02:00
wardrobemod stuff 2026-10-16 15:17:39 +02:00
.dockerignore maybe works 2026-10-05 19:31:00 +02:00
.gitignore fix compose volumes i hope 2026-10-05 19:49:52 +02:00
build.gradle stuff 2026-10-16 15:17:39 +02:00
docker-compose.yaml switch to websockets 2026-10-06 13:19:33 +02:00
dockerfile did not work... 2026-10-05 19:37:50 +02:00
gradle.properties stuff 2026-10-16 15:17:39 +02:00
gradlew version 1 2026-10-04 01:44:09 +02:00
gradlew.bat version 1 2026-10-04 01:44:09 +02:00
LICENSE.txt version 1 2026-10-04 01:44:09 +02:00
README.md stuff 2026-10-16 15:17:39 +02:00
settings.gradle stuff 2026-10-16 15:17:39 +02:00

CosmeticsLib

A client-side Fabric library mod for Minecraft 26.1.2 and 26.3 that shows cosmetics — capes, hats, pets, skins, particle effects and more — on every player in the world.

CosmeticsLib is infrastructure, not a cosmetic pack: other mods register cosmetics through its API, and CosmeticsLib is responsible for resolving, syncing, caching and rendering them.

┌──────────────┐   registers a cosmetic    ┌───────────────┐
│  your mod    │ ────────────────────────► │  CosmeticsLib │
└──────────────┘                           └───────┬───────┘
                                                   │ renders on every player,
                                                   │ resolves other players' sets
                                                   ▼
                                           ┌───────────────┐
                                           │  sync server  │  (you host this)
                                           └───────┬───────┘
                                                   │ verifies account ownership
                                                   ▼
                                          Mojang session server

Why users never sign up

There is no account, no password, and no OAuth application to register. The mod takes the access token the Minecraft launcher already gave it, asks Mojang to vouch for a one-time nonce, and hands the sync server only that nonce. The server calls Mojang's hasJoined and receives the player's real UUID.

The access token never reaches the sync server. Account identity comes from Mojang, not from the client, so nobody can claim to be someone else. See docs/PROTOCOL.md for the full flow and the security reasoning.

For players

Use the jar matching your Minecraft version and install Fabric API for that version. These are separate builds, not a universal jar; cosmetics and the sync protocol are shared across versions.

Building for either Minecraft version

Use Java 25 to run Gradle:

.\gradlew.bat assemble                              # Minecraft 26.1.2 (default)
.\gradlew.bat assemble -Pminecraft_version=26.3      # Minecraft 26.3

The matching Fabric API and optional Mod Menu compile dependency are selected automatically. Jars are named with mc26.1.2 or mc26.3 and placed in build/<version>/libs/ (and examplemod/build/<version>/libs/ / wardrobemod/build/<version>/libs/). Launch a development client with gradlew :examplemod:runClient -Pminecraft_version=26.3; omit the property for 26.1.2. Only the two explicitly supported releases are accepted.

Installing

Install CosmeticsLib alongside a provider mod to equip its cosmetics. Viewers only need CosmeticsLib: when the configured sync server hosts the provider's data bundles, the library downloads, verifies, caches and renders them, including animated skins/capes and static triangle pets. The provider's Java code is never downloaded or executed. Providers without portable bundles still require their rendering mod on the viewer.

Other players load on render demand, not as a server-wide join scan. Players outside Minecraft's entity render distance/frustum are not fetched automatically. Visible crowds load progressively in batches of up to 50, with one peer request in flight and at least one second between batches. Nearby new players get priority without permanently excluding farther visible players. Fresh snapshots (including empty sets), bounded ETag reuse and the disk/immutable asset caches avoid repeat work; unseen cosmetics do not trigger speculative bundle downloads. Hiding other players' cosmetics also suspends their fetching. When the configured server offers the live channel, visible peer updates and emotes are pushed while it is healthy, and the poll is only the fallback; an older server keeps the poll and disables remote emotes rather than reverting to fast polling.

On each world join, CosmeticsLib automatically clears your own equipped set if no local cosmetic provider is registered. This covers uninstalling the last provider mod, including Example Cosmetics, and also publishes an empty set when the local save is already empty or missing. Registered cosmetic definitions and local runtime/generated/resource asset sources count as providers; downloaded cosmetics do not. Viewing other players' cosmetics is unaffected. The automatic clear is saved and uses the same sync and offline-retry behavior as manual removal.

CosmeticsLib itself provides a client-side removal command, so you can always unequip cosmetics even after uninstalling the mod that supplied them:

/cosmeticslib clear cape        # remove one slot, leaving other slots equipped
/cosmeticslib clear pet
/cosmeticslib clear all         # remove every slot, including custom slots
/cosmeticslib clear             # same as clear all
/cosmeticslib clear mymod:aura   # remove a provider's custom slot

Built-in slot names are cape, hat, back, shoulder, pet, skin, wings, particle, tail and emote; fully qualified names such as cosmeticslib:cape also work. Press Tab for available slots, including saved custom slots from uninstalled providers. Clearing a slot removes every cosmetic in that slot. The command works in singleplayer and on vanilla servers.

Removals are saved locally and queued for your configured sync server, even if your local set was already empty. Use the same sync server where the cosmetics were published to remove them for other players; they see the change after their next sync poll. Offline changes are republished on reconnect. Without a configured sync server, the command reports that removal is local only. Individual clears reuse the remaining cosmetics' existing immutable assets without requiring the provider to upload them again or changing their model versions.

In-game settings

With Mod Menu installed, a CosmeticsLib entry appears in the mod list; its config button opens a native settings screen covering the sync server URL, the other players' poll interval, the insecure-HTTP opt-in, own/other-player visibility and the disk cache budget. Save validates first and reports any problem in plain language; Cancel (or Escape) discards. No Cloth Config or YetAnotherConfigLib is involved, and CosmeticsLib runs unchanged when Mod Menu is absent.

The same settings live in config/cosmeticslib.json, which remains editable by hand. On a first run with no file the defaults are:

{
  "syncServerUrl": "https://mccosmeticslib.ramon.cc",
  "allowInsecureHttp": false,
  "pollIntervalSeconds": 60
}

That is a default only: an existing file keeps every value already in it, and an explicit "syncServerUrl": "" still means "local cosmetics only". clientId, the bearer token, their expiry and any namespace upload secrets are never shown or edited by the screen. Changing the server to a different host forgets the previous server's sign-in, because a token is only valid for the origin that issued it; a fresh sign-in happens after the next world join.

For server hosts

One Java process, one jar. The server embeds Undertow, so its HTTP API and the optional live WebSocket channel share a single listener and port; the wire format dependency is Gson (via :protocol).

gradle :syncserver:build          # produces syncserver-<version>-all.jar
java -jar syncserver-0.1-all.jar  # uses config.json in the working directory

For development, .\gradlew.bat :syncserver:run uses the repository-root config.json and data directory (unless dataDir is overridden), not a separate store under syncserver.

Full hosting guide, reverse-proxy and systemd examples, and the security defaults are in syncserver/README.md.

For modders

Declare the custom cosmetics entrypoint — it is order-independent, so your registration cannot race another mod's init.

{
  "entrypoints": { "cosmetics": ["com.example.mymod.MyCosmetics"] },
  "depends": { "cosmeticslib": ">=0.1" }
}
public final class MyCosmetics implements CosmeticsEntrypoint {
    @Override
    public void registerCosmetics(CosmeticRegistrar registrar) {
        registrar.register(CosmeticDefinition.builder(CosmeticId.of("mymod", "flame_cape"))
                .slot(CosmeticSlot.CAPE)
                .attachment(AttachmentPoint.BACK)
                .displayName(Component.translatable("cosmetic.mymod.flame_cape"))
                .animator(CosmeticId.of("cosmeticslib", "sway"))
                .build());
    }
}

Slots and attachment points are open registries, not enums, so you can add your own.

What you get for free:

Sync Your cosmetic shows on other players, fetched and polled automatically
Caching Models and textures cached on disk, offline-tolerant
Animation Built-in animators, or write your own; composable via blend/sequence
Fallback rendering Players without your mod still see it, via a data-only portable model
Skin replacement Static or animated body skins with optional cape/elytra overrides
Particles PARTICLE-slot cosmetics spawned around the player

Full guide: docs/API.md.

Animated body skins use CosmeticSkinTextures.registerAnimatedSkin(id, frames, ticksPerFrame), just like capes. Supply complete matching 64x64 skin atlases; two game ticks per frame means 10 fps. Resource frames are automatically shared in format-4 portable bundles for CosmeticsLib-only viewers. Use an updated library and sync server for animated skin delivery.

Repository layout

Path What it is
src/main/java Platform-neutral public API, registries, config, caching
src/client/java Minecraft-client rendering, HTTP sync, session auth and the mixins
protocol/ The wire format, shared by the mod and the server so they cannot drift
syncserver/ The self-hosted server: plain Java on embedded Undertow (HTTP API + optional live WebSocket on one port)
examplemod/ A worked add-on with cape, hat, pet, tree, flower tail, skin, bat wings, shoulder, wings, particle and emote examples
wardrobemod/ Player-facing provider with persistent history, native imports, model sizing, animated textures and a keyframe emote editor
docs/ Protocol spec, modder API guide, research notes
tools/ Build and verification helpers

See it working in one command

examplemod/ is a real Fabric mod that registers eleven cosmetics through the same public entrypoint any third-party mod would use. It exists so you can watch the whole pipeline run without writing anything:

./gradlew :examplemod:runClient

It equips itself on join without changing your camera perspective. In first person the equipped skin is now visible on your arms/hands (and on the held map), because first-person rendering reads the texture straight from the player; the other cosmetics are body geometry and need F5 to be seen in third person. Your own particle aura is deliberately hidden in first person so it does not fill the camera, while other players' auras still show. You should see a flame cape, party hat, sussy imposter pet, flower tail, example skin, bat wings, a teal shoulder buddy and a sparkle particle aura, and in the log:

(cosmeticslib)  Applying a cape cosmetic through vanilla's cape renderer (the cape will sway normally)
(examplecosmetics) Drawing examplecosmetics:party_hat -- the renderer ran and geometry was submitted

The cosmetics deliberately use different mechanisms, because choosing between them is the main decision an add-on makes. The cape is an animated texture — one registerAnimatedCape call plays 16 flame frames at 10 fps, with no model or renderer. Vanilla draws it, so it swings properly and replaces whatever cape the account has. The hat is geometry, submitted from a renderer, which is what you need for anything vanilla has no equivalent of. The pet is an FBX mesh, drawn with material colours and simulated independently: it pauses and wanders nearby, follows when you move away and faces its own movement. The FBX loader supports binary 7400 static meshes, not skinning or animation. The example also registers your textured GLB tree: /examplecosmetics tree switches the pet to the tree model. GLB support includes static glTF 2.0 scenes, baked node transforms and embedded base-colour textures. The flower tail uses models/flower.glb, mounted at the lower back and extending 0.75 blocks behind the player. The skin animates the pastel rainbow colours of textures/skin/65462827594073e4.png with 32 complete frames at 10 fps (a 3.2-second loop). The source PNG, white stars, transparency, account cape, elytra and arm model are preserved. These equip with the original examples on the first join; /examplecosmetics tail and /examplecosmetics skin toggle them. The bat wings use models/wing.glb in the BACK slot; /examplecosmetics back toggles them. The shoulder buddy and feather wings are baked cuboid geometry declared as inline portable models, so the provider and a library-only viewer render the same numbers; the feather wings carry wing_left/wing_right bones that the flap animator actually moves. The sparkle aura is a metadata-only PARTICLE-slot cosmetic: the definition names a client-owned effect, and the library auto-exports and publishes a bundle containing only that metadata, so a provider and a library-only viewer spawn exactly the same particles (there is no dummy cuboid and no texture). The dance emote is a real, finite player-body animation: the definition carries a keyframe clip against the canonical player bones, which the library adds on top of the vanilla pose. It never starts on equip; the configurable Play Emote key (default B, in Controls → CosmeticsLib) triggers it, and pressing it again stops it. The clip is auto-exported as a format-5 bundle, so a viewer without Example Cosmetics plays the same dance.

The cape uses vanilla's effective 64x32 atlas, and entity geometry is submitted as quads (FBX/GLB triangles repeat their last corner). The example also ships nine generated, data-only bundles: the complete animated cape, the exact hat/cuboid geometry for the party hat and shoulder buddy, the feather wings, all pet/tree/flower/wing triangles with their portable materials and textures, and all 32 animated skin atlases. The sparkle aura and the dance emote are two further syncable examples with no packaged bundle: their metadata-only and clip-only format-5 bundles are auto-exported and uploaded at runtime. The flower's source images are downsampled proportionally to the portable 512-pixel limit without editing the original GLB. The library automatically uploads missing equipped content to an upload-enabled sync host using the owner's normal Minecraft authentication; no namespace secrets or manual imports are needed. Runtime/user-supplied GLB or portable models use the same library APIs and sync flow. Immutable hashes deduplicate bytes and pin each player's exact model version without overwriting a provider's catalog. Other players need only CosmeticsLib and the same sync endpoint. See the hosting guide for host upload limits and optional offline imports.

Toggle them live with a client-side command — no restart, and it works on vanilla servers, because cosmetics never involve the server:

/examplecosmetics          # what is currently equipped
/examplecosmetics cape     # toggle the cape
/examplecosmetics hat      # toggle the hat
/examplecosmetics pet      # toggle the pet
/examplecosmetics tree     # switch to the textured GLB tree companion
/examplecosmetics tail     # toggle the flower tail
/examplecosmetics skin     # toggle the animated rainbow skin
/examplecosmetics back     # toggle the GLB bat wings in the BACK slot
/examplecosmetics shoulder # toggle the baked shoulder buddy
/examplecosmetics wings    # toggle the flapping feather wings
/examplecosmetics aura     # toggle the sparkle particle aura
/examplecosmetics emote    # toggle the opt-in dance body emote (equip; it does not auto-play)
/examplecosmetics play     # play/stop the equipped emote (same as the Play Emote key)
/examplecosmetics clear    # remove every example slot (including the opt-in emote)

The example cape and hat on a player

The mod is the worked example referenced by docs/EXAMPLE_MOD.md.

Building

For the player-facing cosmetic wardrobe, see wardrobemod/README.md. Build it with ./gradlew :wardrobemod:build or launch its development client with ./gradlew :wardrobemod:runClient. Open it with O or /wardrobe. Starter particle and emote imports are in wardrobemod/examples.

./gradlew build

Produces cosmeticslib-<version>.jar, protocol-<version>.jar, and syncserver/build/libs/syncserver-<version>-all.jar.

Requires JDK 25.

Notes for contributors

  • Minecraft 26.1.2 ships unobfuscated. There is deliberately no mappings declaration in build.gradle; adding one fails with "Cannot use Mojang mappings in a non-obfuscated environment." Classes are referenced by their real names.
  • Yarn names do not exist in this version. PlayerRenderer, ResourceLocation, PlayerEntityState and friends are all renamed (AvatarRenderer, Identifier, AvatarRenderState) or gone entirely (Session no longer exists). Ignore Yarn-era tutorials.
  • 26.x uses a deferred render pipeline. RenderLayer.render(PoseStack, MultiBufferSource, …) is now submit(PoseStack, SubmitNodeCollector, …): layers push submit nodes rather than drawing.
  • The mod uses three Mixins: AvatarRenderer.extractRenderState, which carries cosmetics and the frozen emote frame from the entity to the render state for third person; AbstractClientPlayer.getSkin, which applies the equipped skin to the local player's first-person arms/hands and held map; and PlayerModel.setupAnim, which adds a real body-emote clip on top of the vanilla pose at submit and replay. Everything else goes through Fabric's LivingEntityRenderLayerRegistrationCallback, RenderStateDataKey and ModelLayerRegistry.
  • tools/compile-check.ps1 compiles the sources with plain javac for fast iteration; tools/server-smoke-test.ps1 exercises the server's HTTP surface end to end.
  • .\gradlew.bat renderCheck checks FBX face grouping and the cape/cuboid UV layout against Minecraft's actual model APIs. It also runs as part of check and build.
  • .\gradlew.bat petMotionCheck checks seeded wandering, follow hysteresis, speed/turn limits, obstacle avoidance, slabs/stairs, cliffs and safe catch-up. It also runs with check and build.
  • .\gradlew.bat animatedCapeCheck checks animation timing, static-cape compatibility, skin patching and the example's 16 frame atlases. It also runs with check and build.
  • .\gradlew.bat firstPersonCheck checks that the local player's first-person arm/map skin uses the animated cosmetic (model and cape/elytra preserved) and that own particle auras are suppressed in first person while other players' still spawn. It also runs with check and build.
  • .\gradlew.bat providerAssetsCheck resolves downloaded cape, hat and pet bundles on a classpath with no example mod, covering persistent caching and all mesh triangles.
  • .\gradlew.bat :syncserver:providerServerCheck covers authorized hosting, anonymous retrieval, namespace rejection and administrative provider imports using isolated test data.
  • .\gradlew.bat :syncserver:emoteServerCheck covers the transient emote channel: play throttling with stop exemption, clip expiry, nonce freshness and the actor's equipped-slot/digest checks.
  • .\gradlew.bat :syncserver:emoteHttpCheck exercises the real loopback /emotes endpoints with an isolated fake-auth token: 401/403, the publication race, idempotent nonce, 429 throttle, stop exemption, backdated retry, the UUID batch cap and 404 handling.
  • .\gradlew.bat :syncserver:realtimeWsCheck runs the real Undertow listener (HTTP parity plus Java WebSocket clients): anonymous subscribe snapshots, live publication and emote fan-out, HTTP mutation fan-out, auth/cap/pin rejection, reconnect resync and old-host upgrade refusal.
  • .\gradlew.bat realtimeClientCheck drives the live-channel client against a real Undertow host: healthy suppression of periodic peer HTTP reads, subscription snapshots, HTTP push, live publication/emote acknowledgements, reconnect, and no emote polling on a host without the route.
  • .\gradlew.bat emoteCheck checks clip bounds and interpolation, additive application to a real player model (including overlay parts and vanilla restoration), playback lifetime/age compensation, the portable format-5 round-trip and provider-free resolution.
  • .\gradlew.bat emoteTransportCheck checks the transient transport's nonce de-duplication, repeat triggers, explicit stops, unequipped ids and stale/negative ages.
  • .\gradlew.bat automaticAssetsCheck checks runtime model publication to an empty real server, deduplication and independent player model revisions without a provider on the viewer classpath.
  • .\gradlew.bat glbLoaderCheck checks the real tree GLB, scene transforms, accessor layouts, flower GLB, bounded image downsampling, embedded textures and malformed-input handling. providerAssetsCheck also checks textured mesh and skin delivery without the provider installed.
  • .\gradlew.bat :examplemod:exampleCosmeticsCheck checks example registrations, tail/skin toggles, merged startup equipment and clearing all nine example slots in one update.
  • .\gradlew.bat animatedSkinCheck checks looping body frames, account-skin restoration, cape/elytra compatibility, bounded resource/inline exports and provider-free HTTP delivery.
  • .\gradlew.bat cosmeticsCommandCheck checks individual/all client clears without a provider, automatic no-provider cleanup, saved custom slots, durable empty choices and publication. It also runs with check and build.

Status

Version 0.1 — the core library, the sync server and the protocol are complete and verified. See the goal checklist in the repository history for what is verified versus assumed.

Licence

See LICENSE.txt.