From a0f70447bf1ebc9fd0d567f96a18e9ee1427914e Mon Sep 17 00:00:00 2001 From: MrDavid5465 Date: Thu, 3 Sep 2026 18:04:55 -0400 Subject: [PATCH 1/9] fix(cmake): handle a libproc2.pc that reports Version: UNKNOWN Some distro packagings -- the freedesktop Flatpak SDK among them -- ship a libproc2.pc whose Version field is the literal string UNKNOWN. Compared against that, VERSION_GREATER_EQUAL is always false, so the build fell through to the pre-4.0.5 four-argument PIDS_VAL API and failed to compile against a current libproc2 whose header moved to three arguments long ago. Take the old-API branch only when the version string really parses and really is old. Co-Authored-By: Claude Opus 5 --- CMakeLists.txt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d3e0678..a4975b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,9 +20,13 @@ pkg_check_modules(LIBPROC2 libproc2) if (LIBPROCPS_FOUND) #add_compile_definitions(USE_OLD_PID_VAL=0) elseif (LIBPROC2_FOUND) - if (LIBPROC2_VERSION VERSION_GREATER_EQUAL "4.0.5") - #add_compile_definitions(USE_OLD_PID_VAL=0) - else() + # Some distro packagings -- the freedesktop Flatpak SDK among them -- ship + # a libproc2.pc whose Version field is the literal string UNKNOWN. Compared + # against that, VERSION_GREATER_EQUAL is always false, so this fell through + # to the pre-4.0.5 four-argument PIDS_VAL API and failed to compile against + # a current libproc2 whose header has long since moved to three. Take the + # old-API branch only when the version really parses and really is old. + if (LIBPROC2_VERSION MATCHES "^[0-9]" AND LIBPROC2_VERSION VERSION_LESS "4.0.5") add_compile_definitions(USE_OLD_PID_VAL) endif() else() From bdd288868ce0196040d58b596183efb083486a5b Mon Sep 17 00:00:00 2001 From: MrDavid5465 Date: Thu, 3 Sep 2026 18:05:07 -0400 Subject: [PATCH 2/9] feat: look at the host for sims when running inside a Flatpak simapi identifies a running sim by scanning /proc for known process names and reads /proc//environ for the Steam compat variables behind the auto-bridge. Flatpak gives every sandbox its own PID namespace and offers no way to share the host's: measured 634 processes on the host against 4 inside a sandbox, unchanged by --allow=devel or --filesystem=host, and /proc cannot be bound in either ("Path /proc is reserved by Flatpak"). simd has no flag to be told which sim to use, so a sandboxed simd simply never finds one. The sim lookup, the environ read, both liveness checks, notify-send and the bridge exec now route through `flatpak-spawn --host`, guarded on the presence of /.flatpak-info so a native build takes the original code paths unchanged. Three things this had to work around: - The host process table is read with two tagged `ps` runs (args and comm) rather than one combined format, because a process whose name contains spaces -- "AC: main thread" -- cannot be parsed unambiguously from a single line. - Output comes back through a file staged in $XDG_RUNTIME_DIR/app/$FLATPAK_ID and renamed into place, not through popen: a pipe to `flatpak-spawn --host` never reaches EOF, so the read hangs. - The redirection is written as `{ ...; } > out.part` because in `sh -c` a trailing redirect binds only to the last command of a sequence. Co-Authored-By: Claude Opus 5 --- simapi/getpid.c | 273 ++++++++++++++++++++++++++++++++++++++++++++++-- simapi/simapi.h | 4 + simd/simd.c | 70 +++++++++++-- 3 files changed, 334 insertions(+), 13 deletions(-) diff --git a/simapi/getpid.c b/simapi/getpid.c index c88ac84..5b183cb 100644 --- a/simapi/getpid.c +++ b/simapi/getpid.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -38,6 +39,11 @@ static int isMatch(const char* possibleMatch, const char* checkAgainst) int is_pid_running(pid_t pid) { + if (simapi_in_flatpak()) + { + return simapi_host_pid_alive(pid); + } + if (pid <= 0) { return 0; @@ -114,16 +120,31 @@ char* getEnvValueForPid(pid_t pid, const char* envName) sprintf( &path[6], "%d/environ", pid ); - envFile = fopen(path, "r"); - if ( envFile == NULL ) + /* The pid came from the host's process table, so its /proc entry exists + * only out there -- see simapi_host_capture(). */ + if ( simapi_in_flatpak() ) { -// errno = ESRCH; - return NULL; + char hostcmd[96]; + snprintf(hostcmd, sizeof(hostcmd), "cat /proc/%d/environ", (int) pid); + buf = simapi_host_capture(hostcmd, &maxIdx); + if ( buf == NULL ) + { + return NULL; + } } + else + { + envFile = fopen(path, "r"); + if ( envFile == NULL ) + { +// errno = ESRCH; + return NULL; + } - maxIdx = readData(&buf, envFile); + maxIdx = readData(&buf, envFile); - fclose(envFile); + fclose(envFile); + } envNameLen = strlen(envName); cur = buf; @@ -171,9 +192,249 @@ int check_if_number (char* str) } + +/* ------------------------------------------------------------------------ + * Looking at the host from inside a Flatpak sandbox. + * + * A sandbox gets its own PID namespace -- measured on a real install, 5 pids + * visible inside against 497 on the host -- so the /proc walk below finds only + * the sandbox itself and no sim is ever detected. Flatpak has no option to + * share the host's namespace (--allow=devel and --filesystem=host both change + * nothing), leaving `flatpak-spawn --host`, which asks the session helper to + * run a command outside. That needs --talk-name=org.freedesktop.Flatpak in the + * manifest. + * + * The output comes back through a FILE, not a pipe. A popen()'d + * `flatpak-spawn --host ...` hands the pipe's write end to the session helper, + * and a copy of it outlives every process the sandbox can see: reading to EOF + * never returns. Measured -- simd sat in anon_pipe_read with no children left + * and no further log line, forever. So the host writes to a file under + * $XDG_RUNTIME_DIR/app/$FLATPAK_ID, the one directory mounted at the same path + * on both sides, and renames it into place; the sandbox then reads an ordinary + * file where EOF means what it says. The rename is what makes "the file + * exists" mean "the output is complete". + * + * None of this is reached outside a sandbox: every caller keeps its original + * code path, so native builds are byte-for-byte unaffected. + * --------------------------------------------------------------------- */ +int simapi_in_flatpak(void) +{ + static int cached = -1; + if (cached < 0) + { + cached = (access("/.flatpak-info", F_OK) == 0) ? 1 : 0; + } + return cached; +} + +/* Visible under the same path inside the sandbox and out. */ +static const char* host_share_dir(void) +{ + static char dir[256]; + if (dir[0] == '\0') + { + const char* runtime = getenv("XDG_RUNTIME_DIR"); + const char* app_id = getenv("FLATPAK_ID"); + if (runtime != NULL && app_id != NULL) + { + snprintf(dir, sizeof(dir), "%s/app/%s", runtime, app_id); + } + else + { + snprintf(dir, sizeof(dir), "/tmp"); + } + } + return dir; +} + +/* Runs `hostcmd` on the host and returns its stdout, or NULL. Caller frees. */ +char* simapi_host_capture(const char* hostcmd, size_t* out_len) +{ + static unsigned long seq = 0; + char out[320]; + char cmd[1024]; + + if (out_len != NULL) + { + *out_len = 0; + } + + snprintf(out, sizeof(out), "%s/simapi-host-%d-%lu", host_share_dir(), (int) getpid(), seq++); + + /* Written to .part and renamed, so the file only appears complete. + * The exit status is deliberately ignored: simd runs a libuv loop whose + * SIGCHLD handling reaps children this call did not spawn, which makes + * system()'s own waitpid fail and lose the status. */ + snprintf(cmd, sizeof(cmd), + /* hostcmd is grouped: it may itself be a sequence, and without + * the braces the redirect would bind to its last command only -- + * which silently dropped half the process table. */ + "flatpak-spawn --host sh -c '{ %s ; } > %s.part 2>/dev/null; mv %s.part %s' >/dev/null 2>&1", + hostcmd, out, out, out); + (void) system(cmd); + + /* system() normally returns only once the command is done; the wait can + * still be lost to that same reaping, so give the rename a moment. */ + FILE* f = NULL; + for (int waited = 0; waited < 2000; waited += 20) + { + f = fopen(out, "r"); + if (f != NULL) + { + break; + } + usleep(20 * 1000); + } + + if (f == NULL) + { + return NULL; + } + + size_t cap = 8192; + size_t len = 0; + char* buf = malloc(cap); + + if (buf != NULL) + { + for (;;) + { + size_t n = fread(buf + len, 1, cap - len - 1, f); + len += n; + if (len + 1 < cap) + { + break; /* short read on a real file means EOF */ + } + cap *= 2; + char* grown = realloc(buf, cap); + if (grown == NULL) + { + break; + } + buf = grown; + } + buf[len] = '\0'; + } + + fclose(f); + unlink(out); + + if (out_len != NULL) + { + *out_len = len; + } + return buf; +} + +/* Is a pid in the *host's* namespace alive? kill(2) would resolve the number + * against the sandbox's own processes -- usually nothing, occasionally the + * wrong one. */ +int simapi_host_pid_alive(pid_t pid) +{ + char hostcmd[96]; + snprintf(hostcmd, sizeof(hostcmd), "kill -0 %d 2>/dev/null && echo alive", (int) pid); + + char* out = simapi_host_capture(hostcmd, NULL); + int alive = (out != NULL && strncmp(out, "alive", 5) == 0); + free(out); + return alive; +} + +/* Does a path exist on the *host*? For paths that are the host's by nature -- + * a Proton binary under STEAM_COMPAT_TOOL_PATHS, say -- since the sandbox's + * own filesystem knows nothing about them. Checking locally is how the bridge + * launch failed silently: Proton was never found, so the fork never happened + * and nothing was logged above debug level. */ +int simapi_host_file_exists(const char* path) +{ + char hostcmd[1024]; + snprintf(hostcmd, sizeof(hostcmd), "test -e '%s' && echo yes", path); + + char* out = simapi_host_capture(hostcmd, NULL); + int exists = (out != NULL && strncmp(out, "yes", 3) == 0); + free(out); + return exists; +} + +static struct SimProcessInfo pidof_host(char* pname[], int num) +{ + struct SimProcessInfo p; + p.pid = -1; + p.pos = -1; + + /* Two ps runs, each line tagged, because a single `ps -o pid=,comm=,args=` + * cannot be parsed: comm may contain spaces. Wine names Assetto Corsa's + * process "AC: main thread", which a naive scanf read as comm="AC:" and + * argv[0]="main" -- nothing matched "acs.exe", simapi never set simstatus, + * and simd mapped no telemetry at all while the game ran. Splitting the + * two fields into their own lines keeps each one unambiguous: "A + * " gives argv[0] as the first token after the pid, and + * "C " gives comm as the whole rest of the line, which is + * the same pair libproc2 hands the native path below. */ + char* table = simapi_host_capture( + "ps -A -o pid=,args= | sed \"s/^/A /\"; ps -A -o pid=,comm= | sed \"s/^/C /\"", + NULL); + if (table == NULL) + { + return p; + } + + char* saveptr = NULL; + for (char* line = strtok_r(table, "\n", &saveptr); + line != NULL; + line = strtok_r(NULL, "\n", &saveptr)) + { + char kind = line[0]; + if (kind != 'A' && kind != 'C') + { + continue; + } + + int pid = 0; + int consumed = 0; + if (sscanf(line + 1, " %d %n", &pid, &consumed) < 1 || consumed == 0) + { + continue; + } + + char* field = line + 1 + consumed; + if (kind == 'A') + { + /* argv[0] only. The whole of `ps -o args=` would be looser than + * libproc2's PIDS_CMDLINE_V first element: a shell whose arguments + * merely mention a sim's exe -- a Steam launch command, say -- + * would match, and simd would track that shell as the game. */ + char* end = strchr(field, ' '); + if (end != NULL) + { + *end = '\0'; + } + } + + for (int i = 0; pname[i] != NULL && i < num; i++) + { + if (strcasestr(field, pname[i]) != NULL) + { + p.pid = pid; + p.pos = i; + free(table); + return p; + } + } + } + + free(table); + return p; +} + struct SimProcessInfo pidof (char* pname[], int num) { + if (simapi_in_flatpak()) + { + return pidof_host(pname, num); + } + struct SimProcessInfo p; p.pid = -1; p.pos = -1; diff --git a/simapi/simapi.h b/simapi/simapi.h index 3d53b12..4667bbc 100644 --- a/simapi/simapi.h +++ b/simapi/simapi.h @@ -113,6 +113,10 @@ struct SimProcessInfo void simapi_set_faux_siminfo(SimInfo* si); +int simapi_in_flatpak(void); +char* simapi_host_capture(const char* hostcmd, size_t* out_len); +int simapi_host_pid_alive(pid_t pid); +int simapi_host_file_exists(const char* path); int is_pid_running(pid_t pid); struct SimProcessInfo get_process_match(char* pidstrings[], int num); char* getEnvValueForPid(pid_t pid, const char* envName); diff --git a/simd/simd.c b/simd/simd.c index f422f76..320ad7a 100644 --- a/simd/simd.c +++ b/simd/simd.c @@ -350,6 +350,21 @@ int startudp(int port) return err; } +/* Paths handed to the bridge launch are the host's whenever this is a Flatpak + * build; everywhere else this is the ordinary local check. */ +static bool bridge_file_exists(const char* path) +{ + if (path == NULL) + { + return false; + } + if (simapi_in_flatpak()) + { + return simapi_host_file_exists(path) ? true : false; + } + return does_file_exist(path); +} + int is_pid_running(pid_t pid) { if (pid <= 0) @@ -357,6 +372,14 @@ int is_pid_running(pid_t pid) return 0; } + /* Sandboxed, the pids simd tracks belong to the host's namespace, where + * kill(2) cannot reach them. Shared with simapi so both halves agree on + * how the question is asked. */ + if (simapi_in_flatpak()) + { + return simapi_host_pid_alive(pid); + } + // send signal 0 (no actual signal) if (kill(pid, 0) == 0) { @@ -395,7 +418,8 @@ void bridgeclosecallback(uv_timer_t* handle) if(simds.notify == true) { char cmd[512]; - snprintf(cmd, sizeof(cmd), "notify-send -t 3000 \"%s\" \"game stopped\"", "simd"); + snprintf(cmd, sizeof(cmd), "%snotify-send -t 3000 \"%s\" \"game stopped\"", + simapi_in_flatpak() ? "flatpak-spawn --host " : "", "simd"); system(cmd); } @@ -519,7 +543,10 @@ void gamefindcallback(uv_timer_t* handle) { char* pathcheck1 = NULL; asprintf(&pathcheck1, "%s/dist/bin/wine", token); - if(does_file_exist(pathcheck1) == true) + /* Proton lives on the host, so ask the host. Locally this + * always answered false under Flatpak and the bridge was + * never launched. */ + if(bridge_file_exists(pathcheck1) == true) { wineexe = strdup(pathcheck1); } @@ -531,7 +558,7 @@ void gamefindcallback(uv_timer_t* handle) if(wineexe == NULL) { asprintf(&pathcheck1, "%s/files/bin/wine", token); - if(does_file_exist(pathcheck1) == true) + if(bridge_file_exists(pathcheck1) == true) { wineexe = strdup(pathcheck1); } @@ -598,13 +625,41 @@ void gamefindcallback(uv_timer_t* handle) close(devnull); } - if(env_simd_wrap_exe == NULL) + char* target = (env_simd_wrap_exe == NULL) ? wineexe : env_simd_wrap_exe; + + if(simapi_in_flatpak()) { - ret = execve(wineexe, newargv, newenviron); + /* Proton, its prefix and the bridge exe are all on the + * host; execve here would look for them inside the + * runtime. --env carries the environment across, since + * flatpak-spawn does not pass this one through, and + * --watch-bus ties the host process's lifetime to this + * one so the existing SIGTERM teardown still ends it. */ + char* hostargv[16]; + char envopts[4][512]; + int n = 0; + int e = 0; + + hostargv[n++] = "flatpak-spawn"; + hostargv[n++] = "--host"; + hostargv[n++] = "--watch-bus"; + for(int i = 0; newenviron[i] != NULL && e < 4; i++) + { + snprintf(envopts[e], sizeof(envopts[e]), "--env=%s", newenviron[i]); + hostargv[n++] = envopts[e]; + e++; + } + for(int i = 0; newargv[i] != NULL && n < 15; i++) + { + hostargv[n++] = newargv[i]; + } + hostargv[n] = NULL; + + ret = execvp("flatpak-spawn", hostargv); } else { - ret = execve(env_simd_wrap_exe, newargv, newenviron); + ret = execve(target, newargv, newenviron); } _exit(127); } @@ -642,7 +697,8 @@ void gamefindcallback(uv_timer_t* handle) { char cmd[512]; const char* gamename = simapi_gametofullstr(sim); - snprintf(cmd, sizeof(cmd), "notify-send -t 3000 \"%s\" \"Detected %s (%i)\"", "simd", gamename, sim); + snprintf(cmd, sizeof(cmd), "%snotify-send -t 3000 \"%s\" \"Detected %s (%i)\"", + simapi_in_flatpak() ? "flatpak-spawn --host " : "", "simd", gamename, sim); system(cmd); } } From 3f1d85fa6c08b6065a5281eec7aa8dad593ba8a8 Mon Sep 17 00:00:00 2001 From: MrDavid5465 Date: Thu, 3 Sep 2026 18:19:28 -0400 Subject: [PATCH 3/9] build: add deb, rpm and Flatpak packaging for simd No upstream CMake changes were needed. SYSTEMD_USER_UNIT_DIR and SIMD_CONFIG_DIR are already CACHE PATH variables, so packaging redirects them off $ENV{HOME} rather than patching the install rules. Each format ships /usr/bin/simd, the libsimapi SONAME chain simd links at runtime, and simd.config as an example -- a package must not write into a user's home, which is what upstream's defaults do. packaging/simd.service replaces the shipped unit for deb and rpm. Upstream's conf/simd.service sets Type=simple against a daemon that double-forks, so systemd would take the parent's exit as the service failing while the real daemon carried on unsupervised, and it hardcodes ExecStart=%h/.local/bin/simd with LD_LIBRARY_PATH=%h/.local/lib, which is only correct for a build installed into ~/.local. The packaged unit runs /usr/bin/simd -n under systemd's own supervision. The Flatpak has no unit -- autostart is a ~/.config/autostart entry running `flatpak run`, the same pattern other Flatpak background services use. Its desktop entry passes -n for the same reason: the sandbox is torn down when the process flatpak started exits, so a double-forking simd would take its own daemon down with it. The manifest documents plainly that this bundle can run arbitrary host commands and is therefore not a security boundary. That is inherent to what simd does: it identifies a sim by reading the host process table, which a sandbox cannot see (634 host processes against 4 inside, /proc unbindable), and flatpak-spawn --host is the only mechanism that reaches it. Co-Authored-By: Claude Opus 5 --- packaging/debian/build-deb.sh | 55 +++++ packaging/fedora/simd.spec | 50 +++++ .../io.github.spacefreak18.simd.desktop | 16 ++ .../io.github.spacefreak18.simd.metainfo.xml | 24 ++ .../flatpak/io.github.spacefreak18.simd.yml | 205 ++++++++++++++++++ packaging/simd.service | 28 +++ stage/usr/bin/simd | Bin 0 -> 150992 bytes stage/usr/include/simapi.h | 124 +++++++++++ stage/usr/include/simdata.h | 201 +++++++++++++++++ stage/usr/include/simmapper.h | 93 ++++++++ stage/usr/lib/libsimapi.so | 1 + stage/usr/lib/libsimapi.so.1 | 1 + stage/usr/lib/libsimapi.so.1.0.1 | Bin 0 -> 70680 bytes stage/usr/lib/systemd/user/simd.service | 18 ++ stage/usr/share/pkgconfig/simapi.pc | 13 ++ stage/usr/share/simd/simd.config | 61 ++++++ 16 files changed, 890 insertions(+) create mode 100755 packaging/debian/build-deb.sh create mode 100644 packaging/fedora/simd.spec create mode 100644 packaging/flatpak/io.github.spacefreak18.simd.desktop create mode 100644 packaging/flatpak/io.github.spacefreak18.simd.metainfo.xml create mode 100644 packaging/flatpak/io.github.spacefreak18.simd.yml create mode 100644 packaging/simd.service create mode 100755 stage/usr/bin/simd create mode 100644 stage/usr/include/simapi.h create mode 100644 stage/usr/include/simdata.h create mode 100644 stage/usr/include/simmapper.h create mode 120000 stage/usr/lib/libsimapi.so create mode 120000 stage/usr/lib/libsimapi.so.1 create mode 100755 stage/usr/lib/libsimapi.so.1.0.1 create mode 100644 stage/usr/lib/systemd/user/simd.service create mode 100644 stage/usr/share/pkgconfig/simapi.pc create mode 100644 stage/usr/share/simd/simd.config diff --git a/packaging/debian/build-deb.sh b/packaging/debian/build-deb.sh new file mode 100755 index 0000000..9003962 --- /dev/null +++ b/packaging/debian/build-deb.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Builds a .deb from an already-configured build tree. +# +# build-deb.sh +# +# Dependencies are computed by dpkg-shlibdeps from the built binaries rather +# than maintained by hand: library package names drift between releases, and a +# stale hand-written list still builds fine, failing only at install time on +# someone else's machine. +set -euo pipefail + +builddir=${1:?build dir} +version=${2:?version} +output=${3:?output .deb} + +pkgroot=$(mktemp -d) +trap 'rm -rf "$pkgroot"' EXIT + +DESTDIR="$pkgroot" cmake --install "$builddir" >/dev/null + +# Upstream's CMake installs its systemd unit from simd/conf/simd.service, which +# hardcodes ExecStart=%h/.local/bin/simd and Type=simple against a daemon that +# double-forks. Replace it with the packaged one: /usr/bin/simd -n, supervised +# by systemd rather than by a fork it cannot see. +install -Dm644 packaging/simd.service "$pkgroot/usr/lib/systemd/user/simd.service" + +mkdir -p "$pkgroot/DEBIAN" +cat > "$pkgroot/DEBIAN/control" < +Description: SimAPI telemetry daemon for racing simulators + simd watches for a running racing simulator, maps its telemetry, and + republishes it as a universal shared memory map at /dev/shm/SIMAPI.DAT + for other applications to read. + . + Ships libsimapi alongside it, which simd links at runtime. +CTRL + +# shlibdeps needs the shipped library on its search path to resolve simd's own +# dependency on it; without -l it reports libsimapi.so.1 as an unknown symbol +# source and refuses to emit a Depends line at all. +( cd "$pkgroot" && dpkg-shlibdeps -l"$pkgroot/usr/lib" \ + -O usr/bin/simd usr/lib/libsimapi.so.1.0.1 ) > "$pkgroot/shlibdeps.txt" +deps=$(sed -e 's/^shlibs:Depends=//' "$pkgroot/shlibdeps.txt") +rm -f "$pkgroot/shlibdeps.txt" +echo "Depends: $deps" >> "$pkgroot/DEBIAN/control" + +echo "--- control ---" +cat "$pkgroot/DEBIAN/control" + +dpkg-deb --build "$pkgroot" "$output" diff --git a/packaging/fedora/simd.spec b/packaging/fedora/simd.spec new file mode 100644 index 0000000..73f87ab --- /dev/null +++ b/packaging/fedora/simd.spec @@ -0,0 +1,50 @@ +Name: simd +Version: %{?_version}%{!?_version:0.0.0} +Release: 1%{?dist} +Summary: SimAPI telemetry daemon for racing simulators +License: LGPL-3.0-or-later +URL: https://github.com/Spacefreak18/simapi + +BuildRequires: gcc cmake pkgconfig +BuildRequires: libuv-devel yder-devel argtable-devel libconfig-devel procps-ng-devel + +%description +simd watches for a running racing simulator, maps its telemetry, and +republishes it as a universal shared memory map at /dev/shm/SIMAPI.DAT for +other applications to read. Ships libsimapi alongside it, which simd links +at runtime. + +# Builds whatever tree has been staged at %{_sourcedir}/simapi. CI stages the +# checked-out tree so an rpm's contents are the commit it was built from, +# rather than whatever the default branch happened to be at build time. +%prep +rm -rf %{_builddir}/simapi +cp -r %{_sourcedir}/simapi %{_builddir}/ + +%build +cd %{_builddir}/simapi +# The two *_DIR variables are upstream cache paths that default under +# $ENV{HOME} -- fine for a developer's own install, but a package must never +# write into a user's home. Redirected here rather than patched upstream. +cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_INSTALL_LIBDIR=%{_lib} \ + -DSYSTEMD_USER_UNIT_DIR=/usr/lib/systemd/user \ + -DSIMD_CONFIG_DIR=/usr/share/simd +cmake --build build -j$(nproc) + +%install +cd %{_builddir}/simapi +DESTDIR=%{buildroot} cmake --install build +# Upstream's unit hardcodes ExecStart=%%h/.local/bin/simd with Type=simple +# against a daemon that double-forks; the packaged one runs /usr/bin/simd -n +# under systemd's own supervision. +install -Dm644 packaging/simd.service %{buildroot}/usr/lib/systemd/user/simd.service + +%files +/usr/bin/simd +/usr/%{_lib}/libsimapi.so* +/usr/lib/systemd/user/simd.service +/usr/share/simd/simd.config +/usr/include/*.h +/usr/share/pkgconfig/simapi.pc +%license LICENSE.rst diff --git a/packaging/flatpak/io.github.spacefreak18.simd.desktop b/packaging/flatpak/io.github.spacefreak18.simd.desktop new file mode 100644 index 0000000..d572cc4 --- /dev/null +++ b/packaging/flatpak/io.github.spacefreak18.simd.desktop @@ -0,0 +1,16 @@ +[Desktop Entry] +Name=SimAPI Daemon +Comment=Maps racing simulator telemetry to shared memory for other applications +Exec=simd -n +Icon=io.github.spacefreak18.simd +Terminal=false +Type=Application +Categories=Game;Utility; +# Not shown in menus: simd has no user interface. The entry exists so desktop +# "startup applications" tools can find it, and so an autostart entry can +# reference it by app id. +NoDisplay=true +# -n (--nodaemon) above is deliberate. simd double-forks by default, and under +# `flatpak run` the sandbox is torn down when the process it started exits -- +# taking the daemon with it. Running in the foreground keeps flatpak's own +# process supervision pointed at the real daemon. diff --git a/packaging/flatpak/io.github.spacefreak18.simd.metainfo.xml b/packaging/flatpak/io.github.spacefreak18.simd.metainfo.xml new file mode 100644 index 0000000..7945fcf --- /dev/null +++ b/packaging/flatpak/io.github.spacefreak18.simd.metainfo.xml @@ -0,0 +1,24 @@ + + + io.github.spacefreak18.simd + SimAPI Daemon + Racing simulator telemetry mapped to shared memory + MIT + LGPL-3.0-or-later + +

