Skip to content

Fix Linux game detection: search every Steam install, and don't dereference a null - #274

Draft
IrPgFKS0 wants to merge 1 commit into
BeamMP:masterfrom
IrPgFKS0:fix/linux-steam-library-detection
Draft

IrPgFKS0 wants to merge 1 commit into
BeamMP:masterfrom
IrPgFKS0:fix/linux-steam-library-detection

Conversation

@IrPgFKS0

Copy link
Copy Markdown

Three separate reasons LegitimacyCheck() can fail to find BeamNG on Linux, all in the __linux__
branch of src/Security/BeamNG.cpp. Each is visible by reading the code; the second and third are
also demonstrable.

1. The search stops at the first Steam install

for (const auto& path : steamappsCommonPaths) {
    if (std::filesystem::exists(steamappsPath)) {
        steamappsFolderFound = true;
        libraryFoldersPath = steamappsPath / "libraryfolders.vdf";
        if (std::filesystem::exists(libraryFoldersPath)) {
            libraryFoldersFound = true;
            break;            // <-- first vdf wins, permanently
        }
    }
}

It breaks as soon as it finds a steamapps folder that merely has a libraryfolders.vdf — not one
that actually lists the game. A machine with more than one Steam install (a native one plus Flatpak
is common) only ever gets the first examined; if the game is registered in the other, detection
fails even though everything is installed correctly.

Now every candidate that exists is searched, stopping when the game is actually found.

2. Null dereference on a library with no apps block

(folderInfo.second->childs["apps"]->attribs).contains("284160")

childs is unordered_map<string, shared_ptr<basic_object>>, so operator[] on a missing key
value-initialises a null shared_ptr, inserts it, and the -> dereferences null. A library
folder entry with no games installed has exactly that shape.

I checked this against the vendored vdf_parser.hpp rather than assuming. Parsing:

"libraryfolders"
{
    "0" { "path" "/home/u/.local/share/Steam"  "label" "" }
    "1" { "path" "/mnt/games/SteamLibrary"  "apps" { "284160" "12345678" } }
}

childs["apps"] yields nullptr for entry "0". With find() + a null check the entry is
skipped and the game is correctly found in entry "1".

3. A detection failure is reported, then ignored

All three failure paths return with GameDir empty, and main() continues straight into
PreGame(GetGameDir())CheckVer()std::filesystem::file_size() on a path that does not
exist. That is why the report in #251 shows two errors:

[ERROR] The game directory was not found.
[ERROR] Exception in main(): filesystem error: cannot get file size: No such file or directory [integrity.json]

The second is only a consequence of the first, but it is the one that looks like the bug. These now
throw with a message saying what was searched for. LegitimacyCheck() is already called inside
a try/catch in main() that logs and rethrows — this change simply makes that handler
reachable; it could not fire for this path before.

Also

Two Steam locations that were missing from the candidate list:

  • ~/.local/share/Steam/steamapps — a native install where the ~/.steam symlinks are absent
  • ~/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps — Flatpak's real data
    directory, rather than only the .steam/root symlink inside the sandbox

Scope

This deliberately does not touch case-sensitive path resolution. That is what #259 addresses,
and these changes compose with it rather than conflicting — if a library path's casing does not
match the disk, #259's resolver is still what is needed.

Refs #251.

Testing

Verified the new library-scanning logic against the real vdf_parser.hpp: an entry without an
apps block is skipped rather than dereferenced, a vdf that does not list 284160 yields nothing
so the outer loop continues, and the upstream expression is confirmed to produce the null described
above.

I was not able to run a full Linux build locally (my container toolchain is broken at the
moment), so the complete translation unit is unverified on GCC — hence draft. The change is
confined to the __linux__ branch and uses no API the surrounding code does not already use
(debug(std::string) appears a few lines above; .contains is already used on attribs).


Transparency: this fix came out of debugging a LAN-only fork of the launcher, and was written with
AI assistance and reviewed by me. I am not making the template's self-authorship declaration, and
I am filing it as a draft so maintainers can decide how they want to treat it. Happy to rework,
split, or close it.

IrPgFKS0 added a commit to IrPgFKS0/BeamMP-Launcher that referenced this pull request Sep 14, 2026
Same change submitted upstream as BeamMP#274 (draft), extracted against
upstream/master; this region of the fork was byte-identical, so the patch is identical.

Diagnosed from a report that the combined binary works on two CachyOS boxes but fails on
Fedora with "The game directory was not found" followed by "cannot get file size:
integrity.json" -- upstream issue BeamMP#251. Three defects in the __linux__ branch:

1. The candidate loop `break`s at the first steamapps dir that merely HAS a
   libraryfolders.vdf, not one that lists the game -- so a machine with both a native and a
   Flatpak Steam only ever gets the first examined. Every existing candidate is now
   searched. This is the likely Fedora cause (Steam there usually comes from Flathub while
   a stray ~/.steam/root sorts first) and explains why Arch-based boxes are unaffected: for
   them the first candidate IS the right one.

