What's new in this build
Build 167 of nx2 (e5e82c1) is build 158 plus 38 commits — five pull requests — and for once the headline is easy to see: Fire Emblem: Fortune's Weave's DLSS stand-in is now real AMD FSR 3.1, GPU-decoded movies are no longer green, Donkey Kong Bananza gets through its cutscenes, and LAN Play through a room finally works. Around that: the video decoder got a batch of end-of-stream fixes, the network stack had a set of socket-lifetime bugs fixed, Convert to NUP keeps your dump's folder layout, the save-folder button stops guessing, and the CI no longer cancels its own release builds.
Builds 159–166 have no release; the numbers are CI run ids, and that stretch of pushes is exactly what the release-queue change below fixes.
Nothing new needs configuring. If a machine can't run FSR 3.1, the improved stand-in runs instead, and one environment variable forces that on purpose.
Fire Emblem: Fortune's Weave: real FSR 3.1 instead of the stand-in (PR #346, @Duck912)
Fortune's Weave ships a DLSS network, and DLSS itself can't run here, so until now the game was served by a temporal stand-in — an approximation of DLSS that averaged jittered frames. Build 167 replaces it with AMD FidelityFX Super Resolution 3.1, which wants the same inputs DLSS does (jittered colour, motion vectors, jitter, depth) and produces a proper reconstructed 1440p image from the game's 720p render.
What that means in practice:
- The DLSS path now runs FSR 3.1's eight passes — prepare inputs, luma and shading-change pyramids, shading change, reactivity, luma instability, accumulate, RCAS — on Vulkan, with the shaders vendored from AMD's FidelityFX SDK (unmodified, MIT).
- RCAS Sharpness in the graphics settings controls FSR's sharpening, and its tooltip now says so. 0% turns the sharpening off.
- FSR computes its own exposure from the HDR colour, so the picture no longer drifts darker over time (that was a stand-in bug, see below).
- The game's output keeps its own format; FSR renders into its own RGBA16F image.
When FSR can't run, the stand-in still does — no depth, mismatched scales, a device missing FSR's formats or features, or pipelines that fail to build. EDEN_NX2_DLSS_REPLACEMENT=standin forces the stand-in for A/B comparisons.
Only the Vulkan backend has this path at all — on OpenGL there is no temporal upscale stage, so nothing changes there.
The stand-in got much sharper for everyone else (same PR)
If you do fall back — an older GPU, OpenGL, or an A/B run — the stand-in is no longer the blurry thing it was. Its 1440p output was measurably softer than a plain bilinear 2× of the same 720p input (Laplacian detail 0.0066 against 0.0081 for bilinear), and it drifted darker over time, for three reasons: the sample kernel was about twice too wide for the output resolution, the frame history was stored in the output's B10G11R11 format whose 5–6 bit mantissas rounded away each frame's 0.03–0.1 share, and the neighbourhood clip was throwing away detail that only some jitter positions sample (railings, thin edges).
History is now an RGBA16F pair with no per-frame copy, still pixels use a third of the kernel width and a looser 1.5-sigma clip so detail accumulates across jitter positions, and moving pixels keep the old kernel and clip — including pixels whose history falls far outside the neighbourhood, remembered in the history's alpha. Static output goes 0.0066 → 0.0102 with no brightness drift and no new ghosting. Resolution scaling also works now: previously the stand-in fell back to a bilinear blit at non-native scales.
Two things that were renamed in the UI (because the names were wrong)
No behaviour change, and your saved configuration carries over (setting ids and enum values are untouched) — but two entries were labelled as AMD's FSR 3 when they are not it:
- Window filter "AMD FidelityFX Super Resolution 3" was FSR 1's EASU and RCAS with RCAS at 0.8× the slider and no temporal blending → now "AMD FidelityFX Super Resolution 1, softer sharpening" (FSR 1 Soft).
- Frame generation "FSR 3" was Eglantine's own optical-flow interpolation, not AMD's → now "Optical Flow Interpolation (built-in)".
The plain FSR filter is labelled "AMD FidelityFX Super Resolution 1 (FSR 1)".
Movies: green video on GPU-decoded titles is fixed (PR #356)
Movies decoded on the host GPU came out green — Mario Kart World's pre-race movie, Hyrule Warriors' cutscenes — while CPU-decoded ones were fine. FFmpeg 6.1's swscale leaves the chroma plane of an NV12→NV12 copy at zero, and that copy was in the path for GPU-decoded frames. NV12 frames are now copied without swscale.
Donkey Kong Bananza: the cutscene no longer aborts the game
Bananza's movie player acquires one decoded frame each time a released output image's fence completes, and aborts with 2111-3604 when that ring is empty. The old Mario Kart World workaround completed those fences immediately at the end of a stream, which is exactly an empty ring the moment a cutscene movie ends. Released images now keep their normal pending fence after the end of a stream, while Stop (207) and Finalize (115) still complete every fence handed out, so a client waiting on its images is still released when it stops the decoder. Tested on Bananza: the post-breakout cutscene ends and gameplay continues in the mining tunnel. EDEN_NX2_VDEC_EOS_RELEASE_NOW=1 restores the old immediate completion, EDEN_NX2_VDEC_TRACE=1 logs every decoder call.
Related, in the same batch: vdecsvc handles end-of-stream without aborting (no infinite waits in movie teardown, no re-armed wakeups, m_stream_ended reset on new input and on Flush/Stop), picks H.264 when the configure carries no codec name and the stream is H.264, and refreshes the GPU copy of each decoded frame — that trio is what the Yoshi demo's videos needed.
LAN Play through a room works now (PR #313, @cira0100)
Mario Kart World's LAN Play had two independent failures, and both are fixed:
- With airplane mode on (the default) the game rejected the network and went back to the main menu instead of opening the LAN browser:
IRequest::GetResultrefused the request. A joined room is the emulated network — its sockets only reach room members — so it now counts as an available network forGetResultandIsAnyInternetRequestAccepted. The room host and moderators live in the Moderator state rather than Joined, so that state is accepted too. - With airplane mode off, players could open the browser but never saw each other. Room fake IPs are spread over
192.168.0.0/16and some room servers hand members different/24s, while NIFM reported the host adapter's/24mask alongside the fake IP — so the games treated each other as off-subnet. A room now reports255.255.0.0, and the room address override is shared between the NIFM profile and the IP config calls so the two can't drift apart.
Underneath that, the room socket stack had real bugs:
- Joining a network didn't actually join.
Connectslept for a second holdingpacket_mutex, so the host's SyncNetwork reply couldn't be handled until Connect had already returned success in StationOpened — you'd see each other's group, enter it, and never meet. It now waits on a condition variable, accepts the reply only from the selected host and session, fails after five seconds, checks packet sizes and senders, rejects duplicate joins taking another station, and seeds session ids per network. ProxySocket::Recvwas a stub returning zero bytes, so games that read UDP replies withrecv()never saw them — MKW's LAN discovery replies are read that way. It now delegates to the datagram path, with matching non-blocking, timeout,MSG_PEEKand truncation behaviour, and severalrecv()bugs on the way there are fixed: a partial TCP read no longer leaves zero bytes in front of unread data, a blocking read no longer busy-waits and reportsETIMEDOUT, oversized UDP datagrams are truncated as BSD does instead of dropped withEMSGSIZE, and an expired wait returnsEAGAIN.- Socket lifetime races: a host socket could be closed twice (the second
closesockethitting an unrelated socket that had reused the fd), calls kept a raw reference into the shared descriptor table while another thread'sclose()destroyed the socket mid-call,accept()could publish into a slot another thread had just picked, and anfd == MAX_FDread past the table. Sockets now work on a copy taken under the table lock,close()frees the slot first and resets it only if it still holds the same socket, andpoll()keeps its sockets alive while it waits. fcntl(SETFL, O_NONBLOCK)was being undone by aMSG_DONTWAITreceive, so an fd reported non-blocking while its socket blocked again. The mode is now switched only forMSG_DONTWAIT, and the flags are read under the lock at both ends.- The SDL frontend never called
Network::Init, so its multiplayer option could not join a room at all.
Room packet delivery is also cheaper: only sockets that want a packet are collected, the packet is decompressed once instead of once per matching socket, DecompressDataZSTD no longer trusts the frame's declared size, and unwanted or malformed packets are dropped. 525 lines of new tests cover room packet matching, size-checked ZSTD decompression, ProxySocket receives and the LDN join handshake.
Convert to NUP keeps your dump's folder layout (@a55uka)
Anything that isn't exefs or romfs was being moved under data/, so a dump with a root-level data/ folder came out as data/data/, and other extras were renamed the same way. Those entries now keep the name and path the dump has, the way the reference packer does.
The save-folder button no longer guesses, and no longer asserts (@kraibse)
Switch 2 titles can report no save sizes in their NACP and still create account saves at runtime. The Open Save Data Location action assumed exactly one of user or device save data exists — it asserted Game uses both user and device savedata? and, with neither declared, fell into the device branch and created an empty directory in the wrong place. It now uses the device layout only when the NACP explicitly declares it, otherwise resolves the account save path (falling back to no user id when no profile is selected), and only creates the folder for titles that are known to need one.
Smaller fixes
- Game list scanning (@kraibse) now builds its path with
Common::FS::ToU8Stringinstead ofstd::filesystem::u8path, and the generated build-info source no longer redefinesBUILD_ID— both are path/build hygiene fixes for the worker that scans your game folders. - VP9 video playback fixes (upstream Eden #4590, via @crueter/@john): the VP9 codec wrote segmentation probabilities read from guest memory instead of from the decoded frame, which broke playback of VP9 streams in the browser applet.
- CI publishes again after a burst of pushes (#355). Pushes in quick succession cancelled every running build, so a busy stretch produced no release at all — which is why 159–166 don't exist. The running build now finishes and the newest push waits behind it.
How it works, for the technically curious
git diff build-158..build-167is 82 files, 18 123 insertions, 518 deletions. 15 276 of those insertions are the vendoredexternals/FidelityFX-SDKshaders (32 files, unmodified from FidelityFX SDK v1.1.4, commitc6efa6bf7f20, MIT; no prebuilt binary, DLL or AMD tool is used). Only the GPU half of FSR 3.1.4 is included — the HLSL callbacks, the autogen-reactive pass and the debug view are left out.src/is 48 files, +2840/−517; tests are 525 new lines in 4 files.- The upscaler is
vk_fsr3_upscaler.cpp(693 lines), a port of the SDK'sffx_fsr3upscaler.cpphost side, recorded through the scheduler with the shaders compiled by glslangValidator inhost_shaders. It takes HDR colour, render-size motion vectors and reversed depth; the game's first DLSS pass reads the depth throughcbuf0+0x298(Z32, reversed, the colour's size), and Kepler compute keeps it next to the motion vectors (RememberDlssMotionVectors).DescriptorBankInfogained sampled-image and sampler counts (default 0) because the SDK's GLSL uses separate textures and samplers. Devices without compute quad operations get the no-wave SPD variants. TextureCacheRuntime::TemporalUpscaleruns FSR first, then the stand-in, then the bilinear blit; a gap of more than 250 ms between evaluations restarts FSR's history (menus, movies, loads). ABlitColoroverload that described its source asGENERALin the descriptor while transitioning it toSHADER_READ_ONLY_OPTIMAL(a validation error) now describes the layout it uses.- The video decoder work is
vdecsvc— the HLE service insidesrc/core/hle/service/nx2_gsv.h— plussrc/video_core/host1x/nx2_video_decoder.cpp: released images keep pending fences after end of stream,m_stream_endedgates the re-arm ofm_ready->room, andFlush/Stopcomplete the fences handed out so far. The MKW workaround it replaces was the immediate completion;EDEN_NX2_VDEC_EOS_RELEASE_NOW=1restores it. Note the end-of-stream change was tested on Bananza; Mario Kart World, whose movies the old workaround targeted, was not retested for it. - The network work is 10 commits across
src/core/hle/service/sockets/bsd.cpp,ldn/lan_discovery.cpp,nifm/nifm.cppandsrc/core/internal_network/{network,socket_proxy,sockets}. The new tests aresrc/tests/core/ldn.cpp(room join handshake, packet matching),src/tests/core/internal_network/socket_proxy.cpp(ProxySocketreceives) andsrc/tests/common/zstd_compression.cpp(size-checked decompression). - The one build fix in this range is
Fsr3Upscaler::Pass::Dispatchno longer taking aschedulerparameter that shadowedFsr3Upscaler::scheduler, which failed the Windows MinGW PGO build under-Werror=shadow(#364).
Automatic build of nx2 at e5e82c1.
Linux
Linux packages come as an AppImage (self-contained, needs glibc 2.39 or newer: Ubuntu 24.04, Fedora 40, Debian 13, Arch) and a tar.gz (uses your system libraries, so Qt 6 and the usual libraries must be installed).
| Build Type | Standard | PGO (Recommended) | Notes |
|---|---|---|---|
| amd64 | AppImage · tar.gz | AppImage · tar.gz | Make the AppImage executable (chmod +x) and run it
|
| ARM (aarch64) | AppImage · tar.gz | AppImage · tar.gz |
Windows
Windows packages are in-place zip files: extract and run eglantine.exe, or eden-cli.exe from a terminal.
| Build Type | Standard | PGO (Recommended) | Notes |
|---|---|---|---|
| amd64 / x86_64 (MSVC) | MSVC zip | Use if you run into graphical issues on other builds | |
| amd64 / x86_64 (MinGW) | PGO zip | Built with MinGW clang; bundles its own DLLs, no Visual C++ redistributable needed |
macOS
| Build Type | Standard | PGO (Recommended) | Notes |
|---|---|---|---|
| arm64 (Apple Silicon) | zip | macOS 15 or newer. Not notarized: right-click and choose Open the first time, or run xattr -dr com.apple.quarantine eglantine.app. eden-cli is inside the app, in Contents/MacOS
|
The PGO builds are the same programs compiled with profile-guided optimization (clang plus Eden's profile); prefer them when your CPU supports it.