Conversation
Fixes the 26.2 client crash: with Rinku 2.2.1 a GUI open/close loop died on the fourth iteration with "Texture view Sampler0 (MCEF Browser Texture 1x1) has been closed!", and on 3.0.4 the same loop runs 25 times with the browser preload pool left enabled. The API move is mostly mechanical — com.cinemamod.mcef.MCEF becomes de.keksuccino.rinku.Rinku, MCEFBrowser becomes RinkuBrowser, the mixin follows CefUtil to de.keksuccino.rinku.util, and getTextureLocation is now getTextureIdentifier. Three things kept it small: RinkuInitListener is a functional interface so the scheduleForInit lambdas are untouched, RinkuBrowser still extends CefBrowserOsr so setFocus/getURL/ executeJavaScript/loadURL/setZoomLevel come along for free, and CefUtil.init still calls CefApp.startup(String[]) so the GPU-flag ModifyArg survives the move. mcef_version is renamed to rinku_version since MCEF is gone from the project entirely. CefUtilMixin no longer injects --use-gl=desktop. That flag was removed from Chromium years ago; it slipped through while Rinku shipped an older build, but against Chromium 151 it fails CefApp.startup outright, so CEF reached INITIALIZATION_FAILED and no browser was ever created. It was our own mine, not something the migration broke — any Chromium bump would have set it off. Rinku publishes its NeoForge builds inconsistently: 26.2 carries classes at the top level while 1.21.1, 1.21.11 and 26.1.2 are jarJar wrappers whose real mod sits in META-INF/jarjar/. NeoForge unpacks that at runtime, but javac sees an empty jar, so the build now compiles against whatever actually holds the classes and leaves the published artifact on the runtime classpath untouched. All seven targets build. Verified end to end on 1.21.11-fabric and 26.2-neoforge: CEF initializes, the page loads, the bridge is injected, page-to-game channels arrive, and the GUI reopens without crashing. Closes #28 Closes #31
Both Minecraft 26 dev servers died during mod loading:
ModLoadingException: Rinku (rinku) has failed to load correctly
java.lang.NoClassDefFoundError: net/minecraft/client/gui/screens/Screen
Rinku is a client-side library, and NeoForge's dev dist cleaner strips
the client classes it constructs against. A plain runtimeOnly dependency
feeds both dev runs, so runServer loaded it and died. Production was
never affected — WebGUI does not declare Rinku in neoforge.mods.toml, so
a real server never loads it — which is why this only ever bit
developers, and only on 26.x where Rinku's builds are not dist-safe.
The obvious fix does not work: moddev has dropped per-run classpaths on
modern Minecraft, and clientAdditionalRuntimeClasspath answers "no
additional classpath anymore for Minecraft 1.21.11". So Rinku now stays
off the runtime classpath entirely and reaches the client run as a jar
in its mods folder, installed by a task bound to runClient alone. That
also matches how the mod is actually used: a player installs Rinku, a
server operator does not.
Minecraft 26 gets the server smoke test in CI as well. The dev server
was broken on both 26 targets for months only because nothing ever
booted one there.
Verified: both 26 servers reach "Done" with WebGUI initialised, 1.21.1
and 1.21.11 servers still do, and the 26.2 client still brings up
Chromium and the page bridge with Rinku coming from the mods folder.
Closes #29
Covers the whole stack: the Rinku rename and dependency refresh, the Rinku 3.0.4 migration with its 26.2 crash fix, and the dev server fix. The upgrade is not transparent for everyone, so the entry says who has to act. Rinku's mod id changed from mcef to rinku, and WebGUI bundles it on Fabric via jar-in-jar but not on NeoForge — so Fabric players do nothing while NeoForge players must swap the jar in their mods folder. The README claimed NeoForge "needs no extra dependencies", which was never true: nothing bundles the browser library there. The installation steps now say so per loader.
|
Добавил в эту же ветку CHANGELOG и бамп версии до 1.7.0 — запись покрывает весь стек (#30, #32, #33). По ходу вскрылось расхождение, которое стоит отметить отдельно: Rinku бандлится в jar WebGUI только на Fabric ( Заодно поправил README: там значилось «NeoForge needs no extra dependencies», что не было правдой никогда — браузерную библиотеку туда всегда надо ставить руками. |
Installing WebGUI alone on NeoForge kills the game during mod loading:
@mixin target com.cinemamod.mcef.CefUtil was not found
NoClassDefFoundError: org/cef/handler/CefDisplayHandler
at land.webgui.WebGUIMod.<init>
Reproduced on the released 1.6.2 from Modrinth with Prism, so this is
not a regression — it is shipping today. WebGUI declared the browser
library nowhere: not in neoforge.mods.toml, not in the Modrinth version
metadata, and nothing is bundled into the NeoForge jar. So the launcher
installs nothing, FML does not know the mod is needed, and the player
gets a crash report instead of a missing-dependency screen.
Fabric was never affected: there the library rides inside the jar via
jar-in-jar, which is why it feels like it installs itself.
Declaring it required does both jobs — launchers resolve and install
Rinku, and a hand-copied install fails with FML's readable screen rather
than a stack trace. Bundling it with jarJar the way Fabric does is worth
considering too, but Rinku's NeoForge artifacts are themselves jarJar
wrappers on several versions, so that needs more care than a one-liner.
Refs #34
|
GitHub закрыл этот PR автоматически: его базовая ветка |
…tools (#38) * Keep Rinku out of the server run once the client has run The fix in #33 was incomplete. It kept Rinku off the runtime classpath and installed it into the client run's mods folder instead — but both dev runs share versions/<target>/run, so once anyone has started the dev client, the jar sitting in mods/ is loaded by the dev server too and it dies exactly as before: ModLoadingException: Rinku (rinku) has failed to load correctly java.lang.NoClassDefFoundError: net/minecraft/client/gui/screens/Screen That is why it looked fixed: the run directories were untouched when I verified it, so mods/ was empty and the server booted. It breaks again the moment a developer runs the client first, which is always. Separate game directories per run would be tidier, but they move worlds, configs and log paths, and every tool around them. Deleting the jar before the server starts says what is actually meant — the server must not see Rinku — and touches nothing else. Refs #29 * Add an opt-in custom death screen Set deathScreenUrl in server.json and the vanilla death screen is replaced by that page; leave it empty and nothing changes. Opt-in on purpose: replacing that screen takes away the player's only way to respawn, so it happens only when a server explicitly asks for it. The page is told what happened through a webgui:death event — killer (player, mob or environment), damage cause, the vanilla death message, and hardcore/canRespawn flags — and gets window.webgui.respawn() to stand in for the button it replaced. Where to hook in was the main design decision. The obvious target is the death packet handler, but that is onDeathMessage on Yarn and handlePlayerCombatKill on Mojang mappings and has moved between versions, which is the shape of target that has broken MouseMixin twice. Instead Fabric injects into MinecraftClient.setScreen, which has one signature across every Fabric target here, and NeoForge uses ScreenEvent.Opening and needs no mixin at all — so neither side carries a single version conditional, and 26.2 renaming setScreen to setScreenAndShow costs nothing. Three details that only showed up in a running game: The url is pushed on join, not at death. Deciding whether to suppress the vanilla screen has to happen the instant it opens, and learning the url at death time races the vanilla death packet. The death payload is held on the client and replayed after the document loads. It arrives while the page is still being created, so an immediate emit lands before any listener exists. Respawn does not clear the screen itself. The client still counts as dead until the server answers, so setScreen(null) makes vanilla reopen the death screen, which this very interceptor then replaces with a second browser nothing closes. Vanilla closes the screen on its own once the respawn lands. The screen is also left alone while Chromium is still starting. WebViewScreen closes itself when there is no browser, vanilla reopens the death screen because the player is still dead, and replacing it again recursed until the client crashed — reachable by dying in the first seconds of a session. Verified in a running game on all seven targets: real dedicated server, join, death, screen replacement, killer data in the page, respawn from the page, and the browser actually released afterwards. * Report the killer's entity type as a registry id entityType came from EntityType.toString(), which yields the translation key — "entity.minecraft.zombie". A page matching on the killer wants the id it would use anywhere else in the game, so send "minecraft:zombie". Yarn spells the lookup EntityType.getId and Mojang mappings spell it getKey; both are static and both return the identifier. Verified on 1.21.11-fabric and 26.2-neoforge with a real attributed mob kill, since the two mappings build this payload through separate code. * Leave the death payload on window.webgui.death The death event fires once, right after the document loads, so a page whose bundle only subscribes from a mounted component never hears it. Snapshot it on the namespace the way client and entity already are, and the late subscriber has something to read. * Tell clients what the server runs instead of refusing them A NeoForge server registered its channels as required, so a client without WebGUI - or with an older build of it - was disconnected with "Incompatible client! Please use NeoForge <version>", which names a mod that is not the problem. Fabric did the opposite and admitted the player, then silently dropped every packet, so nothing opened and nothing said why. The channels are optional now. The server introduces itself on join, and a client whose protocol differs gets a message naming both versions. Servers that really must require the mod set requireClientMod and get a kick message that explains itself. Two things found along the way: the death screen URL was pushed on join on Fabric but not on NeoForge, where it only arrived with the death packet itself, and Fabric pushed it twice. Also caps page events per player per second. That channel is the only thing a client can push at the server, every message is dispatched on the server thread, and a page is web content - a loop in someone's JavaScript can fire it as fast as the socket allows. * release: 1.7.1 * Let a server host its own pages Getting an interface in front of players meant finding a web host, a domain and a deploy pipeline before you could change a button. Now the files go in config/webgui/web/ and a URL setting points at them with webgui:/index.html. They travel down the connection the player is already on, so there is no port to open and it works behind NAT like everything else a server sends. Content is addressed by SHA-256: a file the client already has is never fetched again, and a changed file is a different name, so a page cannot be stale. External hosting is untouched - http(s) URLs behave exactly as before and the two mix freely. The browser reads them over loopback rather than a custom scheme. A scheme would avoid the socket, but non-standard schemes are second-class in Chromium and debugging that through a game window is miserable. The port is fixed by default because a bundled page's origin is what a backend has to name in CORS, and one that moved every launch could not be configured at all. Serving the pages ourselves also fixes something older: window.webgui was injected only after a document finished loading, so a plain inline script calling it threw "Cannot read properties of undefined" - only pages that waited for DOMContentLoaded ever worked. Injecting at load start is not enough on its own, because executeJavaScript is a message to another process and the page's own scripts routinely win that race. For pages served here the bridge is woven into the document itself, which cannot race. Limits, so a mistake here cannot take a server down: 8 MiB per file, 64 MiB in total, 2000 files, a file list that fits one packet, and a per-player bandwidth budget alongside the existing page-event one. * Let a page hand the player a file, and take one from them Both were dead controls. CEF asks the host where to put a download and, hearing nothing, throws it away, so an invoice or a CSV export was unclickable. A windowless browser cannot put up its own file chooser either, so <input type="file"> did nothing at all and gave no hint why. Downloads land in webgui-downloads/ under the game directory and the player is told the name, because a file that arrives with no trace is one they will never find. The page does not choose where its bytes go: the suggested name is the one part of a download web content fully controls, so the path is stripped out of it, refused characters are replaced, and names Windows reserves are sidestepped - writing to CON succeeds and goes nowhere, which would look like the download working and leaving nothing behind. The chooser is LWJGL's, not Swing's: Rinku boots AWT headless so the game can own the window, and JFileChooser throws outright in that state. Both naming rules live in WebviewDownloadNames, apart from the handlers that use them. They are the only thing between attacker-controlled text and a filesystem call, and free of browser classes they can be tested on every version - the handlers themselves cannot even be loaded on some of them. Popups are deliberately not handled. CEF documents a callback for it, but JCEF never routes a popup to it on a windowless browser: with Chromium's popup blocker off and a page calling window.open outright, onBeforePopup did not fire once. Pages written for this environment can navigate with location.href instead. * Show an icon and an author in the Mods list, and declare Rinku everywhere The Mods list is where a player looks to see what they installed and who wrote it, and WebGUI had no icon on either loader and no author on NeoForge at all. The icon is the project logo rasterised from the site's SVG, shipped at 128 and 256 because the two loaders ask for different sizes. More importantly, no CurseForge upload declared any dependency - including the NeoForge ones, which need Rinku installed separately. A NeoForge player who downloaded from CurseForge got no Rinku and the game died during mod loading with NoClassDefFoundError: exactly the failure the Modrinth declaration was added to prevent for 1.7.0, missed on the other platform. Fabric uploads now say rinku(embedded) rather than nothing. Rinku ships inside the Fabric jar, so asking a launcher to install it would fetch a second copy; embedded states the relationship without that. Drops the native file chooser. It took the client down with an access violation inside libcef's message loop - twice, and not from the thread it ran on: moving it to the render thread changed nothing, and the same crash then happened with no file dialog involved at all. Five clean single-client runs could not reproduce it, so the cause is still unknown, and a whole-client crash is not something to ship on the strength of "it did not happen to me". Downloads stay. They are verified repeatedly, by hand and unattended, and were never part of a crash. * Tell the page the field of view A page already knows where the player is and which way they are facing, and it can read its own viewport, but not the field of view - that lives in the player's settings and nowhere a page can reach. Without it there is no way to turn a point in the world into a point on the screen, so anything anchored to the world was out of reach. It is the setting, not the momentary value. The renderer stretches the field of view while sprinting or under a speed effect, and following that would mean reading the camera every frame; a marker placed from this is exact standing still and drifts a little on the move. Verified on all seven targets by writing a non-default fov into options.txt and requiring the matching degrees in the page - asserting a plausible number would have passed on a hardcoded default. * Fix client data never updating after the page opened on NeoForge 1.21.11 A page there received exactly one payload — the one pushed when its document loaded — and then nothing, ever. Position, health, game mode: frozen at whatever they were when the page opened, with nothing in the log to say so. From the outside it looked like a page that had stopped re-rendering, which is the wrong place to go looking. The per-tick push asks whether the browser's texture is ready, and on NeoForge that test was getRenderer().getTextureID() != 0. Rinku 3 keeps the texture as a Blaze3D GpuTexture, which need not have a raw GL id at all; on this target the id stayed 0, so the test never passed. The document-load push skips the check entirely, which is why a page got one payload and no more. Both loaders now use isTextureReady(), Rinku's own accessor, which the Fabric side was already using. Caught by a check that changes the world and expects the payload to follow, where every earlier test was satisfied by a single payload and passed. * Tell the page what the crosshair is on Alongside where the player is and which way they face, a page can now ask what they are actually looking at: a block with its registry id, whole coordinates and the face being approached, or an entity with its type, name and uuid, each with the distance to the point of contact. This is the game's own answer — the one it uses to draw the block outline and the name above a mob — not a second ray cast with its own idea of reach, which would disagree with what the player sees. type is always present, "none" included. A field that disappears makes every page write the same optional-chaining dance before it can ask the only question that matters. Verified on all seven targets against a world the test builds itself: the player is teleported to a known spot facing a known direction, a gold block goes exactly three away, and the payload has to name those coordinates, that block and the south face. Then the block is replaced by a zombie, and finally the player looks at the sky. * Apply the whole config on /webgui reload The bundled page list was only ever sent when a player joined, so after a reload the server served new bytes while the client still checked them against the old hash. trustedCommandOrigins and mainMenuUrl were never re-sent at all: dropping an origin from the trusted list did nothing for anyone already online until they reconnected. Join and reload now go through one sendConfigSnapshot, so they cannot drift apart again. The client keeps its session token for the life of the session instead of minting a new one per manifest — rotating it moved the origin out from under every open page, which turned a reload into a 404. Leaving a world still clears it, so a page from the previous server cannot read the next one's files. An open bundled page is reloaded when the revision actually changed; pages from a web host are left alone. Also rescan the web folder when server.json is missing or empty, since pages are served by default and the folder can still have changed. * Say in the log why a page asset could not be served A bundled page whose build emitted absolute paths came up blank with nothing anywhere to explain it: the 404 went to the browser, and the only trace on our side was a debug line nobody has enabled. Both misses now warn once per distinct path, name the document that asked, and say that a relative base is the fix. resolve() also stopped blaming the wrong thing. It reported "this server ships no pages" whether the server shipped none or no loopback port was free, which sent an operator to check a config file that was fine. * Add a settings screen with page developer tools The mod had no settings of its own and nowhere to put any: NeoForge's mod list shows a Config button when a mod registers a screen factory, and on Fabric Mod Menu does the same, so both now open one. The first setting on it turns Chromium's own view of the page into log lines - console calls with the file and line that made them, uncaught exceptions, failed requests with their url and status, and the browser warnings nobody could see before. A page in the game has no inspector, and a blank one left a developer with nothing at all to go on. It reads the DevTools protocol, which needs neither a window nor a debugging port: openDevTools() wants a desktop window a game does not have, and the real inspector needs --remote-debugging-port, which is fixed on the command line before any mod runs. That argument does work, so the screen reports whether someone passed it and prints the address to open. Capped at 20 messages a second, and off by default. Also stop advising a rebuild when Chromium asks the origin root for favicon.ico, which it does on every page and which is nobody's mistake. * release: 1.8.0 Everything since 1.7.0 ships as one version. 1.7.1 was prepared but never published, so its entries belong here rather than to a version nobody could install. Also documents what the browser does not do - window.open, and what window.webgui.isHud is for - and lists the limitations worth knowing before installing rather than after. --------- Co-authored-by: Кирилл Гринев <k.grinev@pravo.tech>
Closes #28. Closes #29. Closes #31. Closes #34.
Что чинится
INITIALIZATION_FAILEDrunServerна 26.x и 1.21.11-neoforgeЧетыре находки
1. Краш GUI на 26.2 (#31).
Texture view Sampler0 (MCEF Browser Texture 1x1) has been closed!— детерминированно на 4-м открытии, независимо от задержки перед закрытием. Rinku 3.0.4 его снимает; обходной путь сbrowser-preload-enabled=falseбольше не нужен.2. Мина в нашем коде.
CefUtilMixinподмешивал вCefApp.startupфлаг--use-gl=desktop, удалённый из Chromium несколько лет назад. Пока Rinku тянул старый Chromium, он проходил незамеченным; на Chromium 151 валит инициализацию CEF целиком. Рвануло бы при любом обновлении Chromium, независимо от этой миграции.3. Игра падала на старте на NeoForge (#34). Воспроизведено на релизной 1.6.2 с Modrinth в Prism: библиотека не была объявлена нигде — ни в
neoforge.mods.toml, ни в метаданных Modrinth, ни вложена в jar. Лаунчер не ставил ничего, FML не знал, что мод нужен, пользователь получалNoClassDefFoundError: org/cef/handler/CefDisplayHandlerвместо экрана «не хватает мода». Fabric не затронут — там библиотека внутри jar.4. dev
runServer(#29). Rinku попадала в runtime обоих dev-ранов. Аккуратного решения не нашлось: moddev убрал per-run classpath на современных Minecraft. Теперь она не на classpath вовсе, а приезжает в клиентский ран jar'ом вrun/mods— что заодно точнее моделирует продакшн.Миграция API
com.cinemamod.mcef.MCEF→de.keksuccino.rinku.Rinku,MCEFBrowser→RinkuBrowser, цель миксина →de.keksuccino.rinku.util.CefUtil,getTextureLocation()→getTextureIdentifier(), свойствоmcef_version→rinku_version.Обошлось 12 файлами механической замены:
RinkuInitListener— функциональный интерфейс,RinkuBrowser extends CefBrowserOsr,CefUtil.init()всё ещё зовётCefApp.startup(String[]).Обход апстримной непоследовательности
NeoForge-сборки Rinku 3.0.4 упакованы по-разному: у 26.2 классы сверху, у 1.21.1, 1.21.11 и 26.1.2 это jarJar-обёртки. В рантайме NeoForge распаковывает сам, javac видит пустой jar — сборка компилируется против того, где реально лежат классы.
CI
smoke: trueдобавлен обоим таргетам MC 26. Баг с dev-сервером прожил месяцы ровно потому, что там никто не поднимал сервер.Проверено
Все 7 таргетов собираются. Стендом mc-webgui/testkit, по 10/10 на 1.21.11-fabric и 26.2-neoforge: CEF инициализируется, страница грузится, мост инжектится, каналы page→game доходят, GUI переоткрывается без краша, скриншот снимается. Серверы: 26.2, 26.1.2, 1.21.11-neoforge, 1.21.1-neoforge — все стартуют.
Не проверено: подтянет ли лаунчер Rinku по объявленной зависимости на реальной установке. Локально подтверждено только то, что зависимость попала в jar.
Версия
mod_version= 1.7.0, CHANGELOG заполнен. Мерж вmainзапустит публикацию.