+ simd watches for a running racing simulator, maps its telemetry, and + republishes it as a universal shared memory map at /dev/shm/SIMAPI.DAT + for other applications to read. +

+

+ This build can run commands on the host system. It has to: identifying a + running simulator means reading the host's process table, which a Flatpak + sandbox cannot otherwise see. Treat this package as unconfined software + delivered through Flatpak, not as a sandboxed application. +

+
+ io.github.spacefreak18.simd.desktop + https://github.com/Spacefreak18/simapi + +
diff --git a/packaging/flatpak/io.github.spacefreak18.simd.yml b/packaging/flatpak/io.github.spacefreak18.simd.yml new file mode 100644 index 0000000..8a5bfda --- /dev/null +++ b/packaging/flatpak/io.github.spacefreak18.simd.yml @@ -0,0 +1,205 @@ +# Flatpak manifest for simd, the SimAPI telemetry daemon. +# +# simd is a background service, not an application: it watches for a running +# racing simulator, maps its telemetry, and republishes it at +# /dev/shm/SIMAPI.DAT for other applications to read. It is packaged as a +# Flatpak so it can be installed on immutable distributions, where a .deb or +# .rpm cannot be, and is started the way other Flatpak services are -- an +# entry in ~/.config/autostart running `flatpak run`. +# +# READ THIS BEFORE INSTALLING: this app can run arbitrary commands on the +# host, and its sandbox is therefore not a security boundary. +# +# That is not incidental, it is what the daemon does. simd identifies a +# running sim by scanning /proc for known process names and reads +# /proc//environ for the Steam compat variables behind its auto-bridge. +# Flatpak gives every sandbox its own PID namespace and offers no way to share +# the host's -- measured 634 processes on the host against 4 inside a sandbox, +# unchanged by --allow=devel or --filesystem=host, and /proc cannot be bound +# in ("Path /proc is reserved by Flatpak"). The only mechanism that reaches +# host processes is flatpak-spawn --host, which is general-purpose by nature: +# an app that can run one host command can run any of them. +# +# So --talk-name=org.freedesktop.Flatpak below is doing real work and is also +# the reason this bundle isolates nothing. Flatpak here is a delivery +# mechanism for immutable systems, not a confinement story. Anyone reviewing +# this for a public repository should weigh it on those terms. +app-id: io.github.spacefreak18.simd +runtime: org.freedesktop.Platform +runtime-version: '25.08' +sdk: org.freedesktop.Sdk +command: simd +finish-args: + # How simd reaches the host process table, the environ reads behind the + # auto-bridge, notify-send, and the Proton bridge exec. See the header. + - --talk-name=org.freedesktop.Flatpak + # Where simd publishes telemetry, as POSIX shared memory + # (/dev/shm/SIMAPI.DAT). shm is its own device option and is NOT implied by + # --device=all -- measured: `ls /dev/shm` inside a sandbox without it + # returned 0 entries against 18 on the host. Without this the map simd + # writes is invisible to every reader outside the sandbox, which is the + # entire point of running it. + - --device=shm + # simd builds its config path from getpwuid() rather than $HOME or + # XDG_CONFIG_HOME (simd.c), so it wants the host's real directory. Without + # it simd runs on but logs "Disabling Automatic Bridge Mode" and never + # launches the Windows bridge under Proton. + - --filesystem=xdg-config/simd:create + # Sim telemetry under Proton lives in the game's wine prefix. + - --filesystem=home + - --share=network + +modules: + - name: libconfig + buildsystem: cmake-ninja + config-opts: + - -DBUILD_TESTS=OFF + # libconfig 1.7.3's CMakeLists.txt predates CMake 3.5's minimum + # version policy; confirmed via a failed local build. + - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 + post-install: + - sh -c 'f=$(ls -1 COPYING COPYING.txt COPYING.LIB LICENSE LICENSE.txt LICENSE.md LICENCE licence.txt 2>/dev/null | head -1); if [ -n "$f" ]; then install -Dm644 "$f" /app/share/licenses/libconfig/$(basename "$f"); else echo "WARNING: no licence file found for libconfig"; fi' + sources: + - type: archive + url: https://github.com/hyperrealm/libconfig/archive/refs/tags/v1.7.3.tar.gz + sha256: 68757e37c567fd026330c8a8449aa5f9cac08a642f213f2687186b903bd7e94e + + - name: argtable2 + buildsystem: cmake-ninja + config-opts: + # Same CMake-minimum-version issue as libconfig -- this one's + # CMakeLists.txt is even older (2011). + - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 + # 2011-era code missing / includes for + # isspace/toupper/bzero; GCC 14+ made implicit-function-declaration a + # hard error by default where it used to just warn. Confirmed via a + # failed local build. + - -DCMAKE_C_FLAGS=-Wno-error=implicit-function-declaration + # add_library(argtable2 ...) has no explicit STATIC/SHARED, so it + # defaults to CMake's global BUILD_SHARED_LIBS (off by default -> a + # static .a). monocoque's own target_link_libraries() lists argtable2 + # before helper (which needs its symbols, e.g. arg_end/arg_parse in + # parameters.c) -- fine for a shared library (all symbols always + # available regardless of link-line position, which is what real + # distro packages of this ship), but a static archive only pulls + # symbols already needed by things linked *before* it, so helper's + # later reference to argtable2's symbols never resolves. Confirmed + # via a failed local build ("undefined reference to arg_end" etc. + # from helper's parameters.c, even though libargtable2.a genuinely + # contained those symbols). Building shared instead matches how this + # dependency normally ships and avoids the link-order sensitivity + # entirely, rather than reordering every target_link_libraries() call + # in CMakeLists.txt. + - -DBUILD_SHARED_LIBS=ON + # This CMake port has no install() rules at all (confirmed: no + # CMakeLists.txt in the tree calls install()), so the default + # cmake-ninja `ninja install` step silently does nothing -- monocoque's + # own build then fails on a missing argtable2.h. build-commands here + # replace the default install step: `ninja` still does the real + # compile, then copy the header and whatever library form actually got + # built (glob rather than a fixed versioned filename, since the exact + # .so version suffix isn't predictable without a build). + build-commands: + - ninja + - install -Dm644 src/argtable2.h /app/include/argtable2.h + - | + for f in src/libargtable2.so* src/libargtable2.a; do + if [ -e "$f" ]; then cp -P "$f" /app/lib/; fi + done + true + post-install: + - sh -c 'f=$(ls -1 COPYING COPYING.txt COPYING.LIB LICENSE LICENSE.txt LICENSE.md LICENCE licence.txt 2>/dev/null | head -1); if [ -n "$f" ]; then install -Dm644 "$f" /app/share/licenses/argtable2/$(basename "$f"); else echo "WARNING: no licence file found for argtable2"; fi' + sources: + - type: archive + url: https://sourceforge.net/projects/argtable/files/argtable/argtable-2.13/argtable2-13.tar.gz/download + sha256: 8f77e8a7ced5301af6e22f47302fdbc3b1ff41f2b83c43c77ae5ca041771ddbf + dest-filename: argtable2-13.tar.gz + + - name: libuv + buildsystem: cmake-ninja + config-opts: + - -DBUILD_TESTING=OFF + post-install: + - sh -c 'f=$(ls -1 COPYING COPYING.txt COPYING.LIB LICENSE LICENSE.txt LICENSE.md LICENCE licence.txt 2>/dev/null | head -1); if [ -n "$f" ]; then install -Dm644 "$f" /app/share/licenses/libuv/$(basename "$f"); else echo "WARNING: no licence file found for libuv"; fi' + sources: + - type: archive + url: https://github.com/libuv/libuv/archive/refs/tags/v1.49.2.tar.gz + sha256: 388ffcf3370d4cf7c4b3a3205504eea06c4be5f9e80d2ab32d19f8235accc1cf + + # orcania and yder are what simd logs through. Neither is in the freedesktop + # runtime and neither is packaged for Fedora either (the rpm legs vendor both + # as static libraries); here they are ordinary /app/lib shared libraries that + # ship inside the bundle. + # + # CMAKE_C_FLAGS_RELEASE carries -Wno-error because both projects append their + # own `-Wall -Werror` to CMAKE_C_FLAGS -- setting that variable would be + # overridden, while the _RELEASE flags land after it where the last of + # -Werror/-Wno-error wins. orcania 2.3.3 assigns strstr() on a const pointer + # to a char*, which newer compilers reject outright. + + # orcania and yder are what simd logs through. Neither is in the freedesktop + # runtime and neither is packaged for Fedora either (the rpm legs vendor both + # as static libraries); here they are ordinary /app/lib shared libraries that + # ship inside the bundle. + # + # CMAKE_C_FLAGS_RELEASE carries -Wno-error because both projects append their + # own `-Wall -Werror` to CMAKE_C_FLAGS -- setting that variable would be + # overridden, while the _RELEASE flags land after it where the last of + # -Werror/-Wno-error wins. orcania 2.3.3 assigns strstr() on a const pointer + # to a char*, which newer compilers reject outright. + - name: orcania + buildsystem: cmake-ninja + config-opts: + - -DCMAKE_BUILD_TYPE=Release + - -DCMAKE_C_FLAGS_RELEASE=-O2 -DNDEBUG -Wno-error + - -DBUILD_ORCANIA_TESTING=OFF + post-install: + - sh -c 'f=$(ls -1 COPYING COPYING.txt COPYING.LIB LICENSE LICENSE.txt LICENSE.md LICENCE licence.txt 2>/dev/null | head -1); if [ -n "$f" ]; then install -Dm644 "$f" /app/share/licenses/orcania/$(basename "$f"); else echo "WARNING: no licence file found for orcania"; fi' + sources: + - type: git + url: https://github.com/babelouest/orcania + commit: ffc8b55d09a3488f4f6be38034b33bc64bf8b0ce + + - name: yder + buildsystem: cmake-ninja + config-opts: + - -DCMAKE_BUILD_TYPE=Release + - -DCMAKE_C_FLAGS_RELEASE=-O2 -DNDEBUG -Wno-error + # Journald would need systemd-devel for a backend simd never selects; + # its config offers file and syslog. + - -DWITH_JOURNALD=OFF + - -DBUILD_YDER_TESTING=OFF + post-install: + - sh -c 'f=$(ls -1 COPYING COPYING.txt COPYING.LIB LICENSE LICENSE.txt LICENSE.md LICENCE licence.txt 2>/dev/null | head -1); if [ -n "$f" ]; then install -Dm644 "$f" /app/share/licenses/yder/$(basename "$f"); else echo "WARNING: no licence file found for yder"; fi' + sources: + - type: git + url: https://github.com/babelouest/yder + commit: dffe82c0483bb95d0d518ba1e36c568e63a24628 + + # simapi built through its *own* CMakeLists, unlike the monocoque module + # below which compiles the same sources into a static library of its own. + # That is what produces libsimapi.so and, with BUILD_SIMD, the simd binary -- + # pinned to the exact commit monocoque's submodule tracks, so the daemon and + # the app in this bundle agree on the SimData layout they map. + + - name: simd + buildsystem: cmake-ninja + config-opts: + - -DCMAKE_BUILD_TYPE=Release + - -DBUILD_SIMD=ON + # Both default ON and install into $ENV{HOME} -- a systemd user unit and + # a config file -- which during a flatpak-builder run would land in the + # build user's home rather than the prefix. The unit is meaningless in a + # Flatpak anyway; autostart is a ~/.config/autostart entry instead. + - -DINSTALL_SYSTEMD_SERVICE=off + - -DSIMD_CONFIG_DIR=/app/share/simd + build-options: + env: + LIBRARY_PATH: /app/lib + post-install: + - install -Dm644 LICENSE.rst /app/share/licenses/simd/LICENSE.rst + - install -Dm644 packaging/flatpak/io.github.spacefreak18.simd.desktop /app/share/applications/io.github.spacefreak18.simd.desktop + - install -Dm644 packaging/flatpak/io.github.spacefreak18.simd.metainfo.xml /app/share/metainfo/io.github.spacefreak18.simd.metainfo.xml + sources: + - type: dir + path: ../.. diff --git a/packaging/simd.service b/packaging/simd.service new file mode 100644 index 0000000..5044aec --- /dev/null +++ b/packaging/simd.service @@ -0,0 +1,28 @@ +[Unit] +Description=SimAPI Daemon - Racing Simulator Telemetry Service +Documentation=https://github.com/Spacefreak18/simapi +After=default.target + +[Service] +# -n (--nodaemon), not the default: simd double-forks on its own, and under +# Type=simple systemd would take the parent's exit as the service dying and +# report it failed while the real daemon carried on unsupervised. Letting +# systemd own the process instead of the double fork also makes Restart= +# and journald logging work as written. +# +# Type=forking with PIDFile=/tmp/simd.pid would be the alternative, but simd +# writes that path unconditionally and a world-writable pidfile in /tmp is +# not something to build a service around. +ExecStart=/usr/bin/simd -n +Restart=on-failure +RestartSec=5 + +# No LD_LIBRARY_PATH: libsimapi is installed to the system library path by +# the package, unlike upstream's conf/simd.service which assumes a build +# installed into ~/.local. + +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=default.target diff --git a/stage/usr/bin/simd b/stage/usr/bin/simd new file mode 100755 index 0000000000000000000000000000000000000000..c2a3cede1e28369b8b2052f202a18ebf990524cc GIT binary patch literal 150992 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 OxP^<3@>O5HTlGJLULp(t literal 0 HcmV?d00001 diff --git a/stage/usr/include/simapi.h b/stage/usr/include/simapi.h new file mode 100644 index 0000000..4667bbc --- /dev/null +++ b/stage/usr/include/simapi.h @@ -0,0 +1,124 @@ +#ifndef _SIMMAPI_H +#define _SIMMAPI_H + +#include +#include + +#define SIMAPI_VERSION 1 + +typedef void (*func_ptr_t)(char* message); +//func_ptr_t logfunc; + +void simapi_set_log_info(func_ptr_t logfunc); +void simapi_set_log_debug(func_ptr_t logfunc); +void simapi_set_log_trace(func_ptr_t logfunc); + + +typedef enum +{ + SIMAPI_LOGLEVEL_INFO = 0, + SIMAPI_LOGLEVEL_DEBUG = 1, + SIMAPI_LOGLEVEL_TRACE = 2, +} +SIMAPI_LOGLEVEL; + +typedef enum +{ + SIMULATORAPI_SIMAPI_TEST = 0, + SIMULATORAPI_ASSETTO_CORSA = 1, + SIMULATORAPI_RFACTOR2 = 2, + SIMULATORAPI_PROJECTCARS2 = 3, + SIMULATORAPI_SCSTRUCKSIM2 = 4, + SIMULATORAPI_OUTSIMOUTGAUGE = 5, + SIMULATORAPI_DIRT_RALLY_2 = 6, + SIMULATORAPI_F1_2018 = 7, + SIMULATORAPI_RACE_ROOM = 8, + SIMULATORAPI_FORZA = 9, + SIMULATORAPI_LMU = 10, + SIMULATORAPI_WRECKFEST2 = 11, + SIMULATORAPI_RICHARD_BURNS_RALLY = 12, +} +SimulatorAPI; + +typedef enum +{ + SIMULATOREXE_SIMAPI_TEST_NONE = 0, + SIMULATOREXE_ASSETTO_CORSA = 244210, //ac + SIMULATOREXE_RFACTOR2 = 365960, //rf2 + SIMULATOREXE_AUTOMOBILISTA2 = 1066890, //ams2 + SIMULATOREXE_AUTOMOBILISTA2_DEMO = 1786210, //ams2demo + SIMULATOREXE_EUROTRUCKS2 = 227300, //et2 + SIMULATOREXE_AMERICANTRUCKS = 270880, //at + SIMULATOREXE_ASSETTO_CORSA_COMPETIZIONE = 805550, //acc + SIMULATOREXE_ASSETTO_CORSA_EVO = 3058630, //ace + SIMULATOREXE_ASSETTO_CORSA_RALLY = 3917090, //acr + SIMULATOREXE_LEMANS_ULTIMATE = 2399420, //lmu + SIMULATOREXE_BEAMNG = 284160, //beamng + SIMULATOREXE_LIVE_FOR_SPEED = 0000001, //lfs + SIMULATOREXE_DIRT_RALLY_2 = 690790, //dr2 + SIMULATOREXE_F1_2022 = 1692250, //f122 + SIMULATOREXE_RACE_ROOM = 211500, //r3e + SIMULATOREXE_FORZA_HORIZON_5 = 1551360, //fh5 + SIMULATOREXE_FORZA_HORIZON_6 = 2483190, //fh6 + SIMULATOREXE_WRECKFEST2 = 1203190, //wf2 + SIMULATOREXE_RICHARD_BURNS_RALLY = 0000002, //rbr +} +SimulatorEXE; + +typedef enum +{ + SIMAPI_ERROR_NONE = 0, + SIMAPI_ERROR_UNKNOWN = 1, + SIMAPI_ERROR_INVALID_SIM = 2, + SIMAPI_ERROR_NODATA = 3, +} +SimAPIError; + +typedef enum +{ + INTEGER = 0, + DOUBLE = 1, + FLOAT = 2, + CHAR = 3, + BOOLEAN = 4, + UINT8 = 5, + UINT32 = 6, + UINT64 = 7, +} +SimDataType; + +typedef struct +{ + bool isSimOn; + bool SimUsesUDP; + bool SimSupportsBasicTelemetry; + bool SimSupportsTyreEffects; + bool SimSupportsRealtimeTelemetry; + bool SimSupportsAdvancedUI; + bool SimCalculatesTyreDiameter; + bool SimCalculatesSlipRatio; + bool SimSupportsHapticEffects; + SimulatorAPI mapapi; + SimulatorAPI simulatorapi; + SimulatorEXE simulatorexe; + pid_t pid; +} +SimInfo; + +struct SimProcessInfo +{ + int pid; + int pos; +}; + +void simapi_set_faux_siminfo(SimInfo* si); + +int simapi_in_flatpak(void); +char* simapi_host_capture(const char* hostcmd, size_t* out_len); +int simapi_host_pid_alive(pid_t pid); +int simapi_host_file_exists(const char* path); +int is_pid_running(pid_t pid); +struct SimProcessInfo get_process_match(char* pidstrings[], int num); +char* getEnvValueForPid(pid_t pid, const char* envName); + +#endif diff --git a/stage/usr/include/simdata.h b/stage/usr/include/simdata.h new file mode 100644 index 0000000..9e49efc --- /dev/null +++ b/stage/usr/include/simdata.h @@ -0,0 +1,201 @@ +#ifndef _SIMDATA_H +#define _SIMDATA_H + +#include +#include + +#define MAXCARS 128 +#define PROXCARS 6 + +typedef enum +{ + SIMAPI_STATUS_OFF = 0, + SIMAPI_STATUS_MENU = 1, + SIMAPI_STATUS_ACTIVEPLAY = 2, +} +SIMAPI_STATUS; + +typedef enum +{ + SIMAPI_FLAG_GREEN = 0, + SIMAPI_FLAG_YELLOW = 1, + SIMAPI_FLAG_RED = 2, + SIMAPI_FLAG_CHEQUERED = 3, + SIMAPI_FLAG_BLUE = 4, + SIMAPI_FLAG_WHITE = 5, + SIMAPI_FLAG_BLACK = 6, + SIMAPI_FLAG_BLACK_WHITE = 7, + SIMAPI_FLAG_BLACK_ORANGE = 8, + SIMAPI_FLAG_ORANGE = 9 +} +SIMAPI_FLAG; + +typedef enum +{ + SIMAPI_GEAR_REVERSE = 0, + SIMAPI_GEAR_NEUTRAL = 1, + SIMAPI_GEAR_FIRST = 2, + SIMAPI_GEAR_SECOND = 3, + SIMAPI_GEAR_THIRD = 4, + SIMAPI_GEAR_FOURTH = 5, + SIMAPI_GEAR_FIFTH = 6, + SIMAPI_GEAR_SIXTH = 7, + SIMAPI_GEAR_SEVENTH = 8, + SIMAPI_GEAR_EIGHT = 9 +} +SIMAPI_GEAR; + + +#pragma pack(push) +#pragma pack(4) + +typedef struct //LapTime +{ + uint32_t hours; + uint32_t minutes; + uint32_t seconds; + uint32_t fraction; +} LapTime; + +typedef struct //CarData +{ + double xpos; + double ypos; + double zpos; + double carspline; + double speed; + uint32_t pos; + uint32_t lap; + uint32_t trackpos; + LapTime lastlap; + LapTime bestlap; + // not all of these are set by each sim + bool inpit; // no matter what, if you're in the pit set this + bool inpitlane; // in pit lane, actively entering or exiting, but not stopped or in garage + bool ingarage; // not always available + bool inpitentrance; // not always available + bool inpitexit; // not always available + bool inpitstopped; // not always available + char driver[128]; + char car[128]; +} CarData; + +typedef struct //ProximityData +{ + double radius; + double theta; // in degrees + uint32_t lap; +} ProximityData; + +typedef struct //SimData +{ + uint64_t mtick; + uint64_t prev_mtick; + + uint32_t simstatus; // less than 1 is off or in menu, 2 is active + uint32_t velocity; + uint32_t rpms; + uint32_t gear; + uint32_t pulses; + uint32_t maxrpm; + uint32_t idlerpm; + uint32_t maxgears; + uint32_t altitude; + uint32_t lap; + uint32_t position; + uint32_t numlaps; + uint32_t playerlaps; + uint32_t numcars; + char gearc[3]; + + double Xvelocity; + double Yvelocity; + double Zvelocity; + + double worldXvelocity; + double worldYvelocity; + double worldZvelocity; + + double gas; + double brake; + double fuel; + double fuelcapacity; + double clutch; + double steer; + double handbrake; + + double turboboost; + double turboboostperct; + double maxturbo; + + double abs; + double brakebias; + /* Wheel angular velocity. Units: radians per second (rad/s) */ + double tyreRPS[4]; + double tyrediameter[4]; + double tyreslipratio[4]; + double tyreslipangle[4]; + double distance; + + double heading; + double pitch; + double roll; + double worldposx; + double worldposy; + double worldposz; + + double braketemp[4]; + double tyrewear[4]; + double tyretemp[4]; + double tyrepressure[4]; + + double tyrecontact0[4]; + double tyrecontact1[4]; + double tyrecontact2[4]; + + double airdensity; + double airtemp; + double tracktemp; + + double suspension[4]; + double suspvelocity[4]; + + double trackdistancearound; + double playerspline; + double trackspline; + uint32_t playertrackpos; + uint32_t tracksamples; + + LapTime lastlap; + LapTime bestlap; + LapTime currentlap; + uint32_t currentlapinseconds; + uint32_t lastlapinseconds; + uint32_t time; + LapTime sessiontime; + uint8_t session; + uint8_t sectorindex; + double sector1time; + double sector2time; + uint32_t lastsectorinms; + uint8_t courseflag; + uint8_t playerflag; + + bool lapisvalid; + + char car[128]; + char track[128]; + char driver[128]; + char tyrecompound[128]; + + CarData cars[MAXCARS]; + ProximityData pd[PROXCARS]; + + uint8_t simapi; + uint64_t simexe; + bool simon; + uint8_t simapiversion; +} SimData; + +#pragma pack(pop) +#endif diff --git a/stage/usr/include/simmapper.h b/stage/usr/include/simmapper.h new file mode 100644 index 0000000..ef9d793 --- /dev/null +++ b/stage/usr/include/simmapper.h @@ -0,0 +1,93 @@ +#ifndef _SIMMAPPER_H +#define _SIMMAPPER_H + +//#include "ac.h" +//#include "rf2.h" +//#include "pcars2.h" +//#include "scs2.h" + +#include "simdata.h" +#include "simapi.h" + + + +//typedef struct +//{ +// void* addr; +// int fd; +// union +// { +// ACMap* ac; +// RF2Map* rf2; +// PCars2Map* pcars2; +// SCS2Map* scs2; +// } d; +//} +//SimMap; + +//struct _simmap; +typedef struct _simmap SimMap; + +typedef struct +{ + void* pcars2_addr; + int pcars2_fd; + void* acphysics_addr; + int acphysics_fd; + void* acgraphics_addr; + int acgraphics_fd; + void* acstatic_addr; + int acstatic_fd; + void* acevophysics_addr; + int acevophysics_fd; + void* acevographics_addr; + int acevographics_fd; + void* acevostatic_addr; + int acevostatic_fd; + void* accrew_addr; + int accrew_fd; + void* r3e_addr; + int r3e_fd; + void* lmu_addr; + int lmu_fd; +} +SimCompatMap; + +bool simapi_does_sim_need_bridge(SimulatorEXE s); +SimulatorEXE simapi_get_sim_exe(SimInfo* si); +SimInfo simapi_get_sim(SimData* simdata, SimMap* simmap, bool force_udp, int (*setup_udp)(int), bool simd); + +int simapi_strtogame(const char* game); +char* simapi_gametostr(SimulatorEXE sim); +char* simapi_gametofullstr(SimulatorEXE sim); + +SimMap* simapi_simmap_create(void); + +int simapi_init(SimData* simdata, SimMap* simmap, SimulatorAPI simulator, SimulatorEXE simexe); +int simapi_initudp(SimData* simdata, SimMap* simmap, SimulatorAPI simulator); +int simapi_datamap(SimData* simdata, SimMap* simmap, SimulatorAPI simulator, bool udp, char* base); +int simapi_sim_clear(SimData* simdata, SimMap* simmap, bool issimd); +int simapi_universalmap_open(SimMap* simmap, SimData* simdata); +int simapi_universalmap_free(SimMap* simmap); +int simapi_compatmap_open(SimCompatMap* compatmap); +int simapi_compatmap_free(SimCompatMap* compatmap); +int simapi_compatmap_clear(SimCompatMap* compatmap); + +void simapi_set_proximity_data(SimData* simdata, int cars, int8_t lr_flip); + +void map_suspension_velocity(SimData* simdata, double new_suspension[4]); + +void map_assetto_corsa_data(SimData* simdata, SimMap* simmap, SimulatorEXE simexe); +void map_rfactor2_data(SimData* simdata, SimMap* simmap); +void map_project_cars2_data(SimData* simdata, SimMap* simmap, bool udp, char* base); +void map_trucks_data(SimData* simdata, SimMap* simmap); +void map_outgauge_outsim_data(SimData* simdata, SimMap* simmap, SimulatorEXE simexe, char* base); +void map_dirt_rally_2_data(SimData* simdata, SimMap* simmap, char* base); +void map_f1_2018_data(SimData* simdata, SimMap* simmap, char* base); +void map_wreckfest2_data(SimData* simdata, SimMap* simmap, char* base); +void map_richard_burns_rally_data(SimData* simdata, SimMap* simmap, char* base); +void map_forza_data(SimData* simdata, SimMap* simmap, char* base); +void map_r3e_data(SimData* simdata, SimMap* simmap); + + +#endif diff --git a/stage/usr/lib/libsimapi.so b/stage/usr/lib/libsimapi.so new file mode 120000 index 0000000..a20b362 --- /dev/null +++ b/stage/usr/lib/libsimapi.so @@ -0,0 +1 @@ +libsimapi.so.1 \ No newline at end of file diff --git a/stage/usr/lib/libsimapi.so.1 b/stage/usr/lib/libsimapi.so.1 new file mode 120000 index 0000000..332f99d --- /dev/null +++ b/stage/usr/lib/libsimapi.so.1 @@ -0,0 +1 @@ +libsimapi.so.1.0.1 \ No newline at end of file diff --git a/stage/usr/lib/libsimapi.so.1.0.1 b/stage/usr/lib/libsimapi.so.1.0.1 new file mode 100755 index 0000000000000000000000000000000000000000..e4641efacd7eb9083f085f610be472ecb722bcda GIT binary patch literal 70680 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 literal 0 HcmV?d00001 diff --git a/stage/usr/lib/systemd/user/simd.service b/stage/usr/lib/systemd/user/simd.service new file mode 100644 index 0000000..0c65e0c --- /dev/null +++ b/stage/usr/lib/systemd/user/simd.service @@ -0,0 +1,18 @@ +[Unit] +Description=SimAPI Daemon - Racing Simulator Telemetry Service +Documentation=https://github.com/Spacefreak18/simapi +After=default.target + +[Service] +Type=simple +Environment="LD_LIBRARY_PATH=%h/.local/lib" +ExecStart=%h/.local/bin/simd +Restart=on-failure +RestartSec=5 + +# Logging goes to journald +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=default.target diff --git a/stage/usr/share/pkgconfig/simapi.pc b/stage/usr/share/pkgconfig/simapi.pc new file mode 100644 index 0000000..56fa12a --- /dev/null +++ b/stage/usr/share/pkgconfig/simapi.pc @@ -0,0 +1,13 @@ +prefix=/usr +exec_prefix=${prefix} + +libdir=/usr/lib +includedir=/usr/include + +Name: simapi +Description: Telemetry Mapping Library for Racing Sims +Version: 1.0.1 + +Requires: +Libs: -L${libdir} -lmylib +Cflags: -I${includedir} diff --git a/stage/usr/share/simd/simd.config b/stage/usr/share/simd/simd.config new file mode 100644 index 0000000..d61b8af --- /dev/null +++ b/stage/usr/share/simd/simd.config @@ -0,0 +1,61 @@ +sims = +( + // currently this is only for shm compatability and only sims listed here will be considered + { + name = "AssettoCorsa"; + gameid = 244210; + launchexe = "AssettoCorsa.exe"; + liveexe = "acs.exe"; + bridgedelay = 5; // this is the default, this is optional + simapi = 1; // SimulatorAPI enum defined in simapi.h + }, + { + name = "AssettoCorsaCompetizione"; + gameid = 805550; + launchexe = "AC2-Win64-Shipping.exe"; + liveexe = "AC2-Win64-Shipping.exe"; + bridgedelay = 5; // this is the default, this is optional + simapi = 1; // SimulatorAPI enum defined in simapi.h + }, + { + name = "AssettoCorsaEVO"; + gameid = 3058630; + launchexe = "AssettoCorsaEVO.exe"; + liveexe = "AssettoCorsaEVO.exe"; + bridgedelay = 5; // this is the default, this is optional + simapi = 1; // SimulatorAPI enum defined in simapi.h + }, + { + name = "AssettoCorsaRally"; + gameid = 3917090; + launchexe = "acr.exe"; + liveexe = "acr.exe"; + bridgedelay = 5; // this is the default, this is optional + simapi = 1; // SimulatorAPI enum defined in simapi.h + }, + { + name = "Automobilista2"; + gameid = 1066890; + useudp = false; + launchexe = "AMS2AVX.exe"; + liveexe = "AMS2AVX.exe"; + bridgedelay = 5; // this is the default, this is optional + simapi = 3; // SimulatorAPI enum defined in simapi.h + }, + { + name = "DirtRally2"; + gameid = 690790; + launchexe = "dirtrally2.exe"; + liveexe = "dirtrally2.exe"; + bridgedelay = 5; + simapi = 6; // SimulatorAPI enum defined in simapi.h + }, + { + name = "RaceRoomExperience"; + gameid = 211500; + launchexe = "RRRE64.exe"; + liveexe = "RRRE64.exe"; + bridgedelay = 5; + simapi = 8; // SimulatorAPI enum defined in simapi.h + }, +); From 6195e7e4c25b67d720b3f16f85efad245bb60039 Mon Sep 17 00:00:00 2001 From: MrDavid5465 Date: Thu, 3 Sep 2026 18:25:14 -0400 Subject: [PATCH 4/9] ci: build deb, rpm, AppImage and Flatpak packages Builds on the packaging already in tools/distro rather than a parallel tree: the per-distro control files and the two Fedora specs are used as they stand, with only the version stamped from the tag. The split into libsimapi and simd is upstream's and is kept. Both specs' %prep cloned the default branch unconditionally, so an rpm's contents tracked master rather than the tag being built -- a fix on the branch being released would be absent from its own release. They now build a staged tree when CI provides one and clone only as a fallback, so a bare `rpmbuild -ba` still behaves as before. simd.spec additionally ships the packaged systemd unit and simd.config as an example, and redirects SYSTEMD_USER_UNIT_DIR and SIMD_CONFIG_DIR off $ENV{HOME}. Neither needed an upstream code change; both are already CACHE PATH variables. The AppImage carries the config but not the unit -- it cannot install one onto the host. tools/distro/simd.svg is a placeholder, present only because linuxdeploy requires an icon and this repository has none. The Flatpak job installs flatpak-builder with Recommends left enabled. Those recommends are the helpers it shells out to at build time -- tar's decompressors, eu-strip, patch -- and stripping them produces a builder that runs, builds several modules, then fails on whichever helper the next module happens to need. Co-Authored-By: Claude Opus 5 --- .github/workflows/packages.yml | 287 ++++++++++++++++++ packaging/debian/build-deb.sh | 55 ---- packaging/fedora/simd.spec | 50 --- tools/distro/fedora/rpm/simapi.spec | 14 +- tools/distro/fedora/rpm/simd.spec | 33 +- .../io.github.spacefreak18.simd.desktop | 0 .../io.github.spacefreak18.simd.metainfo.xml | 0 .../flatpak/io.github.spacefreak18.simd.yml | 13 +- {packaging => tools/distro}/simd.service | 0 tools/distro/simd.svg | 14 + 10 files changed, 347 insertions(+), 119 deletions(-) create mode 100644 .github/workflows/packages.yml delete mode 100755 packaging/debian/build-deb.sh delete mode 100644 packaging/fedora/simd.spec rename {packaging => tools/distro}/flatpak/io.github.spacefreak18.simd.desktop (100%) rename {packaging => tools/distro}/flatpak/io.github.spacefreak18.simd.metainfo.xml (100%) rename {packaging => tools/distro}/flatpak/io.github.spacefreak18.simd.yml (94%) rename {packaging => tools/distro}/simd.service (100%) create mode 100644 tools/distro/simd.svg diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml new file mode 100644 index 0000000..d1b910d --- /dev/null +++ b/.github/workflows/packages.yml @@ -0,0 +1,287 @@ +# Builds simd and libsimapi as .deb, .rpm, AppImage and Flatpak, and attaches +# them to a release when the run is for a tag. +# +# Built in containers on ordinary GitHub-hosted runners rather than against +# self-hosted runner labels, so nothing queues waiting for a machine that may +# not be online. +name: Packages + +on: + push: + tags: [ "*" ] + pull_request: + workflow_dispatch: {} + +jobs: + debs: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + image: ubuntu:latest + - os: debian-latest + image: debian:testing + - os: debian-stable + image: debian:stable-slim + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + permissions: + contents: write + steps: + # Before checkout: these images ship no git, so actions/checkout would + # silently fall back to a tarball download. + - name: Install build dependencies + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates git build-essential cmake pkg-config dpkg-dev \ + libuv1-dev libargtable2-dev libconfig-dev libyder-dev \ + libxdg-basedir-dev libprocps-dev || \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates git build-essential cmake pkg-config dpkg-dev \ + libuv1-dev libargtable2-dev libconfig-dev libyder-dev \ + libxdg-basedir-dev libproc2-dev + + - uses: actions/checkout@v4 + + - name: Determine package version + id: pkgver + shell: bash + run: | + # The control files carry a hardcoded `Version: 1` that nobody + # maintains. A Debian version must start with a digit, so a branch + # build or a non-release tag falls back to 0.0.0 rather than making + # dpkg-deb fail outright. + v="" + [ "$GITHUB_REF_TYPE" = tag ] && v="${GITHUB_REF_NAME#v}" + case "$v" in [0-9]*) ;; *) v="0.0.0" ;; esac + echo "version=$v" >> "$GITHUB_OUTPUT" + echo "packaging as version $v" + + - name: Build + run: | + # SYSTEMD_USER_UNIT_DIR and SIMD_CONFIG_DIR default under $ENV{HOME}; + # a package must never write into a user's home. Both are already + # CACHE PATH variables upstream, so redirecting needs no patch. + cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_SIMD=ON \ + -DSYSTEMD_USER_UNIT_DIR=/usr/lib/systemd/user \ + -DSIMD_CONFIG_DIR=/usr/share/simd + cmake --build build -j"$(nproc)" + + - name: Assemble the two packages + shell: bash + run: | + set -eux + ver='${{ steps.pkgver.outputs.version }}' + ctl=tools/distro/debian/dpkg/${{ matrix.os }} + + # libsimapi: the shared library and its headers. Kept a separate + # package because simd is not its only possible consumer. + rm -rf lib_root && mkdir -p lib_root/DEBIAN lib_root/usr/lib lib_root/usr/include lib_root/usr/share/pkgconfig + cp -P build/libsimapi.so* lib_root/usr/lib/ + cp simapi/simmapper.h simapi/simapi.h simapi/simdata.h lib_root/usr/include/ + cp build/simapi.pc lib_root/usr/share/pkgconfig/ + sed "s/^Version:.*/Version: $ver/" "$ctl/simapi_control" > lib_root/DEBIAN/control + dpkg-deb --build lib_root "libsimapi-${{ matrix.os }}.deb" + + # simd: the daemon, plus the packaged unit and an example config. + rm -rf simd_root && mkdir -p simd_root/DEBIAN simd_root/usr/bin + cp build/simd/simd simd_root/usr/bin/simd + install -Dm644 tools/distro/simd.service simd_root/usr/lib/systemd/user/simd.service + install -Dm644 simd/conf/simd.config simd_root/usr/share/simd/simd.config + sed "s/^Version:.*/Version: $ver/" "$ctl/simd_control" > simd_root/DEBIAN/control + dpkg-deb --build simd_root "simd-${{ matrix.os }}.deb" + + echo "--- control as packaged ---" + dpkg-deb --field "simd-${{ matrix.os }}.deb" Package Version Depends + + - uses: actions/upload-artifact@v4 + with: + name: debs-${{ matrix.os }} + path: "*.deb" + + - name: Release the packages + if: github.ref_type == 'tag' + uses: softprops/action-gh-release@v1 + with: + files: "*.deb" + + rpms: + strategy: + fail-fast: false + matrix: + os: [fedora-43, fedora-44] + include: + - os: fedora-43 + image: fedora:43 + - os: fedora-44 + image: fedora:44 + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + permissions: + contents: write + steps: + - name: Install build dependencies + run: | + dnf install -y --setopt=install_weak_deps=False \ + git rpm-build rpmdevtools gcc cmake pkgconf-pkg-config \ + libuv-devel argtable-devel libconfig-devel yder-devel \ + libxdg-basedir-devel procps-ng-devel + + - uses: actions/checkout@v4 + + - name: Determine package version + id: pkgver + shell: bash + run: | + v="" + [ "$GITHUB_REF_TYPE" = tag ] && v="${GITHUB_REF_NAME#v}" + case "$v" in [0-9]*) ;; *) v="0.0.0" ;; esac + echo "version=$v" >> "$GITHUB_OUTPUT" + + - name: Build both rpms + shell: bash + run: | + set -eux + rpmdev-setuptree + # Stage this checkout as the source, so an rpm's contents are the + # commit it was built from rather than whatever the default branch + # holds -- see the %prep comment in each spec. + rm -rf ~/rpmbuild/SOURCES/simapi + cp -r "$GITHUB_WORKSPACE" ~/rpmbuild/SOURCES/simapi + rm -rf ~/rpmbuild/SOURCES/simapi/.github + + # The specs' hardcoded `Version: 0.0.5` is stale; stamped from the + # tag here so the file stays a working default for a local rpmbuild. + for spec in simapi simd; do + rpmbuild -ba --define "_version ${{ steps.pkgver.outputs.version }}" \ + --define "version ${{ steps.pkgver.outputs.version }}" \ + tools/distro/fedora/rpm/$spec.spec + done + + # *-[0-9]*, not *: rpmbuild also emits debuginfo and debugsource. + cp ~/rpmbuild/RPMS/x86_64/simd-[0-9]*.rpm "simd-${{ matrix.os }}.rpm" + cp ~/rpmbuild/RPMS/x86_64/libsimapi-[0-9]*.rpm "libsimapi-${{ matrix.os }}.rpm" + rpm -qp --requires "simd-${{ matrix.os }}.rpm" + + - uses: actions/upload-artifact@v4 + with: + name: rpms-${{ matrix.os }} + path: "*.rpm" + + - name: Release the packages + if: github.ref_type == 'tag' + uses: softprops/action-gh-release@v1 + with: + files: "*.rpm" + + appimage: + # Built on an older base on purpose: an AppImage is only as portable as + # the oldest glibc it links against. + runs-on: ubuntu-22.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Install build dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential cmake pkg-config file wget \ + libuv1-dev libargtable2-dev libconfig-dev libyder-dev \ + libxdg-basedir-dev libprocps-dev + + - name: Determine version + id: pkgver + shell: bash + run: | + v="" + [ "$GITHUB_REF_TYPE" = tag ] && v="${GITHUB_REF_NAME#v}" + case "$v" in [0-9]*) ;; *) v="0.0.0" ;; esac + echo "version=$v" >> "$GITHUB_OUTPUT" + + - name: Build and stage an AppDir + run: | + set -eux + cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \ + -DBUILD_SIMD=ON \ + -DSYSTEMD_USER_UNIT_DIR=/usr/lib/systemd/user \ + -DSIMD_CONFIG_DIR=/usr/share/simd + cmake --build build -j"$(nproc)" + DESTDIR="$PWD/AppDir" cmake --install build + # An AppImage cannot install a systemd unit onto the host, and the + # daemon is started directly rather than supervised, so it carries + # the example config but not the unit. + rm -f AppDir/usr/lib/systemd/user/simd.service + install -Dm644 tools/distro/flatpak/io.github.spacefreak18.simd.desktop \ + AppDir/usr/share/applications/io.github.spacefreak18.simd.desktop + install -Dm644 LICENSE.rst AppDir/usr/share/doc/simd/LICENSE.rst + + - name: Package the AppImage + env: + # These tools are themselves AppImages and there is no FUSE on the + # runner to mount them with. + APPIMAGE_EXTRACT_AND_RUN: 1 + run: | + set -eux + wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage + chmod +x linuxdeploy-x86_64.AppImage + export OUTPUT="simd-${{ steps.pkgver.outputs.version }}-x86_64.AppImage" + ./linuxdeploy-x86_64.AppImage --appdir AppDir \ + --desktop-file AppDir/usr/share/applications/io.github.spacefreak18.simd.desktop \ + --icon-file tools/distro/simd.svg \ + --output appimage + + - uses: actions/upload-artifact@v4 + with: + name: appimage + path: "simd-*-x86_64.AppImage" + + - name: Release the AppImage + if: github.ref_type == 'tag' + uses: softprops/action-gh-release@v1 + with: + files: "simd-*-x86_64.AppImage" + + flatpak: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Install flatpak-builder + run: | + sudo apt-get update + # Recommends left enabled deliberately: flatpak-builder shells out to + # tar's decompressors, eu-strip and patch, which Debian and Ubuntu + # list as Recommends rather than Depends. With --no-install-recommends + # it installs, builds several modules, then fails on whichever helper + # the next module needs. + sudo apt-get install -y flatpak flatpak-builder xz-utils bzip2 elfutils + flatpak remote-add --if-not-exists --user flathub https://flathub.org/repo/flathub.flatpakrepo + flatpak install -y --user --noninteractive flathub \ + org.freedesktop.Platform//25.08 org.freedesktop.Sdk//25.08 + + - name: Build the bundle + working-directory: tools/distro/flatpak + run: | + flatpak-builder --user --disable-rofiles-fuse --force-clean --repo=repo \ + build-dir io.github.spacefreak18.simd.yml + flatpak build-bundle repo simd.flatpak io.github.spacefreak18.simd + + - uses: actions/upload-artifact@v4 + with: + name: simd.flatpak + path: tools/distro/flatpak/simd.flatpak + + - name: Release the bundle + if: github.ref_type == 'tag' + uses: softprops/action-gh-release@v1 + with: + files: tools/distro/flatpak/simd.flatpak diff --git a/packaging/debian/build-deb.sh b/packaging/debian/build-deb.sh deleted file mode 100755 index 9003962..0000000 --- a/packaging/debian/build-deb.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -# Builds a .deb from an already-configured build tree. -# -# build-deb.sh -# -# Dependencies are computed by dpkg-shlibdeps from the built binaries rather -# than maintained by hand: library package names drift between releases, and a -# stale hand-written list still builds fine, failing only at install time on -# someone else's machine. -set -euo pipefail - -builddir=${1:?build dir} -version=${2:?version} -output=${3:?output .deb} - -pkgroot=$(mktemp -d) -trap 'rm -rf "$pkgroot"' EXIT - -DESTDIR="$pkgroot" cmake --install "$builddir" >/dev/null - -# Upstream's CMake installs its systemd unit from simd/conf/simd.service, which -# hardcodes ExecStart=%h/.local/bin/simd and Type=simple against a daemon that -# double-forks. Replace it with the packaged one: /usr/bin/simd -n, supervised -# by systemd rather than by a fork it cannot see. -install -Dm644 packaging/simd.service "$pkgroot/usr/lib/systemd/user/simd.service" - -mkdir -p "$pkgroot/DEBIAN" -cat > "$pkgroot/DEBIAN/control" < -Description: SimAPI telemetry daemon for racing simulators - simd watches for a running racing simulator, maps its telemetry, and - republishes it as a universal shared memory map at /dev/shm/SIMAPI.DAT - for other applications to read. - . - Ships libsimapi alongside it, which simd links at runtime. -CTRL - -# shlibdeps needs the shipped library on its search path to resolve simd's own -# dependency on it; without -l it reports libsimapi.so.1 as an unknown symbol -# source and refuses to emit a Depends line at all. -( cd "$pkgroot" && dpkg-shlibdeps -l"$pkgroot/usr/lib" \ - -O usr/bin/simd usr/lib/libsimapi.so.1.0.1 ) > "$pkgroot/shlibdeps.txt" -deps=$(sed -e 's/^shlibs:Depends=//' "$pkgroot/shlibdeps.txt") -rm -f "$pkgroot/shlibdeps.txt" -echo "Depends: $deps" >> "$pkgroot/DEBIAN/control" - -echo "--- control ---" -cat "$pkgroot/DEBIAN/control" - -dpkg-deb --build "$pkgroot" "$output" diff --git a/packaging/fedora/simd.spec b/packaging/fedora/simd.spec deleted file mode 100644 index 73f87ab..0000000 --- a/packaging/fedora/simd.spec +++ /dev/null @@ -1,50 +0,0 @@ -Name: simd -Version: %{?_version}%{!?_version:0.0.0} -Release: 1%{?dist} -Summary: SimAPI telemetry daemon for racing simulators -License: LGPL-3.0-or-later -URL: https://github.com/Spacefreak18/simapi - -BuildRequires: gcc cmake pkgconfig -BuildRequires: libuv-devel yder-devel argtable-devel libconfig-devel procps-ng-devel - -%description -simd watches for a running racing simulator, maps its telemetry, and -republishes it as a universal shared memory map at /dev/shm/SIMAPI.DAT for -other applications to read. Ships libsimapi alongside it, which simd links -at runtime. - -# Builds whatever tree has been staged at %{_sourcedir}/simapi. CI stages the -# checked-out tree so an rpm's contents are the commit it was built from, -# rather than whatever the default branch happened to be at build time. -%prep -rm -rf %{_builddir}/simapi -cp -r %{_sourcedir}/simapi %{_builddir}/ - -%build -cd %{_builddir}/simapi -# The two *_DIR variables are upstream cache paths that default under -# $ENV{HOME} -- fine for a developer's own install, but a package must never -# write into a user's home. Redirected here rather than patched upstream. -cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \ - -DCMAKE_INSTALL_LIBDIR=%{_lib} \ - -DSYSTEMD_USER_UNIT_DIR=/usr/lib/systemd/user \ - -DSIMD_CONFIG_DIR=/usr/share/simd -cmake --build build -j$(nproc) - -%install -cd %{_builddir}/simapi -DESTDIR=%{buildroot} cmake --install build -# Upstream's unit hardcodes ExecStart=%%h/.local/bin/simd with Type=simple -# against a daemon that double-forks; the packaged one runs /usr/bin/simd -n -# under systemd's own supervision. -install -Dm644 packaging/simd.service %{buildroot}/usr/lib/systemd/user/simd.service - -%files -/usr/bin/simd -/usr/%{_lib}/libsimapi.so* -/usr/lib/systemd/user/simd.service -/usr/share/simd/simd.config -/usr/include/*.h -/usr/share/pkgconfig/simapi.pc -%license LICENSE.rst diff --git a/tools/distro/fedora/rpm/simapi.spec b/tools/distro/fedora/rpm/simapi.spec index b48e972..3fdaeed 100644 --- a/tools/distro/fedora/rpm/simapi.spec +++ b/tools/distro/fedora/rpm/simapi.spec @@ -14,13 +14,17 @@ Requires: procps-ng %description Racing Simulator Telemetry Libraries +# Builds whatever tree has been staged at $RPM_SOURCE_DIR/simapi, cloning +# upstream only if nothing is staged. The unconditional clone this replaced +# meant an rpm's contents tracked the default branch rather than the tag being +# built, so a fix on the branch being released was absent from its own release. +# CI stages the checked-out tree; a bare `rpmbuild -ba` still works as before. %prep rm -rf $RPM_BUILD_DIR/simapi -rm -rf $RPM_SOURCE_DIR/simapi -cd $RPM_SOURCE_DIR -git clone https://github.com/spacefreak18/simapi -cd simapi -cd .. +if [ ! -d $RPM_SOURCE_DIR/simapi ]; then + cd $RPM_SOURCE_DIR + git clone https://github.com/spacefreak18/simapi +fi cp -r $RPM_SOURCE_DIR/simapi $RPM_BUILD_DIR/ %build diff --git a/tools/distro/fedora/rpm/simd.spec b/tools/distro/fedora/rpm/simd.spec index a52289a..45f3667 100644 --- a/tools/distro/fedora/rpm/simd.spec +++ b/tools/distro/fedora/rpm/simd.spec @@ -16,24 +16,45 @@ Requires: argtable libconfig libuv libsimapi libyder %description Racing Simulator Telemetry Libraries +# Builds whatever tree has been staged at $RPM_SOURCE_DIR/simapi, cloning +# upstream only if nothing is staged. The unconditional clone this replaced +# meant an rpm's contents tracked the default branch rather than the tag being +# built, so a fix on the branch being released was absent from its own release. +# CI stages the checked-out tree; a bare `rpmbuild -ba` still works as before. %prep rm -rf $RPM_BUILD_DIR/simapi -rm -rf $RPM_SOURCE_DIR/simapi -cd $RPM_SOURCE_DIR -git clone https://github.com/spacefreak18/simapi -cd simapi -cd .. +if [ ! -d $RPM_SOURCE_DIR/simapi ]; then + cd $RPM_SOURCE_DIR + git clone https://github.com/spacefreak18/simapi +fi cp -r $RPM_SOURCE_DIR/simapi $RPM_BUILD_DIR/ %build cd $RPM_BUILD_DIR/simapi -cmake -B build -DBUILD_SIMD=on +# The two *_DIR variables default under $ENV{HOME}; a package must never +# write into a user's home. Redirected rather than patched upstream, since +# both are already CACHE PATH variables. +cmake -B build -DBUILD_SIMD=on -DCMAKE_INSTALL_PREFIX=/usr \ + -DSYSTEMD_USER_UNIT_DIR=/usr/lib/systemd/user \ + -DSIMD_CONFIG_DIR=/usr/share/simd cd build make %install mkdir -p $RPM_BUILD_ROOT/usr/bin cp $RPM_BUILD_DIR/simapi/build/simd/simd $RPM_BUILD_ROOT/usr/bin/simd +# Upstream's simd/conf/simd.service sets Type=simple against a daemon that +# double-forks -- systemd takes the parent's exit as the service failing while +# the real daemon carries on unsupervised -- and hardcodes +# ExecStart=%%h/.local/bin/simd. The packaged unit runs /usr/bin/simd -n. +install -Dm644 $RPM_BUILD_DIR/simapi/tools/distro/simd.service \ + $RPM_BUILD_ROOT/usr/lib/systemd/user/simd.service +# An example, not a live config: packages must not write into $HOME, which is +# where simd looks for it (built from getpwuid(), not $HOME or XDG_CONFIG_HOME). +install -Dm644 $RPM_BUILD_DIR/simapi/simd/conf/simd.config \ + $RPM_BUILD_ROOT/usr/share/simd/simd.config %files /usr/bin/simd +/usr/lib/systemd/user/simd.service +/usr/share/simd/simd.config diff --git a/packaging/flatpak/io.github.spacefreak18.simd.desktop b/tools/distro/flatpak/io.github.spacefreak18.simd.desktop similarity index 100% rename from packaging/flatpak/io.github.spacefreak18.simd.desktop rename to tools/distro/flatpak/io.github.spacefreak18.simd.desktop diff --git a/packaging/flatpak/io.github.spacefreak18.simd.metainfo.xml b/tools/distro/flatpak/io.github.spacefreak18.simd.metainfo.xml similarity index 100% rename from packaging/flatpak/io.github.spacefreak18.simd.metainfo.xml rename to tools/distro/flatpak/io.github.spacefreak18.simd.metainfo.xml diff --git a/packaging/flatpak/io.github.spacefreak18.simd.yml b/tools/distro/flatpak/io.github.spacefreak18.simd.yml similarity index 94% rename from packaging/flatpak/io.github.spacefreak18.simd.yml rename to tools/distro/flatpak/io.github.spacefreak18.simd.yml index 8a5bfda..01abe7d 100644 --- a/packaging/flatpak/io.github.spacefreak18.simd.yml +++ b/tools/distro/flatpak/io.github.spacefreak18.simd.yml @@ -198,8 +198,15 @@ modules: LIBRARY_PATH: /app/lib post-install: - install -Dm644 LICENSE.rst /app/share/licenses/simd/LICENSE.rst - - install -Dm644 packaging/flatpak/io.github.spacefreak18.simd.desktop /app/share/applications/io.github.spacefreak18.simd.desktop - - install -Dm644 packaging/flatpak/io.github.spacefreak18.simd.metainfo.xml /app/share/metainfo/io.github.spacefreak18.simd.metainfo.xml + - install -Dm644 tools/distro/flatpak/io.github.spacefreak18.simd.desktop /app/share/applications/io.github.spacefreak18.simd.desktop + - install -Dm644 tools/distro/flatpak/io.github.spacefreak18.simd.metainfo.xml /app/share/metainfo/io.github.spacefreak18.simd.metainfo.xml sources: - type: dir - path: ../.. + path: ../../.. + # Local build trees are not part of the source. Without these a + # developer's build/ and stage/ get copied into the sandbox, where a + # stale CMakeCache.txt pointing at host paths breaks the configure + # step in ways that look nothing like their cause. + skip: + - build + - stage diff --git a/packaging/simd.service b/tools/distro/simd.service similarity index 100% rename from packaging/simd.service rename to tools/distro/simd.service diff --git a/tools/distro/simd.svg b/tools/distro/simd.svg new file mode 100644 index 0000000..1cb4c3b --- /dev/null +++ b/tools/distro/simd.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + From fbadf4b13698a99ac57ef01a8ff2da2b7dd590ce Mon Sep 17 00:00:00 2001 From: MrDavid5465 Date: Thu, 3 Sep 2026 18:30:39 -0400 Subject: [PATCH 5/9] fix(ci): repair the rpm, AppImage and Flatpak legs Three unrelated failures from the first run; the deb legs passed unchanged. rpm: Fedora packages neither yder nor orcania -- `dnf install yder-devel` fails with "No match for argument" on 43 and 44, while Debian and Ubuntu ship libyder-dev. Both are now vendored and built as static libraries, so simd links them without leaving a runtime dependency Fedora cannot satisfy and the spec's Requires: stays as upstream wrote it. AppImage: ubuntu-22.04 cannot build this at all. It ships libprocps rather than libproc2, so getpid.c fails on a missing , and its GCC predates the C23 fixed-underlying-type enums in include/F12018/*.h. Moved to 24.04, which is now the oldest base that works. Flatpak: orcania and yder both installed to /app/lib64, so when yder configured, pkg-config did not find liborcania.pc under /app/lib/pkgconfig and yder linked without orcania. That surfaced much later and looked like something else entirely -- simd failing to link with undefined references to o_malloc, o_free, o_strdup and split_string attributed to libyder.so. Both modules now install to /app/lib. Co-Authored-By: Claude Opus 5 --- .github/workflows/packages.yml | 55 +++++++++++++++++-- .../flatpak/io.github.spacefreak18.simd.yml | 14 +++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index d1b910d..d2ced5b 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -129,9 +129,51 @@ jobs: run: | dnf install -y --setopt=install_weak_deps=False \ git rpm-build rpmdevtools gcc cmake pkgconf-pkg-config \ - libuv-devel argtable-devel libconfig-devel yder-devel \ + libuv-devel argtable-devel libconfig-devel \ libxdg-basedir-devel procps-ng-devel + # yder, and orcania beneath it, are what simd logs through, and Fedora + # packages neither -- `dnf install yder-devel` fails outright with "No + # match for argument" on both 43 and 44, while Debian and Ubuntu ship + # libyder-dev. Built here as *static* libraries on purpose: linked into + # simd they leave the rpm with no runtime dependency Fedora cannot + # satisfy, so the spec's Requires: stays as upstream wrote it. + - name: Build vendored orcania and yder (static) + run: | + set -eux + # BUILD_SHARED=OFF as well as BUILD_STATIC=ON: both default to + # shared-only, and a libyder.so left in the prefix is picked over the + # archive, putting back a runtime dependency no Fedora repo can meet. + # + # -Wno-error via CMAKE_C_FLAGS_RELEASE, not CMAKE_C_FLAGS: both + # projects append their own -Wall -Werror to CMAKE_C_FLAGS, so + # setting it is overridden. The _RELEASE flags land after it, where + # the last of -Werror/-Wno-error wins. orcania 2.3.3 assigns the + # result of strstr() on a const pointer to a char*, which current + # GCC rejects as -Werror=discarded-qualifiers. + cflags='-O2 -DNDEBUG -Wno-error' + + git clone --depth 1 --branch v2.3.3 https://github.com/babelouest/orcania /tmp/orcania + cmake -S /tmp/orcania -B /tmp/orcania/build \ + -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_FLAGS_RELEASE="$cflags" \ + -DBUILD_SHARED=OFF -DBUILD_STATIC=ON -DBUILD_ORCANIA_TESTING=OFF + cmake --build /tmp/orcania/build --target install + + git clone --depth 1 --branch v1.4.20 https://github.com/babelouest/yder /tmp/yder + # Journald would pull in systemd-devel for a backend simd never + # selects; its config offers file and syslog. + cmake -S /tmp/yder -B /tmp/yder/build \ + -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_FLAGS_RELEASE="$cflags" \ + -DBUILD_SHARED=OFF -DBUILD_STATIC=ON -DWITH_JOURNALD=OFF \ + -DBUILD_YDER_TESTING=OFF + cmake --build /tmp/yder/build --target install + + # Fail here rather than shipping a broken rpm: nothing later in this + # job would notice a dynamically linked simd. + test -f /usr/lib64/libyder.a || test -f /usr/lib/libyder.a + - uses: actions/checkout@v4 - name: Determine package version @@ -180,9 +222,12 @@ jobs: files: "*.rpm" appimage: - # Built on an older base on purpose: an AppImage is only as portable as - # the oldest glibc it links against. - runs-on: ubuntu-22.04 + # Oldest base that actually builds this, since an AppImage is only as + # portable as the oldest glibc it links against. 22.04 does not: it ships + # libprocps rather than libproc2, so getpid.c fails on a missing + # , and its GCC predates the C23 fixed-underlying-type + # enums in include/F12018/*.h ("expected identifier or '(' before ':'"). + runs-on: ubuntu-24.04 permissions: contents: write steps: @@ -194,7 +239,7 @@ jobs: sudo apt-get install -y --no-install-recommends \ build-essential cmake pkg-config file wget \ libuv1-dev libargtable2-dev libconfig-dev libyder-dev \ - libxdg-basedir-dev libprocps-dev + libxdg-basedir-dev libproc2-dev - name: Determine version id: pkgver diff --git a/tools/distro/flatpak/io.github.spacefreak18.simd.yml b/tools/distro/flatpak/io.github.spacefreak18.simd.yml index 01abe7d..d0c6980 100644 --- a/tools/distro/flatpak/io.github.spacefreak18.simd.yml +++ b/tools/distro/flatpak/io.github.spacefreak18.simd.yml @@ -150,6 +150,13 @@ modules: - name: orcania buildsystem: cmake-ninja config-opts: + # Everything into /app/lib, not the lib64 CMake picks by default here: + # orcania and yder both landed in /app/lib64, so when yder configured, + # pkg-config did not find liborcania.pc under /app/lib/pkgconfig and + # yder linked without it. The failure surfaced much later and looked + # unrelated -- simd failed to link with undefined references to + # o_malloc/o_free/o_strdup/split_string coming out of libyder.so. + - -DCMAKE_INSTALL_LIBDIR=lib - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_C_FLAGS_RELEASE=-O2 -DNDEBUG -Wno-error - -DBUILD_ORCANIA_TESTING=OFF @@ -163,6 +170,13 @@ modules: - name: yder buildsystem: cmake-ninja config-opts: + # Everything into /app/lib, not the lib64 CMake picks by default here: + # orcania and yder both landed in /app/lib64, so when yder configured, + # pkg-config did not find liborcania.pc under /app/lib/pkgconfig and + # yder linked without it. The failure surfaced much later and looked + # unrelated -- simd failed to link with undefined references to + # o_malloc/o_free/o_strdup/split_string coming out of libyder.so. + - -DCMAKE_INSTALL_LIBDIR=lib - -DCMAKE_BUILD_TYPE=Release - -DCMAKE_C_FLAGS_RELEASE=-O2 -DNDEBUG -Wno-error # Journald would need systemd-devel for a backend simd never selects; From 1be95bc6a05100f7ec4c5aaa2e370fe40b735039 Mon Sep 17 00:00:00 2001 From: MrDavid5465 Date: Thu, 3 Sep 2026 18:34:05 -0400 Subject: [PATCH 6/9] fix(ci): install the icon, and give each leg what it was missing Second round; the deb legs stayed green throughout and the previous fixes all held -- each of these is a new failure further along. rpm: CMAKE_CXX_COMPILER not set. simd/CMakeLists.txt uses CheckCXXCompilerFlag, which enables the CXX language, and Fedora's `gcc` package does not provide a C++ compiler. Added gcc-c++. AppImage: "Could not find dependency: libsimapi.so.1". simd links a library that exists only inside the AppDir, so linuxdeploy's ldd-based resolution could not find it. LD_LIBRARY_PATH now points at the staged AppDir. Flatpak: appstreamcli compose failed. The desktop entry named an icon that was never installed, so the component referenced an icon that did not exist, and NoDisplay=true would have had compose discard the component anyway, leaving the bundle with no metadata. The icon is now installed and the entry is listed -- which also suits how this is meant to be started, since a desktop's "startup applications" picker will not offer a NoDisplay entry. Co-Authored-By: Claude Opus 5 --- .github/workflows/packages.yml | 6 +++++- tools/distro/flatpak/io.github.spacefreak18.simd.desktop | 8 ++++---- tools/distro/flatpak/io.github.spacefreak18.simd.yml | 3 +++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index d2ced5b..a96c765 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -128,7 +128,7 @@ jobs: - name: Install build dependencies run: | dnf install -y --setopt=install_weak_deps=False \ - git rpm-build rpmdevtools gcc cmake pkgconf-pkg-config \ + git rpm-build rpmdevtools gcc gcc-c++ cmake pkgconf-pkg-config \ libuv-devel argtable-devel libconfig-devel \ libxdg-basedir-devel procps-ng-devel @@ -277,6 +277,10 @@ jobs: wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage chmod +x linuxdeploy-x86_64.AppImage export OUTPUT="simd-${{ steps.pkgver.outputs.version }}-x86_64.AppImage" + # simd links libsimapi.so.1, which is inside the AppDir and nowhere + # on the system, so linuxdeploy's ldd-based resolution reported + # "Could not find dependency: libsimapi.so.1" and refused to deploy. + export LD_LIBRARY_PATH="$PWD/AppDir/usr/lib:${LD_LIBRARY_PATH:-}" ./linuxdeploy-x86_64.AppImage --appdir AppDir \ --desktop-file AppDir/usr/share/applications/io.github.spacefreak18.simd.desktop \ --icon-file tools/distro/simd.svg \ diff --git a/tools/distro/flatpak/io.github.spacefreak18.simd.desktop b/tools/distro/flatpak/io.github.spacefreak18.simd.desktop index d572cc4..d60d85b 100644 --- a/tools/distro/flatpak/io.github.spacefreak18.simd.desktop +++ b/tools/distro/flatpak/io.github.spacefreak18.simd.desktop @@ -6,10 +6,10 @@ Icon=io.github.spacefreak18.simd Terminal=false Type=Application Categories=Game;Utility; -# Not shown in menus: simd has no user interface. The entry exists so desktop -# "startup applications" tools can find it, and so an autostart entry can -# reference it by app id. -NoDisplay=true +# Deliberately listed rather than NoDisplay=true. simd has no window, but the +# entry is how a desktop's "startup applications" picker finds it, which is +# how this is meant to be started -- and appstreamcli compose discards a +# NoDisplay component, leaving the Flatpak with no metadata at all. # -n (--nodaemon) above is deliberate. simd double-forks by default, and under # `flatpak run` the sandbox is torn down when the process it started exits -- # taking the daemon with it. Running in the foreground keeps flatpak's own diff --git a/tools/distro/flatpak/io.github.spacefreak18.simd.yml b/tools/distro/flatpak/io.github.spacefreak18.simd.yml index d0c6980..1bb4ea0 100644 --- a/tools/distro/flatpak/io.github.spacefreak18.simd.yml +++ b/tools/distro/flatpak/io.github.spacefreak18.simd.yml @@ -214,6 +214,9 @@ modules: - install -Dm644 LICENSE.rst /app/share/licenses/simd/LICENSE.rst - install -Dm644 tools/distro/flatpak/io.github.spacefreak18.simd.desktop /app/share/applications/io.github.spacefreak18.simd.desktop - install -Dm644 tools/distro/flatpak/io.github.spacefreak18.simd.metainfo.xml /app/share/metainfo/io.github.spacefreak18.simd.metainfo.xml + # The desktop entry names this icon; without it installed, appstream + # has a component referencing an icon that does not exist. + - install -Dm644 tools/distro/simd.svg /app/share/icons/hicolor/scalable/apps/io.github.spacefreak18.simd.svg sources: - type: dir path: ../../.. From 8eac900e4d4f76130cb7d132de937d8796fd82e0 Mon Sep 17 00:00:00 2001 From: MrDavid5465 Date: Thu, 3 Sep 2026 18:38:57 -0400 Subject: [PATCH 7/9] fix: link orcania explicitly, and stop guessing the library directory Two of the three remaining failures were the same mistake made twice, so they are fixed generally rather than per-leg. CMake's GNUInstallDirs picks a different library directory on every platform -- lib64 in the freedesktop runtime, lib/x86_64-linux-gnu on Ubuntu -- and both guesses were already wrong once. In the Flatpak, orcania and yder landed in lib64 where yder's own configure could not find liborcania.pc; in the AppImage, libsimapi landed under the multiarch triplet while linuxdeploy looked in AppDir/usr/lib. CMAKE_INSTALL_LIBDIR is now pinned to lib wherever this project controls the build. simd links yder, and yder calls into orcania. A shared libyder.so records that dependency, which is why Debian and Ubuntu never needed anything: their link just follows it. A static libyder.a records nothing, so o_malloc, o_free, o_strdup, o_strnullempty and split_string all come back undefined -- which is what the Fedora rpm hit, since Fedora packages neither library and the job vendors both as static archives. simd/CMakeLists.txt now names orcania explicitly when it is present, after yder, as a static link needs. appstreamcli compose was failing on the metainfo: no developer element, a description opening on a lowercase word, and an icon that existed only as SVG. A 128x128 raster is now installed alongside it. Verified with appstreamcli validate in the freedesktop SDK -- the only remaining finding is url-not-reachable, which is the sandbox having no network. Co-Authored-By: Claude Opus 5 --- .github/workflows/packages.yml | 6 +++++- simd/CMakeLists.txt | 13 +++++++++++++ .../io.github.spacefreak18.simd.metainfo.xml | 9 ++++++--- .../flatpak/io.github.spacefreak18.simd.yml | 7 +++++++ tools/distro/simd-128.png | Bin 0 -> 3172 bytes 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 tools/distro/simd-128.png diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index a96c765..3641279 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -253,8 +253,12 @@ jobs: - name: Build and stage an AppDir run: | set -eux + # CMAKE_INSTALL_LIBDIR pinned: GNUInstallDirs picks the multiarch + # triplet on Ubuntu, so the library landed in + # AppDir/usr/lib/x86_64-linux-gnu while everything downstream looked + # in AppDir/usr/lib. cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \ - -DBUILD_SIMD=ON \ + -DBUILD_SIMD=ON -DCMAKE_INSTALL_LIBDIR=lib \ -DSYSTEMD_USER_UNIT_DIR=/usr/lib/systemd/user \ -DSIMD_CONFIG_DIR=/usr/share/simd cmake --build build -j"$(nproc)" diff --git a/simd/CMakeLists.txt b/simd/CMakeLists.txt index 2036145..a31f0e6 100644 --- a/simd/CMakeLists.txt +++ b/simd/CMakeLists.txt @@ -32,6 +32,19 @@ endif() add_executable(simd simd.c parameters.c confighelper.c dirhelper.c poke.c ../simmap/mapsimdata.c) target_link_libraries(simd m uv yder ${ARGTABLE_LIBS} config simapi) +# yder calls into orcania -- o_malloc, o_free, o_strdup, o_strnullempty, +# split_string. A shared libyder.so records that dependency and the linker +# follows it, which is why this is not needed on Debian or Ubuntu. A static +# libyder.a records nothing, so all of those come back undefined unless +# orcania is named explicitly, and after yder, which is the order a static +# link needs. Distributions without a yder package -- Fedora among them -- +# have to vendor it, and vendoring it static is what keeps the resulting +# package free of a dependency those distributions cannot satisfy. +find_library(ORCANIA_LIBRARY orcania) +if(ORCANIA_LIBRARY) + target_link_libraries(simd ${ORCANIA_LIBRARY}) +endif() + target_include_directories(simd PRIVATE ${ARGTABLE_INCLUDE_DIR}) # User-level installation diff --git a/tools/distro/flatpak/io.github.spacefreak18.simd.metainfo.xml b/tools/distro/flatpak/io.github.spacefreak18.simd.metainfo.xml index 7945fcf..fea625e 100644 --- a/tools/distro/flatpak/io.github.spacefreak18.simd.metainfo.xml +++ b/tools/distro/flatpak/io.github.spacefreak18.simd.metainfo.xml @@ -7,9 +7,9 @@ LGPL-3.0-or-later

- simd watches for a running racing simulator, maps its telemetry, and - republishes it as a universal shared memory map at /dev/shm/SIMAPI.DAT - for other applications to read. + SimAPI Daemon watches for a running racing simulator, maps its telemetry, + and republishes it as a universal shared memory map at + /dev/shm/SIMAPI.DAT for other applications to read.

This build can run commands on the host system. It has to: identifying a @@ -18,6 +18,9 @@ delivered through Flatpak, not as a sandboxed application.

+ + Paul Dino Jones + io.github.spacefreak18.simd.desktop https://github.com/Spacefreak18/simapi diff --git a/tools/distro/flatpak/io.github.spacefreak18.simd.yml b/tools/distro/flatpak/io.github.spacefreak18.simd.yml index 1bb4ea0..0a992c9 100644 --- a/tools/distro/flatpak/io.github.spacefreak18.simd.yml +++ b/tools/distro/flatpak/io.github.spacefreak18.simd.yml @@ -207,6 +207,10 @@ modules: # Flatpak anyway; autostart is a ~/.config/autostart entry instead. - -DINSTALL_SYSTEMD_SERVICE=off - -DSIMD_CONFIG_DIR=/app/share/simd + # Pinned rather than left to GNUInstallDirs, which picks lib64 here and + # the multiarch triplet on Debian. Every consumer of this prefix then + # has to guess which, and both guesses have already been wrong once. + - -DCMAKE_INSTALL_LIBDIR=lib build-options: env: LIBRARY_PATH: /app/lib @@ -217,6 +221,9 @@ modules: # The desktop entry names this icon; without it installed, appstream # has a component referencing an icon that does not exist. - install -Dm644 tools/distro/simd.svg /app/share/icons/hicolor/scalable/apps/io.github.spacefreak18.simd.svg + # A raster icon as well: appstreamcli compose wants a real 64x64-or- + # larger icon and will not rely on rasterising the SVG. + - install -Dm644 tools/distro/simd-128.png /app/share/icons/hicolor/128x128/apps/io.github.spacefreak18.simd.png sources: - type: dir path: ../../.. diff --git a/tools/distro/simd-128.png b/tools/distro/simd-128.png new file mode 100644 index 0000000000000000000000000000000000000000..0b5a5a93f1b548b131d2f60a67ae878c681ba160 GIT binary patch literal 3172 zcmV-q44dis8K~#90?VbH^Th$rIzvo_GzlFHYTSN2Gq)AHC4iZXR@uqIoVC_~- z12)FSgsMr?geKlL_7ChY09HT<@#B~_Hjo%dpdi#~gF=B0NGh79#HrIbP0}Qe>o&II z>+7>0+?&SswViuy-`Df`E%(KK;`6=FdCv2ka}R+^y1M!YvOGD$f!<3&1_AT{Xapdr z{VYaRhydaME)vjlS&)zUEJkK#CPQjIglz7zL$0o#$S)}H1c19$o669WPYB4%k?C*W z03cPmxS{~Mdi#G!v*b7kcGko^hEC84%8{opU6}l!+*Kt7(9zN1=LG+83jC(_#xeAQ z0X}F+l2kgcD1i3<{;jSIc^|+Xb+wM63lun=6>xNZa&ocoT#*2BjgS8VWS2hX zG4z2>NCG)nC<1Qd9~~VX{@jz#8sC19U7U#bdV71l8;|A*fD`=3!Q8%WA;5ky%fC=? zEJw$Vfx{$=5&g|#=nVlq8kr8g53GBDLoOoWB_oYu=m!OP4ge2a7eLqc$S>JA-%$*9 z^!EQ8xGn%n@Px7UF!Y8{@+1I|uCD$8%9C%6Hi)4+Gy}e$%kt!i(Uvd_fw)NaFvo%3 zYqTW{W1y5C0vrIh(N-`F!^2g+hGA!1 zMjN0v16~0SZt>wjgB#lf9sn>WO89&=jkjY7gw`?!Tc|IjvuA)BZG;{Ogy7lsM*KAB z2UlVqltd}siY4*S&R9u)P2VyaFED;!}O!&w@VmdF<~XZ7+ZaxA;o-`Q_jV!2?0{ z!m%2*7r=oA?P-oX8$30%#GKe(05@MB>gj#2Rj|DPstM#BFbJ@}02aiG%j4>DuT`+U z06t$$Yrgl?>RJsgF(1Ptfc{uU07lw(10bo zfCrlV>x~Zp+XNm*HB9|xSX2P_H+qYX?NhPvgK-1P382%>W4E_>Z;D~Rd$XG#Fg9R0 z0VqBF0MNv9=ycnE8h5R+o&W;M0sw%$KGqGW#Zm(3bn_TgaQ`yHezt9qS}Y}iQtn@7 zhjD@}C$P{ z;O*O`nBG@@U*YK=Pl<@6bFP0hS-k##sLGK*;e3N%w>IE+;T6n?_FFb)dJmw~|I6+l z$cZ6cu;8S9_YP`b@K8Y5{bWi2hH+ZwMemfp3zQ~AA1He@& zi*PzW4$x8ma;|1T3kF{?zVn+U|Ioe*8R(zQHTT7b^}W8e>^GTw-=&V6pKY* zhwi|X0#M`rWt0*^Y70Z9GLAE129HlGi@@sLfhh%0D)+DC{$;*W@BnHGLzP5lR}q*~ z0A+IjrHG`ZstQA!5uF`HU^;6n<^IcYCMn`jWB%0eVV_&EU{r`w(D~uD;_sA2@M=#p z+JyY)M-B|%SXl+tseF)-XjN+?O(%fOCV!L|O^7&D@FWlW1-u?Df8S;@jvFZ_f;NG} zlUw!Oi?f=wk*4bcD&_vmp|voiMReu51HIWM@Hi6Cwl>mK0#N%T7NWH{d%X|a7Vwy}~tr<^>NM$L!+>OYuiRW-*9|QIfZJD> z9hLmqh;0Rs0WhA*6x9WO-yT578^B*%`Vr@I=iUo+VRc>vmMJZOmx zrMvqEFtq0h31YLs9L0aS9~ z9!bQpHMUy#t;vK7JRR>gCaQcQ=={O#0)Oa&mtpMUjL|sCIPqv0sHRx9=u+^rZ25G;#vx4$G$== zy10IIv9BQ3I7kyb zUhEm#i(qqGO+U9*e0z2bmuAAM@3VXiAVVodcya<`r%%B9NOZ2LOC$PyaUn*=3+f44ojr*~s+d5CBQe1WJ^0PZ?7yACJFfNrAz0FmNh9Z znY|W`Cbrzox`7(Yx~zLg^C#?on|cLtiLBPEwM6=~6iKPe5^Hd>*PBvb|^54j0KD zrj#Bez-|CN02%>k+pU?Ql{A1j0j9ISIYP)s68Mq%@VT&>5BWb>pq>NH^Go6Y0000< KMNUMnLSTY&h|@0s literal 0 HcmV?d00001 From 2c8dc720e13676ee834bacaa51278a9d3d055f82 Mon Sep 17 00:00:00 2001 From: MrDavid5465 Date: Thu, 3 Sep 2026 18:43:25 -0400 Subject: [PATCH 8/9] fix(ci): stop shipping an SVG appstream cannot read, and three others The Flatpak's "appstreamcli compose failed" gave no detail in the build log, so this run was diagnosed by staging a prefix and running compose directly in the freedesktop SDK. It names the cause outright: E: file-read-error Unable to read data from file `/usr/share/icons/hicolor/scalable/apps/io.github.spacefreak18.simd.svg`: Unrecognized image file format appstreamcli in that runtime cannot decode the SVG at all, so installing it alongside the PNG was not a fallback, it was the failure. Removing it makes compose report Success against the same staged prefix. The bundle now ships the 128x128 raster only. AppImage: "Could not find suitable icon for Icon entry". linuxdeploy takes an icon's name from its filename, so simd-128.png was deployed as "simd-128" while the desktop entry names io.github.spacefreak18.simd. Copied to the matching filename before deploying. rpm: check-rpaths rejected /usr/bin/simd for a RUNPATH pointing into the build tree, which CMake bakes in because simd links libsimapi from there. The spec's %global __brp_check_rpaths %{nil} no longer suppresses that on Fedora 43/44, so CMAKE_SKIP_RPATH stops it being emitted instead. Nothing needs it: libsimapi installs to a standard system library directory. Co-Authored-By: Claude Opus 5 --- .github/workflows/packages.yml | 8 +++++++- tools/distro/fedora/rpm/simd.spec | 7 +++++++ tools/distro/flatpak/io.github.spacefreak18.simd.yml | 10 +++++----- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 3641279..3e96513 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -269,6 +269,12 @@ jobs: rm -f AppDir/usr/lib/systemd/user/simd.service install -Dm644 tools/distro/flatpak/io.github.spacefreak18.simd.desktop \ AppDir/usr/share/applications/io.github.spacefreak18.simd.desktop + # linuxdeploy takes the icon's *name* from its filename and matches + # it against the desktop file's Icon= entry, so simd-128.png would + # be deployed as "simd-128" and the entry naming + # io.github.spacefreak18.simd finds nothing: "Could not find + # suitable icon for Icon entry". + cp tools/distro/simd-128.png io.github.spacefreak18.simd.png install -Dm644 LICENSE.rst AppDir/usr/share/doc/simd/LICENSE.rst - name: Package the AppImage @@ -287,7 +293,7 @@ jobs: export LD_LIBRARY_PATH="$PWD/AppDir/usr/lib:${LD_LIBRARY_PATH:-}" ./linuxdeploy-x86_64.AppImage --appdir AppDir \ --desktop-file AppDir/usr/share/applications/io.github.spacefreak18.simd.desktop \ - --icon-file tools/distro/simd.svg \ + --icon-file io.github.spacefreak18.simd.png \ --output appimage - uses: actions/upload-artifact@v4 diff --git a/tools/distro/fedora/rpm/simd.spec b/tools/distro/fedora/rpm/simd.spec index 45f3667..62c9a8a 100644 --- a/tools/distro/fedora/rpm/simd.spec +++ b/tools/distro/fedora/rpm/simd.spec @@ -34,7 +34,14 @@ cd $RPM_BUILD_DIR/simapi # The two *_DIR variables default under $ENV{HOME}; a package must never # write into a user's home. Redirected rather than patched upstream, since # both are already CACHE PATH variables. +# CMAKE_SKIP_RPATH: simd links libsimapi out of the build tree, so CMake bakes +# that path in as a RUNPATH and rpm's check-rpaths rejects the resulting +# binary outright ("contains an invalid runpath ... /rpmbuild/BUILD/..."). The +# spec's %%global __brp_check_rpaths %%{nil} no longer suppresses that check on +# Fedora 43/44. Not emitting the RUNPATH is the deterministic fix, and nothing +# needs it: libsimapi installs to a standard system library directory. cmake -B build -DBUILD_SIMD=on -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_SKIP_RPATH=ON \ -DSYSTEMD_USER_UNIT_DIR=/usr/lib/systemd/user \ -DSIMD_CONFIG_DIR=/usr/share/simd cd build diff --git a/tools/distro/flatpak/io.github.spacefreak18.simd.yml b/tools/distro/flatpak/io.github.spacefreak18.simd.yml index 0a992c9..f2f737e 100644 --- a/tools/distro/flatpak/io.github.spacefreak18.simd.yml +++ b/tools/distro/flatpak/io.github.spacefreak18.simd.yml @@ -218,11 +218,11 @@ modules: - install -Dm644 LICENSE.rst /app/share/licenses/simd/LICENSE.rst - install -Dm644 tools/distro/flatpak/io.github.spacefreak18.simd.desktop /app/share/applications/io.github.spacefreak18.simd.desktop - install -Dm644 tools/distro/flatpak/io.github.spacefreak18.simd.metainfo.xml /app/share/metainfo/io.github.spacefreak18.simd.metainfo.xml - # The desktop entry names this icon; without it installed, appstream - # has a component referencing an icon that does not exist. - - install -Dm644 tools/distro/simd.svg /app/share/icons/hicolor/scalable/apps/io.github.spacefreak18.simd.svg - # A raster icon as well: appstreamcli compose wants a real 64x64-or- - # larger icon and will not rely on rasterising the SVG. + # PNG only, deliberately. Installing the SVG as well fails the build: + # appstreamcli in the freedesktop SDK cannot decode it at all and + # aborts compose with "Unrecognized image file format", which surfaces + # only as "appstreamcli compose failed". Reproduced and confirmed fixed + # by running compose against a staged prefix. - install -Dm644 tools/distro/simd-128.png /app/share/icons/hicolor/128x128/apps/io.github.spacefreak18.simd.png sources: - type: dir From 96188fa5e027ae24a51c5be5adff706bc37b7db3 Mon Sep 17 00:00:00 2001 From: MrDavid5465 Date: Sun, 6 Sep 2026 11:48:19 -0400 Subject: [PATCH 9/9] fix(flatpak): put every module's libraries in /app/lib, and run the result The bundle built, packaged and uploaded cleanly, then failed the moment it was installed and started: simd: error while loading shared libraries: libuv.so.1: cannot open shared object file: No such file or directory libuv and libconfig installed to /app/lib64 while everything else was in /app/lib, and only /app/lib is on the runtime's library search path. The previous fix pinned CMAKE_INSTALL_LIBDIR on three modules; it needed to be all six. The reason CI was green is that nothing in that job ever executed what it built. It now installs the bundle and runs `simd --version`, which is enough to prove every shared library resolves. Co-Authored-By: Claude Opus 5 --- .github/workflows/packages.yml | 13 +++++++++++++ .../flatpak/io.github.spacefreak18.simd.yml | 15 +++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index 3e96513..d6401b0 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -334,6 +334,19 @@ jobs: build-dir io.github.spacefreak18.simd.yml flatpak build-bundle repo simd.flatpak io.github.spacefreak18.simd + # Install and run it. A Flatpak that builds is not a Flatpak that works: + # libuv and libconfig installed to /app/lib64, which is not on the + # runtime's library search path, so the bundle built and packaged + # cleanly and then died on startup with + # simd: error while loading shared libraries: libuv.so.1 + # Nothing in this job had ever executed the binary. --version is enough + # to prove every shared library resolves. + - name: Smoke-test the bundle + working-directory: tools/distro/flatpak + run: | + flatpak install -y --user --bundle simd.flatpak + flatpak run io.github.spacefreak18.simd --version + - uses: actions/upload-artifact@v4 with: name: simd.flatpak diff --git a/tools/distro/flatpak/io.github.spacefreak18.simd.yml b/tools/distro/flatpak/io.github.spacefreak18.simd.yml index f2f737e..993f16f 100644 --- a/tools/distro/flatpak/io.github.spacefreak18.simd.yml +++ b/tools/distro/flatpak/io.github.spacefreak18.simd.yml @@ -53,6 +53,11 @@ modules: - name: libconfig buildsystem: cmake-ninja config-opts: + # /app/lib for every module, not the lib64 CMake picks by default: + # only /app/lib is on the runtime's library search path, so anything + # landing in lib64 builds and bundles fine and then fails at startup + # with "cannot open shared object file". + - -DCMAKE_INSTALL_LIBDIR=lib - -DBUILD_TESTS=OFF # libconfig 1.7.3's CMakeLists.txt predates CMake 3.5's minimum # version policy; confirmed via a failed local build. @@ -67,6 +72,11 @@ modules: - name: argtable2 buildsystem: cmake-ninja config-opts: + # /app/lib for every module, not the lib64 CMake picks by default: + # only /app/lib is on the runtime's library search path, so anything + # landing in lib64 builds and bundles fine and then fails at startup + # with "cannot open shared object file". + - -DCMAKE_INSTALL_LIBDIR=lib # Same CMake-minimum-version issue as libconfig -- this one's # CMakeLists.txt is even older (2011). - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 @@ -118,6 +128,11 @@ modules: - name: libuv buildsystem: cmake-ninja config-opts: + # /app/lib for every module, not the lib64 CMake picks by default: + # only /app/lib is on the runtime's library search path, so anything + # landing in lib64 builds and bundles fine and then fails at startup + # with "cannot open shared object file". + - -DCMAKE_INSTALL_LIBDIR=lib - -DBUILD_TESTING=OFF post-install: - sh -c 'f=$(ls -1 COPYING COPYING.txt COPYING.LIB LICENSE LICENSE.txt LICENSE.md LICENCE licence.txt 2>/dev/null | head -1); if [ -n "$f" ]; then install -Dm644 "$f" /app/share/licenses/libuv/$(basename "$f"); else echo "WARNING: no licence file found for libuv"; fi'