All notable changes to BC250-Toolkit are documented here.
- Notifications from automatic game configuration are now clickable: selecting one opens that game's Steam library page, including when configuration failed.
- An automatic-update failure notification opens the details and links to the matching GitHub release.
- These system notices do not play the incoming-chat sound; real Steam message notifications keep their usual sound.
- Uses a shared, safe Steam click router adapted from Steamcord PR #71, with separate IDs so it cannot redirect real Steam chats or Steamcord messages.
After installing an update, the button went back to "Up to date", exactly like a click that did nothing. It now reads "Updated to X ✓", with a note to close and reopen the Quick Access menu to load the new version. Same fix as Steamcord #52, reported by @bastiHST90.
Installing an update wrote the new files and stopped there: the plugin reload
that should follow could never happen, so the update only took effect at the
next boot — with nothing said about it either way. Two measured reasons: Decky's
loader is a PyInstaller bundle, so every plugin backend inherits its library path
and systemctl cannot even start from one; and restarting a system service is
refused to a plugin that does not run as root. Nothing read the result.
The update button now asks the loader — which does run as root — to reload this plugin alone, and reports the new version when it is done. Automatic updates say the update takes effect the next time Steam starts, instead of claiming a reload that never came.
Found through Steamcord #52, reported by @bastiHST90; the same defect was in this plugin.
This plugin runs as root, so it was assumed to be safe from this. It was not: the library-path problem hits every plugin alike, root or otherwise.
The System tab gained a Hardware integrations panel that reports what this machine actually has, and changes nothing:
- Wi-Fi — the adapter NetworkManager knows about, its state and the driver behind it.
- DualSense — whether the
hid_playstationdriver is loaded, and how many controllers are connected right now. - Display — the DRM connectors currently active.
- Audio output — the PipeWire sink in use.
- HDMI-CEC — shown only when a
/dev/cec*bus exists.
Nothing here enables cecd, CEC or any TV setting on its own. The point is the
opposite: knowing what the hardware really offers is what keeps a TV or
controller feature from being proposed on a machine that cannot do it.
Every read-only line in the System tab is now a D-pad stop with a visible highlight, so the Quick Access Menu scrolls down to the rows at the bottom instead of skipping the whole section.
The same probes are available from the terminal with bc250-status, in
bc250-tweaks.
A Steam notification that pops up while you are live ends up in the video: gamescope draws it over the game, and that is the picture a stream captures. BC250 Toolkit's notifications are now held while you are live and shown once the stream ends.
It follows the Streamer mode setting in Steamcord (Automatic, Always on, Off): Automatic holds them during a Discord Go Live or a BoneCast stream or recording, Always on covers OBS and any other streaming software. Without Steamcord, they are held while BoneCast is live or recording.
After a power cut the BIOS puts the core mask back to 6 cores, and the Restore at boot service rewrites it and reboots. The first time this happened for real, the mask was written but the reboot was refused — Operation denied due to active block inhibitor — so the machine stayed on 6 cores / 12 threads, and the attempt was counted anyway, leaving one try out of two.
The service now retries for 30 seconds, logging which programs hold an inhibitor, then reboots ignoring them; nothing irreversible is written at that point of the boot. If the reboot still cannot happen, the attempt is not counted. An already-installed service picks up the new script the next time the plugin loads.
Once the core mask is written, the section only showed the Restore at boot switch, with nothing saying that a restart was still needed. It now says so and offers a Reboot now button until the 8 cores are up.
When BC250 Control Center (by movacx) is installed, the Toolkit becomes its Game Mode interface. A new Tuning tab drives the same protected helper as the Control Center's own Quick Access plugin:
- GPU governor profiles and validated safe points;
- a live 4×5 Compute Unit grid, saved boot table and boot service;
- CPU overclock through the Control Center's detector, manual scale and boot service;
- manual fan speed per channel, and back to automatic.
Nothing is stored on the Toolkit side. Both interfaces read and write the same files, so a change made on the desktop shows up in Game Mode and the other way round. The CU tab goes through the Control Center too while it is present, and the Toolkit's own CU boot service is disabled once the Control Center restores CUs at boot, so the table is never applied twice with two different values. Without the Control Center, everything works as before.
On Bazzite and other rpm-ostree systems, the tab can also install the Control
Center when it is missing: it downloads the official v1.19.0 RPM, refuses it
unless its SHA-256 matches, adds it with rpm-ostree install, then offers to
reboot. The version is pinned to the one whose helper protocol the Toolkit
speaks. Once the Control Center is detected, the button is gone. Elsewhere, the
tab keeps pointing to the project page.
The Toolkit only runs the helper when it is a root-owned, non-writable file, checks its protocol version before every write, and never forwards a value outside the lists the helper accepts.
The "Current" GPU line read pp_dpm_sclk, which stays stuck between 14 and
100 MHz on the BC-250 even while the governor drives the GPU at 1850 MHz. It
now reads the amdgpu freq1_input sensor, the same source MangoHud uses.
The live CU count came from a cache that was only refreshed when empty, so a change made by another tool never appeared. It is now re-read once the cache is more than a minute old.
Two separate faults, both silent:
- The update was applied file by file and gave up on the first one it could not
write — leaving the plugin half updated, part old code and part new. It now
surveys everything first: a code file it cannot write cancels the update
without touching anything, while documentation, licences and
plugin.jsonare skipped and the update proceeds. - The release check ran once, a few seconds after boot, which is often before
the network is up — and nothing retried, so the plugin stayed on its version
until a boot that happened to be luckier. It now retries while the failure is
the network. On the machine this was found on, three boots out of four had
been dying on
Temporary failure in name resolution.
When an update genuinely cannot be applied, the plugin now says so instead of writing one line to a log nobody reads.
- Keep the 8-core unlock across power cuts. A new Restore at boot toggle in the CPU unlock section. The CU profile can simply be re-poked at boot because compute units are written live; cores cannot, since the presence mask is only read when the CPU initialises. The service therefore checks the mask at startup and, only when the cores are missing, rewrites it and reboots the machine once. The mask survives warm reboots, so that extra reboot happens only after a genuine power-off.
- The unlock status now reports whether that service is enabled, so the toggle reflects the real state of the system rather than a remembered preference.
- A service that reboots the machine at boot is the most dangerous thing this
plugin can install, so it is capped at two attempts before giving up for
good, and
bc250.nocoreunlockon the kernel command line disarms it without needing a working system. - Its scripts are copied to
/usr/local/lib/bc250-core-unlock/, outside the plugin directory that Decky rewrites on every update. - The toggle needs the
bc250-core-bootsudoers rules from bc250-tweaks.
-
A GPU load that is actually measured. The BC-250's firmware does not report GPU activity at all:
gpu_busy_percentanswers operation not supported, andaverage_gfx_activityin the driver's metrics table holds0xFFFF, the "unsupported" sentinel. Overlays divide that by 100 and show 655 % — a number that never moves, whatever the machine is doing.The Resources tab now derives the figure from the kernel's per-engine accounting (
drm-engine-gfxinfdinfo), the same sourcenvtopuses. Measured on a BC-250: about 58 % with only the Steam interface drawn, 68-75 % in a game, tracking the GPU temperature as it climbs from 40 to 46 °C.
- The SoC temperature is now read from the metrics table alongside the GPU temperature, instead of being unavailable.
- Power draw. The same metrics table carries GPU and package wattage, and both are unusable on this chip: under a constant load the GPU figure swings between 0.9 W and 62 W within seconds. The decoding is sound — the temperature beside it is steady and the CPU power field holds its sentinel — so the firmware itself is at fault. Overlays read that field too, which is where their wattages come from. No number is better than an invented one.
The Toolkit degraded differently: with no MostRecent, it fell back to the first account listed, which is right only by luck when several exist.
- The active Steam account could no longer be identified, after Steam changed
its files. Steam stopped publishing a numeric
ActiveUserinregistry.vdf— it now publishesAutoLoginUser, holding the account name — and droppedMostRecentfromloginusers.vdfin favour ofAutoLoginandTimestamp. Both probes came back empty and everything fell back to a generic profile. The account is now resolved fromAutoLoginUsermatched by name, thenAutoLogin, then the most recentTimestamp; the older keys are still tried first, so an older Steam behaves exactly as before.
- Calls to
systemctl --userinherited the plugin loader's PyInstaller environment. Decky's loader is a PyInstaller binary and pointsLD_LIBRARY_PATH(and sometimesLD_PRELOAD) at its own bundled libraries. Child processes inherited them, so system binaries loaded the wronglibcryptoand aborted withOPENSSL_3.4.0 not found. That silently broke the user-systemd reload on unload and made gamemode always report itself as inactive. Both call sites now run with a cleaned environment, restoring the originalLD_LIBRARY_PATHthat PyInstaller saves aside. This never showed on Bazzite, whose system libraries happen to match the bundled ones; it was found while fixing the same flaw in Steamcord (Steamcord #38).
- The in-plugin updater could not update anything on a normal install, and
said it had. Decky root-owns the plugin's top-level directory, so creating
the temporary file the updater writes through failed with
Permission denied— even though the files being replaced belong to the user. Writing in place is now used as a fallback when the temporary file cannot be created but the destination exists and is writable. - A failed automatic update was reported as a success.
apply()returns a dict, and{"ok": False, "error": …}is always truthy in Python, so the boot-time auto-updater restarted Decky after a failure as if the update had landed — repeating on every boot, since the installed version never changed. It now reads the result and logs why it gave up.
- 8-core CPU unlock (6C/12T → 8C/16T). The BC-250 enumerates only 6 of the
8 Zen 2 cores on its Oberon die. The CU/UMA tab now reads the core presence
mask, shows whether this particular board is eligible, and can wake the other
two cores up. Only a mask of exactly
0x77is acted on — an asymmetric mask looks like genuine factory defect binning, and the SMU primitive writes0xFFregardless, so those boards are deliberately left alone.- Temporary by design. The mask survives warm reboots but a full power-off reverts it. It is presented as a compatibility test to run before considering the modified BIOS, which is the only way to make it permanent.
- The SMU governor is stopped for the write and restarted whatever happens.
Its unit name is auto-detected (
cyan-skillfish-governor-smu,oberon-governor, …) and its absence is handled, so this works on any distribution. - The status probe requires three consistent reads before it reports a mask: the governor shares the same SMN window, so a read taken mid-transaction returns an unrelated register.
- Credit: the unlock primitive and script are the work of rw-r-r-0644 (MIT), vendored untouched with its licence.
- CPU cores and threads in the System tab's Resources section —
6C / 12T, turning green at8C / 16T.
- The plugin was not running as root at all. The privileged flag in
plugin.jsonwas spelled_rootinstead ofroot, so DeckyLoader ignored it and the backend ran as the normal user. Everything that needs privileges — CU management, UMA/VRAM writes — was silently relying on passwordlesssudobeing configured, which is not the case on most systems. Now fixed. - The user ID lookup could return root, which the fix above would have made
reachable.
_user_uid()read the owner of~/.local/share/bc250-toolkitfirst; now that the plugin creates that directory itself as root, the function returned0— and~/.drircwould have been chowned to root, silently taking the user's mesa configuration away from them. It now asks the home directory, which never belongs to root. The same mistake is fixed for the user-contextsystemctl --user daemon-reload, which would have talked to root's systemd. - Files written into the home directory are now given back to the user, instead of staying root-owned.
- Controller navigation rows could log "Unhandled flow-children" errors.
The current Steam client only accepts
row/column(and their variants) asflow-childrenvalues; the legacyhorizontalvalue is rejected on every render, which can degrade controller navigation on the affected button rows. All occurrences now userow.
- Monochrome SVG icons across the QAM UI, matching the rest of the Necrosiak plugin suite (Steamcord v1.16.1). Color emoji (tabs, refresh, download, warning, GitHub) were replaced with monochrome vector icons that inherit the surrounding text size and color.
- In-plugin updates failed on root-owned installs. The updater overwrote
files with
shutil.copy2, which ends with achmodon the destination — something a non-root process cannot do on root-owned files even when they are world-writable. Files are now replaced via a temp file + atomicos.replace, which only needs write permission on the directory — and every replaced file becomes owned by the user, so a root-owned install heals itself as it updates.
- Update failures are now visible. When installing an update fails (for example on a root-owned local install: Permission denied), the QAM shows the error under the update button instead of staying on "installing…" forever.
- System tab really scrolls with a controller now. Each info row is wrapped in a focusable so the D-pad steps through them and the Quick Access Menu scrolls to follow the focus (the previous scroll-container approach didn't move without focusable children).
- System tab is scrollable with a controller again. The tab is made of display-only fields (no focusable element when bc250-tweaks isn't installed), so after the fan/clock rows were added the gamepad got stuck at the top and couldn't reach the lower info. The tab is now wrapped in a scrollable focusable container, and the temperature + clock are shown on one line per chip (CPU / GPU) to keep it short.
- Live GPU and CPU clocks in the System tab, next to the temperatures and
fan. GPU shader clock from the amdgpu sensor, CPU clock averaged from
/proc/cpuinfo. (Fan control was evaluated and deliberately left out — the board's PWM mode isn't reliably controllable and forcing it risks overheating; monitoring only.)
- Fan speed in the System tab, next to the CPU/GPU temperatures. Read from the board's Super-I/O sensor (nct6686 on the BC-250); the fastest spinning header is reported as the active fan (RPM).
- umr auto-install now detects the OS and adds Fedora (
dnf) and Debian/Ubuntu (apt) on top of the existing rpm-ostree + Arch family (pacman/paru/yay/shelly). A new_is_ostree()check keeps rpm-ostree as the only method on immutable images (Bazzite/SteamOS), so a mutable BC-250 (Fedora/Debian) can now install umr from the button too. - READMEs (9 languages): added a compatibility section — the plugin targets every OS documented for the BC-250 with automatic OS detection.
- CachyOS/Arch support for the umr installation — the install button now
detects the package manager (
rpm-ostreeon Bazzite/SteamOS,pacman,paruoryayon CachyOS/Arch) instead of assuming rpm-ostree. Contributed by @AyeZeeBB, merged from their fork. - GPU instance fallback — umr reads/writes now try
cyan_skillfish@1, thencyan_skillfish@0, then umr's own auto-detection, so the CU tools keep working on kernels/systems where the GPU enumerates on a different debugfs instance. Contributed by @AyeZeeBB.
- READMEs (9 languages): CachyOS listed in supported systems and requirements, umr install wording updated, new Community contributors section.
- VRAM (UMA) management — new section in the CU tab to set the BIOS
UMA Frame Buffer Size (Auto / 2G / 4G / 8G) by patching the
AmdSetupEFI NVRAM variable directly, without entering the BIOS screen. Takes effect at the next reboot; the panel shows the live VRAM and the value staged in the BIOS. - Guard rails: BIOS version whitelist (P3.00), NVRAM layout/size check,
automatic backup before every write (with a
restore_uma_backuprollback method); the buttons are disabled on unrecognized BIOSes. - Permanent warning in the panel: Auto (≈8 GB) is the safe recommended value — if graphical artifacts (e.g. green glitches) appear after a change, switch back to Auto.
- Resources section in the System tab — shows the enabled system RAM (what the OS gets after the UMA carve-out), the used RAM (with a colored usage percentage) and the number of active CUs.
- The CU tab is now labeled CU/UMA to reflect the new VRAM section.
- UMA writes now go through the root helper
bc250-uma-helper(installed by bc250-tweaks) with a NOPASSWD sudoers rule — no more sudo password prompt from the QAM. Writing UMA requires an up-to-date bc250-tweaks install.
0.3.2 - 2026-06-29
- Consistent action buttons — every action button (System update, DB
refresh, update check/install, CU UMR install, games refresh, About/GitHub)
now uses the same focusable
CardBtncard style as the Games/CU lists. The "update available" button turns green to stand out.
0.3.1 - 2026-06-29
- UI overhaul — horizontal tab bar (Games / CU / System / Settings) with controller focus highlight, matching the Steamcord style.
- Games list as cards — each game shows its Steam library icon (fetched via
appStore, with a colored-initial fallback) plus a focus halo. - Inline per-game config — a game's settings (variants, Proton, launch options, notes) now expand directly under the selected game instead of at the bottom of the list; config variants are picked from small focusable buttons.
- CU profiles as cards (
CardBtn) with active/focus states. - About section in Settings — plugin name, version, author and a button to open the GitHub repository.
- Native Steam notifications — all in-plugin toasts now use
DisplayClientNotification(popup + sound) instead of the Decky toaster, with a guard against an empty Steam ID (which would otherwise crash the panel).
- New
get_versionmethod exposing the installed version (read frompackage.json) to the UI.
0.3.0 - 2026-06-28
- Multi-config per game — a game can ship several tuned profiles (e.g. Stable vs Performance); the chosen variant is remembered.
- Auto-apply (opt-in) — applies a known game's full config on launch and pre-configures every installed game from the database.
- Per-game GPU / RADV overrides written to
~/.drirc. - Reusable
ue5_dx12_oompreset for Unreal Engine 5 DX12 games that crash with Out of video memory despite free VRAM (first validated on Code Vein 2).
0.2.0 - 2026-06-28
- Release-based auto-update (silent auto-update, manual button, toggle).