3.0.0.22
Curse Maven Snippet
What's new
📦 UPDATE 3.0.0.22 (BETA)
⚡ Core — Boot pipeline, modules & progress metrics
- ⚙️ Renamed:
WaterMediaAPI→WaterMediaModule(moved toorg.watermedia) — non-API boot processes extend it too now; the lifecycle (load/start/release) isprotected, so only theWaterMediaorchestrator (same package) can drive it - ⚙️ Changed: Binaries and Config are homologated as boot modules —
WaterMediaBinariesandWaterMediaConfigextendWaterMediaModuleand boot through the same registry, orderedBinaries → Config → Codecs → Platform → Media → Network; the hand-rolled pre-registry startup blocks inWaterMedia.startare gone - ⚙️ Changed: the global
load()pre-pass is interleaved per module (load→start), so config-dependent step counts (e.g.NetworkAPI's file-server step) read registered config values instead of compile-time defaults - ⚙️ Changed: the FFmpeg bootstrap is centralized in
MediaAPI(code-sectioning into the player served no purpose) —FFMediaPlayer.load()/loaded()/loadError()/vulkanDecodeAvailable()becameMediaAPI.ffmpegLoaded()/ffmpegError()/vulkanDecode(); binaries extraction stays separate in the binaries module - ✨ Added: three-level boot metrics on
WaterMedia, names only —step()/steps()/stepName()(which module is loading),taskStep()/taskSteps()/taskName()(which element the module is loading),work()/workTotal()/workName()(units of the demanding task in flight: download/extraction bytes) — enough to emulate three loading bars;currentAPI(),totalWorkSteps()andcompletedWorkSteps()are removed, module instances are never exposed - ✨ Added:
WaterMedia.failures()— safe (non-fatal) boot failures asFailure(api, step)records (a failed FFmpeg load, binaries extraction, media cache init or file-server bind), surfaced as[NO]lines in the app's boot splash - ✨ Added: FFmpeg extraction publishes real byte progress (compressed bytes read vs. the shipped zip size) through the binaries module's work bar; the boot splash shows the percentage on the active line
⚡ API — Sealing & extension hardening (security)
- ⚙️ Changed:
GFXEngineis nowsealed(permitsGLEngine,VKEngine,HeadlessGFXEngine) andSFXEngineis nowsealed(permitsALEngine,JSEngine) — the publicMediaPlayer/TxMediaPlayer/FFMediaPlayerconstructors take an engine instance, so sealing closes the injection gate: a caller can only pass WaterMedia's own engines, never a hand-rolled subclass, and can no longer reimplement an engine to work around a rendering bug that isn't WaterMedia's - ⚙️ Added:
HeadlessGFXEngine— a permitted no-GPUGFXEnginethat records uploads into memory instead of a context (server-side probing, CI, headless validation); exposesuploadCount()/lastUpload()/format()/activeFrame()for pipeline introspection and replaces the former test-only stand-in - ⚙️ Changed: every concrete API class a developer receives an instance of is now
finalso it can't be extended — the sixImageReaders (GIF/JPEG/PNG/WEBP/NETPBM/SVGReader), the fourNetpbmDecoders,ImageMetadata,NetRequest,NetworkServer, the fourWaterMediaAPIfacades (Codecs/Media/Network/PlatformAPI), and the built-inIPlatformimplementations (WaterPlatform+ every web platform) - ⚙️ Changed:
XCodecExceptionis nowsealed(permitsUnsupportedFormatException) andYtDlpPlatformis nowsealed(permitsYouTubePlatform) - 🔸 Note:
IPlatform(registered by apps throughPlatformAPI.register),VKContext(the modder Vulkan-device bridge), and the platform exception hierarchy (PlatformException/MatureContentException) stay open on purpose — they are documented extension points, not instances handed back to callers - 🔸 Note: the unnamed-module boundary means
sealedonly compiles for same-package subclasses, so the cross-package abstract bases (WaterMediaAPI,ImageReader,ImageWriter,NetpbmDecoder) are hardened by finalizing their leaves instead — a developer subclass of the base is inert because WaterMedia never accepts one
⚡ CodecsAPI — JPEG decoder (security hardening)
- 🐛 Fixed: uncapped 16-bit SOF dimensions overflowed the coefficient/sample/BGRA int math — a few-byte header could wrap
new int[...]to0/negative (ArrayIndexOutOfBoundsException/NegativeArraySizeException) or force multi-GB allocations (OutOfMemoryError); both axes are now hard-capped at 16384 (16K) and rejected with a clean codec error - 🐛 Fixed: the scan header's 4-bit DC/AC Huffman table selectors (0..15) were used unchecked to index the
[2][4]table array, so a selector of 4..15 threw an uncontrolledArrayIndexOutOfBoundsException; selectors are now validated to 0..3 - 🐛 Fixed: a malformed DHT declaring an oversubscribed (non-canonical) Huffman code overflowed the fast-lookup table fill and value indexing with an uncontrolled
ArrayIndexOutOfBoundsException; the table builder now rejects oversubscribed codes with a clean codec error - 🐛 Fixed: a corrupt DC Huffman table could yield an out-of-range magnitude category (SSSS > 15) feeding a bogus bit count into the coefficient extend; the category is now validated to 0..15
⚡ CodecsAPI — GIF decoder (security hardening)
- 🐛 Fixed: uncapped 16-bit logical-screen dimensions overflowed
width*height*4int math (NegativeArraySizeException) or forced multi-GB allocations (OutOfMemoryError) for the canvas and LZW index buffers; the canvas is now hard-capped at 16384 (16K) per axis and rejected with a clean codec error - 🐛 Fixed: an animation frame whose image descriptor extends beyond the logical screen overflowed the
width*heightLZW allocation; frames are now bounds-checked against the canvas and rejected with a clean codec error - ⚙️ Added: config
decoders.gif.clampImageDesc(default off) — when enabled, an out-of-bounds frame is clamped to the canvas (best-effort, logged as WARN, may show artifacts) instead of failing the decode - 🐛 Fixed:
ScreenDescriptor,ImageDescriptorandColorTablethrew uncheckedIllegalArgumentExceptionon malformed data, escaping theIOExceptioncontract and crashing the caller; validation now surfaces asXCodecExceptionat the parse boundary - 🐛 Fixed: the sub-block buffer growth (
next <<= 1) could overflow to a negative size (NegativeArraySizeException) on pathologically large inputs; growth is now overflow-guarded and bounded
⚡ CodecsAPI — PNG/APNG decoder (security hardening)
- 🐛 Fixed: APNG
fcTLframe geometry was never validated against the canvas — a frame declaring a size larger than the canvas, or negative x/y offsets, drove out-of-bounds writes (uncontrolledArrayIndexOutOfBoundsException) in the decode, blend and dispose paths; every frame is now bounds-checked (the existingFCTL.validateis now actually called, and hardened tolongmath so offset+size can't wrap past the check) and rejected with a clean codec error - 🐛 Fixed: uncapped
IHDRdimensions — the raw 32-bit width/height overflowedwidth*height*4int math (NegativeArraySizeException) or forced multi-GB allocations (OutOfMemoryError); both axes are now hard-capped at 16384 (16K) and rejected with a clean codec error - 🐛 Fixed: IDAT/fdAT decompression bomb — inflation grew the output buffer unbounded, so a tiny image could inflate to gigabytes (
OutOfMemoryError); output is now bounded to the exact size a well-formed frame decompresses to (computed aslongto avoid overflow on 16K depth-16 frames) and aborts with a clean codec error past it - 🐛 Fixed: zTXt/iTXt decompression bomb — compressed text metadata inflated with no output limit; both are now capped at 2 MB decompressed and throw past it
- 🐛 Fixed: indexed
bKGDwith a palette index beyond thePLTE(or with noPLTEat all) hit the uncheckedPLTE.getColorwith an uncontrolledArrayIndexOutOfBoundsException— validated at construction (only reachable withdecoders.png.useBKGDChunkenabled)
⚡ CodecsAPI — WEBP decoder (security hardening)
- 🐛 Fixed: uncapped 24-bit VP8X canvas and ANMF frame dimensions — a crafted file of a few bytes could declare up to 16777216×16777216, overflowing
width*height*4int math and forcing multi-GB allocations (OutOfMemoryError) or uncontrolledArrayIndexOutOfBoundsException; both are now hard-capped at 16384 (16K) per axis and rejected with a clean codec error - 🐛 Fixed: lossless images using bundled palettes (16 colors or fewer) crashed with an uncontrolled
IndexOutOfBoundsException(the image decodes at a reduced width but the palette inverse copied back at full width) — they now fail with a clean codec error; proper bundling decode is pending - 🐛 Fixed: VP8 lossy frames declaring broken partition sizes (partition 0 or token partitions overrunning the chunk, truncated size table, empty partitions) escaped the
IOExceptioncontract with uncontrolledIllegalArgumentException/BufferUnderflowException— sizes are now validated up front and fail with a clean codec error - 🐛 Fixed: VP8L bodies shorter than the 5-byte header inside extended (
VP8X) or animation (ANMF) containers crashed with an uncontrolledIllegalArgumentExceptionwhen skipping the header (only the simpleVP8Lpath validated it) — now rejected with a clean codec error - ⚙️ Added: config
decoders.vp8.brokenTokens(default off) — instead of failing, attempts to repair frames with broken token partition sizes: declared sizes are clamped to the bytes actually available and unusable partitions are replaced with a duplicate of the previous one or with fake zeroed data; decode continues best-effort and may show artifacts, every repair is logged as WARN - 🐛 Fixed: the container-declared dimensions (VP8X canvas or ANMF frame) were never checked against the dimensions the embedded VP8/VP8L bitstream actually carries — output buffers are sized from the container, so a crafted mismatch overran the YUV plane copy (
IndexOutOfBoundsException), the BGRA buffer limit (IllegalArgumentException) or the animation compositor (ArrayIndexOutOfBoundsException), and a smaller canvas silently delivered truncated pixels; both sides must now agree (as the spec requires) and mismatches fail with a clean codec error - 🐛 Fixed: ANMF sub-chunk sizes were read as signed 32-bit and used unvalidated — a negative size made the sub-chunk cursor stop advancing (infinite loop / thread hang) or walk into negative indices (
ArrayIndexOutOfBoundsException), and a huge size wrapped the bounds check; sizes are now validated with overflow-free math before use - 🐛 Fixed: animation frames extending past the canvas were silently clipped while still decoding (and allocating) the full off-canvas frame; the spec requires frames to lie fully inside the canvas, so they are now rejected with a clean codec error — which also bounds every per-frame allocation by the validated canvas size
- 🐛 Fixed: the animation pre-scan crashed the constructor with an uncontrolled
IllegalArgumentExceptionon an odd-sized VP8X chunk truncated exactly at its end (the RIFF padding byte seek landed one past the buffer limit); the scan now clamps and degrades to a static-image summary - 🐛 Fixed: the 16K-per-axis cap alone still allowed 16384×16384 declarations — ~1 GiB per pixel buffer and ~2 GiB on the animated path (canvas + output) from a few-byte header (
OutOfMemoryError); a total-pixel cap (64 Mpx, e.g. 8192×8192) now rejects them at construction, and zero-dimension VP8 frames are rejected instead of producing empty buffers - 🐛 Fixed: VP8L transform declarations were unbounded — repeating transform types let a tiny file queue thousands of full-image inverse passes (CPU exhaustion); each transform type may appear at most once per the spec and duplicates now fail with a clean codec error
- ⚙️ Changed: VP8L huffman group counts are now enforced through a feasibility gate — each group costs at least 20 bits, so a count the remaining bitstream cannot physically encode (e.g. a 1×1 entropy image declaring 65536 groups ≈ 200 MB of tables from a few-KB file) fails before allocating instead of after; counts past the recommended 256 still log a WARN
⚡ MediaAPI — Engines (rewrite: construction, naming & BCn pixel formats)
- ⚙️ Changed: engine creation is centralized in
MediaAPIfactories —glEngine(thread, ex),vkEngine(ctx),jfxEngine(onFrame),awtEngine(onFrame),headlessEngine(preload),alEngine([buffers]),jsEngine([bufferMs]); everyBuilderclass andbuildDefault()is removed (they were one-argument wrappers around one-argument constructors) - ⚙️ Changed: the client-side check moved into the sealed
GFXEngine/SFXEnginebase constructors — one enforcement point covering every engine (previously onlyGLEngine/VKEnginechecked, each on its own), impossible to bypass even with a directnew;HeadlessGFXEngineis the sanctioned server-side exception via a package-private unchecked path - ⚙️ Renamed (
GFXEngine):setVideoFormat→format(...),supportsFormat→supports,supportsFrameTextures()→preload(),uploadFrameTextures→preload(frames, stride),useFrameTexture→frame(index),requiredBufferAlignment()→alignment(),releaseBuffer(buf)→release(buf),bitsPerComponent()→bits()— record-like naming, overload-paired with the getters - ⚙️ Renamed (
SFXEngine):setAudioFormat→format(type, channels, rate), mirroring the GFX contract - ✨ Added:
PixelFormatnow carries intrinsic layout data —planes(),blockBytes(),compressed()— and gains the block-compressed constantsBC1/BC2/BC3/BC5/BC7ahead of the ISPC encoders, so BCn is a first-class pixel format instead of an external codec-only concept - ⚙️ Removed:
GFXEngine#supportsCompressedTextures(String)/uploadCompressedFrames(ByteBuffer[], String, int)— compressed textures ride the standard path now:supports(PixelFormat.BC*)+format(BC*, w, h)+preload(blocks, 0), withblockBytesintrinsic to the enum; theTxMediaPlayercodec-cache replay maps the DDS codec id straight to itsPixelFormatconstant - ⚙️ Changed:
GLEngine's five conversion shaders share one uniform scheme (plane0..plane3/bitScale/uvSwap/outputWidth) — undeclared locations resolve to -1 and are silently ignored per the GL spec, so a single compile/bind path replaces the three per-format switches (~25 uniform fields and ~150 duplicated lines dropped) - ⚙️ Changed: single-use helpers aggressively inlined across both GPU engines (
GLEngine: quad/PBO init, shader release, hub acquire/release, async frame-texture scheduling;VKEngine:init,buildFormat,updateDescriptorSet,ensureComputePipeline, the SPIR-V cache wrappers) — locals stay in one frame for the JIT and the read flow is linear; big cold well-named steps (ensureYcbcr, ring lifecycle) stay extracted
⚡ MediaAPI — Engines (VKEngine)
- ⚙️ Renamed:
VKContext#instance()→vkInstance()andVKContext#device()→vkDevice()— avoids overloading Minecraft 26.x'sVulkanDevice#instance()by return type alone (the JVM resolves by full descriptor, so the mixin-merged class is legal bytecode, but same-name methods muddy stack traces and crash reports); a mod can now implementVKContextdirectly on it through a mixin (MC's ownvkDevice()accessor satisfies the interface verbatim) and pass the cast device straight toMediaAPI.vkEngine; the README documents the full pattern
⚡ MediaAPI — Engines (GLEngine)
- ⚙️ Removed: the 9 GL proxy callbacks (
setGenTexture/setBindTexture/setTexParameter/setPixelStore/setDelTexture/setActiveTexture/setBindVertexArray/setBindFrameBuffer/setBindBuffer) and theBindConsumer/TexParamConsumerinterfaces — the engine is now self-contained;MediaAPI.glEngine(renderThread, renderThreadEx)is the whole contract - ⚙️ Added: exact GL state capture/restore around every upload wave — texture bindings, active unit, sampler bindings, pixel-store, PBO/ARRAY_BUFFER bindings, VAO, READ/DRAW framebuffers, program, viewport and draw toggles are read once and restored to their exact prior values, so any host-side skip-if-equal cache (Minecraft
GlStateManager, Sodium/Iris trackers) stays truthful without integration - ⚙️ Added: per-render-thread drain hub — all engines sharing a render thread drain through one batched task per frame inside a single capture/restore envelope, keeping the
glGet*cost constant per frame (not per player) at any scale; a broken engine no longer aborts the rest of the wave - ⚙️ Added: provably-safe deletion of exposed texture names — storage is freed immediately on release (zero-size respec) and the name is deleted the moment no texture unit still binds it (which, given exact state restore, proves no host binding cache references it), so a cached binding can never point at a driver-reused name; pending names are tracked per
GLCapabilitiesidentity so they never leak across recreated contexts - 🐛 Fixed: the YUV→RGBA convert pass ran with whatever blend/scissor/colorMask/cull/polygon-mode/sampler state the host left behind — frames could come out clipped, blended or mis-sampled; the pass now forces a clean draw state and restores the host's exactly
- 🐛 Fixed: a queued upload draining after
release()resurrected GL resources (leaked textures on a dead engine) — engines are now terminally flagged and stale render tasks become no-ops - ⚙️ Changed:
GLEngineconstruction now rejects a non-nullrenderThreadwithout an executor instead of failing later on submit
⚡ MediaAPI — Engines (SFXEngine)
- ⚙️ Added:
JSEngine— a native, dependency-freeSFXEnginebacked by the Java Sound API (javax.sound.sampled), playing decoded PCM straight through the OS mixer (WASAPI/DirectSound, ALSA/PulseAudio, CoreAudio) with no OpenAL context and no external library; a first-class alternative toALEngine, usable even when OpenAL is available - ⚙️ Added: stream-based
SFXEnginebackend —format(...)opens a freshSourceDataLine(reopens on format change); uploads are non-blocking viaavailable()backpressure, mirroringALEngine's buffer-pool contract so the clock keeps tracking the audible position throughpendingMs()(framesWritten − getLongFramePosition(), rebased onflush()); volume viaMASTER_GAIN; conservative U8/S16 mono/stereo capability tables so format negotiation always lands on an openable line - ⚙️ Added: standalone app — an "Audio engine" selector (Settings → App → General) switches between
OPENALandJAVASOUND; the choice (AppContext.audioEngine) feeds theSupplier<SFXEngine>at player creation, so it applies to the next media opened - ⚙️ Added:
SFXEngine#speed()— reports whether the backend can change the playback speed (ALEngine→true,JSEngine→false), so players and UIs can honor engines stuck at 1.0× instead of desyncing against them - ⚙️ Added:
MediaPlayer#canSpeed()—trueonly when the source and the audio engine both allow a speed change (live streams and speed-less engines reportfalse);speed(float)now refuses instead of scaling the playback clock against audio stuck at 1.0×, which caused unfixable stuttering onFFMediaPlayerunderJSEngine - ⚙️ Added: standalone app — the player screen's speed dropdown locks (not clickable, drawn dimmed) while the current media reports
canSpeed() == false - 🔸 Note: the Java Sound backend has no portable pitch/rate control, so
speed(float)is a documented no-op (playback stays at 1.0×) and there is no per-source spatialization; useALEnginewhen those are required
⚡ MediaAPI — Engines (Software: JavaFX / AWT)
- ✨ Added:
JFXEngine— aGFXEnginethat renders decoded frames into a JavaFXImage; the engine's direct BGRA buffer backs aPixelBufferwith no copy, so bindingimage()to anImageViewshows video after a singleupdateBufferon the FX thread — a pure JavaFX app can embed WaterMedia with no OpenGL/Vulkan context - ✨ Added:
AWTEngine— aGFXEnginethat renders into an AWTBufferedImage(TYPE_INT_ARGB) for Swing/AWT apps; uses onlyjava.desktop, so it needs no extra dependency - ⚙️ Added: shared
SWEnginebase (sealed, permitsJFXEngine/AWTEngine) — accepts only single-plane BGRA/RGBA and declines every YUV/planar format, so the decoder's scaler pre-converts frames to BGRA; each frame is copied into a reusable direct buffer and published, firing an optionalonFramehook for repaint.texture()returns a non-zero sentinel (there is no GPU texture) - ⚙️ Added: JavaFX is a compile-only dependency (openjfx 21 LTS, host-classified — matches the Java 21 runtime; a newer JavaFX would fail with
UnsupportedClassVersionError) — WaterMedia compilesJFXEngineagainst it but never bundles it; a consuming JavaFX app supplies its own runtime, andJFXEngineloads FX classes only when instantiated
⚡ PlatformsAPI — Web platforms (Imgur)
- 🐛 Fixed: most Imgur links failed to resolve even though the API answered HTTP 200 —
/gallery/{id}posts holding a single image (the most common kind on Imgur) carry the image fields directly ondatainstead of animages[]array and were rejected as "empty or unsuccessful"; both response shapes are now detected and resolved - 🐛 Fixed: direct image links (
imgur.com/{id}) crashed during JSON deserialization — the image endpoint answers"ad_type": nulland Gson refusesnullfor a primitive record component; the data records now bind only the fields actually consumed, making them immune to Imgur's endpoint-dependentnulls - 🐛 Fixed: hidden albums (
/a/{id}links never posted to the gallery) returned 404 —/a/links now resolve through the album endpoint instead of the gallery one - ⚙️ Added: tag links (
imgur.com/t/{tag}/{id}) resolve through the gallery flow; the legacy#/t/fragment detection (broken —URI#getFragment()never contains#) was removed - ⚙️ Added:
www.imgur.com/m.imgur.comhosts are claimed, title slugs and file extensions are stripped from ids, and URLs without a media id are ignored with a warning instead of hitting the API - ⚙️ Added: NSFW posts and images now honor
platforms.allowMatureContentand throwMatureContentExceptionwhen disabled — same gate as Kick/Twitch
⚡ PlatformsAPI — Web platforms (Medal)
- ✨ Added:
MedalPlatform— resolves Medal.tv game clips through the publicmedal.tv/api/content/<id>endpoint (no auth); claims every URL shape (/games/<game>/clips/<id>, any locale prefix like/es/, the short/clip|/clips/<id>links, and?contentId=<id>), exposes the HLS ladder (source/540p/360p/240p) as quality variants like Kick, with the progressive source MP4 and the social-share MP4 as ordered fallbacks, full metadata (title, description, author, duration, thumbnail, posted date) and URL-expiry honored; DMCA-takedown and login-walled clips throw a clearPlatformException - 🔸 Note: Medal's
contentUrl<height>pMP4 rungs are ignored — they serve the source file for every rung when no real ladder was transcoded, so the HLS master is used as the single source of truth for qualities
⚡ PlatformsAPI / Tools — Shared JSON tooling
- ⚙️ Changed: Imgur, Kick (channel/VOD/clip/search), Medal, Sendvid, Streamable and Twitter dropped their identical private fetch helpers and resolve through
fetchJson;NetRequest,PornHubPlatform,TwitchPlatformand the standalone app (AppContext) dropped their privatenew Gson()copies and parse/write throughJsonTool
⚡ App — AWT popup player (showcase)
- ✨ Added: opening an MRL can pop out a native AWT/Swing player window (
AWTPlayerWindow) instead of the in-app GPU screen — aMediaPlayerdriven by anAWTEngine, with an aspect-fit video surface and a minimal transport bar (play/pause, seek, volume, time). Showcases embedding WaterMedia with no GPU backend and exercisesAWTEngineend-to-end inside the product - ⚙️ Added:
AppContext.playerTarget(IN_APP/AWT) with a segmented Player output control in the settingsenginessection (persisted inwatermedia-app.toml); thePLAYERnavigation branches to the popup when set toAWT. The window disposes on close without touching the host app, and its blocking player teardown runs off the EDT
⚡ GENERAL
- 🛠️ Minecraft/Sodium GL state can no longer be desynced by video playback — no
GlStateManagerwiring needed anymore, and the overhead stays flat no matter how many screens play at once - 🛠️ Hardened the WebP, PNG/APNG, GIF and JPEG decoders against malicious files — crafted images can no longer force gigabyte memory allocations, decompression bombs or crash threads with unexpected exceptions; every malformed file now surfaces as a clean codec error
- 🐛 Fixed: Imgur links work again — gallery posts, albums (including hidden ones), single images and tag links all resolve now
All Relations
- All Relations
- Embedded Library
- Optional Dependency
- Required Dependency
- Tool
- Incompatible
- Include
This mod has no related projects