2. `childs["apps"]->attribs` is UB for a library entry with no "apps" block -- childs maps
   to shared_ptr, so operator[] inserts a value-initialised null and the -> dereferences
   it. Demonstrated against the vendored vdf_parser, not assumed: a two-entry vdf whose
   first library has no "apps" block yields nullptr for that key. Now find() + null check.

3. Every failure path returned with GameDir empty, and main() went on to
   PreGame(GetGameDir()) -> CheckVer() -> file_size() on a nonexistent path. That is the
   confusing second error users report; it is only a consequence of the first. They now
   throw a descriptive message. LegitimacyCheck() is already wrapped in a try/catch in
   main() that logs and rethrows -- that handler was simply unreachable for this path.

Plus two missing Steam locations: ~/.local/share/Steam/steamapps (native install with no
~/.steam symlinks) and the Flatpak data directory proper
(~/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps).

Case-sensitive path resolution is deliberately NOT included -- that is upstream's open
BeamMP#259, and these compose with it rather than conflicting. The fork had previously skipped
BeamMP#259 as "game-launch-on-Linux only, not our deployment"; Linux clients are now in scope, so
it is worth revisiting separately.

NOT BUILT: the container toolchain is down (Docker engine returning 500; WSL2 reports the
Virtual Machine Platform disabled), so no Linux binary carries this yet and the exe version
is deliberately NOT bumped. The new VDF logic was compiled and exercised with MSVC against
the real parser header; the rest of the translation unit is unverified on GCC.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
IrPgFKS0 added a commit to IrPgFKS0/BeamMP that referenced this pull request Sep 14, 2026
… ledger

Mod side is version + notes only -- the one real mod change in this build is the physmult
global-leak fix already committed in f41d7bd, which had been sitting unbuilt since h98
went out. This is the build it rides into.

The reason for the build is launcher-side: on Linux the client could fail to find BeamNG
and quit with a misleading "cannot get file size: integrity.json". Reported as upstream
issue BeamMP#251, reproduced by a user whose two CachyOS machines work and whose Fedora machine
does not. Three causes, all in code this fork inherited byte-identically from upstream --
see BeamMP-Launcher b1938c6, offered upstream as BeamMP/BeamMP-Launcher#274.

Ledger: h99 added, h96 demoted out of the "previous build" slot, kept-tag list extended to
five (h99/h98/h96/h94/0.38-rollback). The AGPL source offer now names lan-release-p13h99 --
it must always name a tag that `git ls-remote --tags` actually shows, which is the invariant
that had quietly broken before h98.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@IrPgFKS0

Copy link
Copy Markdown
Author

Update: I was able to build this on Linux after all (my container toolchain is working again), so the caveat in the description about the full translation unit being unverified on GCC no longer applies — it compiles clean, and the resulting binary contains the new code paths.

Still happy to rework or split this however suits you.

… a null

Three separate reasons `LegitimacyCheck()` can fail to find BeamNG on Linux, all in the
`__linux__` branch of src/Security/BeamNG.cpp.

1. The candidate loop `break`s at the first steamapps directory that merely *has* a
   libraryfolders.vdf, so if that Steam install is not the one the game is registered in,
   the remaining candidates are never examined. A machine with both a native and a Flatpak
   Steam only ever gets the first one looked at. Every candidate that exists is now
   searched, and the loop stops when the game is actually found.

2. `folderInfo.second->childs["apps"]->attribs` is undefined behaviour for any library
   entry without an "apps" block. `childs` is
   `unordered_map<string, shared_ptr<basic_object>>`, so `operator[]` on a missing key
   value-initialises a null `shared_ptr` and inserts it, and the `->` then dereferences
   null. A library folder with no games installed has exactly that shape. Looked up with
   `find()` and null-checked instead. (Verified against the vendored vdf_parser: parsing a
   libraryfolders.vdf whose first entry has no "apps" block yields a null for that key.)

3. On failure the function returned with `GameDir` empty, and main() went straight on to
   `PreGame(GetGameDir())` -> `CheckVer()` -> `std::filesystem::file_size()` on a path that
   does not exist. Users therefore saw

       [ERROR] The game directory was not found.
       [ERROR] Exception in main(): filesystem error: cannot get file size:
               No such file or directory [integrity.json]

   and reported the second line, which is only a consequence of the first. These now throw
   with a message that says what was looked for. `LegitimacyCheck()` is already called
   inside a try/catch in main() that logs and rethrows, so this is the path that error
   handling was written for; it was simply unreachable before.

Also adds two Steam locations that were missing: `~/.local/share/Steam/steamapps` (a native
install where the `~/.steam` symlinks are absent) and
`~/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps` (Flatpak's real data
directory, rather than only the `.steam/root` symlink inside the sandbox).

Deliberately does NOT touch case-sensitive path resolution -- that is what BeamMP#259 addresses,
and these changes compose with it rather than conflicting.

Refs BeamMP#251
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant