This commit is contained in:
2026-08-31 22:36:57 +02:00
parent eeac39e7b1
commit 9bcd84587e
15 changed files with 317 additions and 117 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
# Citizen Launcher 1.0.0
# Citizen Launcher 1.0.1
Citizen Launcher is a distro-neutral Star Citizen setup, launch, repair and maintenance application for Linux. Omarchy support is optional; the core is one static Go binary shared by Debian/Ubuntu, Fedora, Arch/Omarchy and other desktop distributions.
@@ -30,7 +30,7 @@ The launcher owns the fragile user-space gaming stack so users do not have to pi
Install the release `.deb`:
```bash
sudo apt install ./citizen-launcher_1.0.0_amd64.deb
sudo apt install ./citizen-launcher_1.0.1_amd64.deb
```
The package adds the desktop application and enables the system package-update timer. Future Citizen Launcher `.deb` releases can be installed automatically after release-asset digest and package metadata verification.
+22 -33
View File
@@ -1,42 +1,31 @@
# Citizen Launcher 1.0.0
# Citizen Launcher 1.0.1
First productized distro-neutral release.
Reliability hotfix based on the first real-machine 1.0.0 acceptance bundle.
## Setup and gameplay
## Fixed
- Replaces the old LUG-controlled installation path with one Citizen Launcher core.
- Chooses the newest stable LUG Wine release that actually passes a fresh-prefix self-test on the local CPU/glibc environment; incompatible releases are cached and skipped.
- Preserves a previous working Wine runner and never activates an untested candidate.
- Uses a deterministic SHA-256-pinned Winetricks 20260125 base (`arial`, `tahoma`, `win11` only).
- Avoids the unreliable Winetricks PowerShell MSI path. RSI PowerShell compatibility is provided by verified portable PowerShell Core 7.4.19 and the RSI-compatible Wine wrapper 3.0.5.
- Manages DXVK itself, including DLL verification and Wine native overrides.
- Parses current Electron Builder RSI `latest.yml` layouts and verifies the RSI installer SHA-512 before execution.
- Repairs prefix components, RSI compatibility, DXVK, RSI Launcher and desktop integration without deleting game data.
- Vulkan hardware detection now evaluates individual `vulkaninfo --summary` GPU blocks instead of treating the presence of any software ICD as a software-only system.
- A real AMD/NVIDIA/Intel Vulkan GPU is accepted even when Mesa also exposes `llvmpipe`/lavapipe/softpipe.
- Discrete GPUs are preferred for display/status, then integrated GPUs, then non-software virtual GPUs.
- QEMU/bochs remains blocked when it is the only usable graphics path, but no longer blocks a machine that also exposes a real working Vulkan GPU.
- Vulkan API compatibility is evaluated on the selected real GPU rather than the first arbitrary device in the summary.
- PowerShell compatibility self-test no longer requires stdout to be relayed through Wine. PowerShell Core is tested directly and the RSI-compatible wrapper is tested by its propagated exit status.
- Game/support status now discovers Prefix, DXVK, PowerShell, RSI Launcher and game files before applying an overall hardware/preflight block. A blocked machine therefore no longer reports existing components as falsely `missing`.
## Reliability hardening
## Regression coverage
- Fixes the historical literal `\\n` Star Citizen desktop-entry regression and automatically repairs old affected launchers.
- Fixes Wine registry path escaping; the file-association tweak is non-fatal.
- Fixes executable permissions when self-sync copies over an existing temporary file.
- Single GUI instance via file lock; a second launch reuses the existing localhost UI.
- One active GUI job at a time plus a cross-process gaming-stack lock.
- Detects a running RSI Launcher or Star Citizen and does not launch duplicates or kill an active game.
- Exact `/proc` `WINEPREFIX` matching avoids false busy/running detections for similarly named prefixes.
- Wine and DXVK release archives use safe extraction with traversal/link checks.
- Downloads are staged and atomically activated; logs rotate and maintenance is deferred while the prefix is busy.
- AMD RADV + llvmpipe must select AMD and report Vulkan ready.
- llvmpipe-only must remain rejected.
- a successful PowerShell wrapper with empty stdout must pass.
- blocked overall health must still expose real installed component states.
- existing 1.0 release-gate coverage remains active: race detector, stack/GUI locks, desktop-entry repair, safe archive extraction, self-update validation, package validation and integration tests.
## System and UX
## Upgrade
- Responsive standalone GUI with concise errors and expandable technical diagnostics.
- Preflight for Vulkan, AVX, RAM+swap, free storage, Linux filesystem, `vm.max_map_count` and open-file limits.
- Debian package installs the recommended system limits and uses Polkit for interactive preparation when needed.
- User-local 0.9.x binaries/desktop entries that shadow a newer Debian package are migrated safely.
- Generic uninstall removes launcher integration but preserves game/config data.
Debian/Ubuntu/Mint:
## Updates and maintenance
```bash
sudo apt install ./citizen-launcher_1.0.1_amd64.deb
```
- Debian package installs a systemd timer that checks for a new Citizen Launcher release about every six hours.
- Automatic package update requires the expected GitHub release asset, a SHA-256 digest, matching `citizen-launcher` package name/version and `amd64` architecture before APT is invoked.
- Root updater ignores user-controlled release-repository environment variables and rejects writable/non-root repository configuration.
- Running GUIs are not killed during package replacement; they offer a controlled restart into the installed version.
- CI/release gate runs unit tests, vet, race detector, static build, integration test and package verification.
Existing configuration, Wine prefix, RSI Launcher and Star Citizen data are preserved.
+25 -31
View File
@@ -1,10 +1,10 @@
# Citizen Launcher 1.0.0 – Verification Report
# Citizen Launcher 1.0.1 – Verification Report
Date: 2026-08-31
## Release gate
The 1.0.0 source tree passed the complete automated release gate:
The 1.0.1 source tree passed the complete automated release gate:
- shell syntax checks for installer, uninstaller, packaging and integration scripts
- `gofmt` cleanliness
@@ -15,41 +15,35 @@ The 1.0.0 source tree passed the complete automated release gate:
- Omarchy updater integration test against disposable local Git repositories
- Debian package build and metadata/payload verification
- generic Linux amd64 tarball build and execution check
- process-level GUI single-instance test: a second GUI invocation reused the first localhost instance
- live GUI `/api/ping` check reporting version 1.0.0
- systemd service/timer syntax verification with `systemd-analyze verify`
- desktop entry newline regression check (no literal `\\n` in Exec entries)
- AppStream XML parse check
- GitHub Actions CI/release YAML parse check
- systemd service/timer syntax verification
- desktop entry regression checks
- AppStream and workflow validation inherited from the 1.0 release gate
## Regression coverage added for 1.0.0
## Real-machine regressions reproduced from the support bundle
The Go test suite explicitly covers:
The uploaded Debian 13 support bundle exposed two 1.0.0 false negatives:
- safe archive extraction and traversal/symlink rejection
- exact Wine-prefix process matching
- GUI and stack-operation file locks
- duplicate primary-action handler regression
- Star Citizen/RSI duplicate launch handling
- DXVK DLL/override state
- deterministic Winetricks checksum and minimal base verbs
- legacy user-local binary migration
- broken desktop entry repair from the 0.9.2 literal-newline bug
- copied executable permission preservation
- Debian update package name/version/architecture validation
- release asset selection/version comparison
- Electron-builder RSI `latest.yml` variants
- non-MSI portable PowerShell compatibility path
- Wine registry path escaping
- support-bundle sanitizer behavior
- required SHA-256 release digest fail-closed behavior
1. `vulkaninfo` reported both `AMD Radeon Graphics (RADV PHOENIX2)` and Mesa `llvmpipe`. 1.0.0 rejected the whole system merely because the software ICD was present. 1.0.1 parses devices independently and selects the real AMD GPU.
2. The RSI PowerShell wrapper returned a successful process exit but no captured stdout. 1.0.0 incorrectly required a marker string in stdout. 1.0.1 validates PowerShell Core and then treats the wrapper's propagated exit status as authoritative.
Automated regression tests cover both conditions.
## Additional process-level verification
A real two-process GUI test was run against the built binary:
- first `gui --no-open` created a localhost endpoint
- second `gui --no-open` returned the exact same endpoint instead of creating another backend
- `/api/ping` on that endpoint reported version `1.0.1`
A synthetic `vulkaninfo` run matching the support bundle's AMD + llvmpipe layout selected `AMD Radeon Graphics (RADV PHOENIX2)` and reported Vulkan `ready`. The sandbox itself has insufficient RAM for Star Citizen, so its overall hardware state remained blocked for RAM as expected; the GPU path was no longer the blocker.
## Final binary / package hashes
- `backend/bin/citizen-launcher`: `5e55630dc47b89a11185384f09d79fb3813fce51b4d0e30a9660e6526c93a46d`
- `dist/citizen-launcher_1.0.0_amd64.deb`: `976a4cb3efc193f9f05e880ab0456d80e49cb7b5db0a61621d4dc4a788e7c8a1`
- `dist/citizen-launcher-1.0.0-linux-amd64.tar.gz`: `551bca5656382f79fd6e388d14f58555d0db66418a0388ab8a8ba2ac1a058e90`
- `backend/bin/citizen-launcher`: `7cd0d85046b2c9ac295c6ef5b86f93a3452bfb3cd94cff048e7511db581f3d07`
- `dist/citizen-launcher_1.0.1_amd64.deb`: `aba0944a0d2334f14f425e12a55ddd28d011449387e5be1ef3e0731e9d9a5119`
- `dist/citizen-launcher-1.0.1-linux-amd64.tar.gz`: `59edf81938c305b1d86d917d38222470b4f5e42c9c3dc350bf1ac430d8748bfc`
## Scope boundary
The release gate validates the launcher, package, updater, GUI, locks, migration, install/repair logic and local safety properties in the build environment. It cannot perform an RSI account login, download the full Star Citizen game, or execute a real game session with an external GPU from this sandbox. Those remain real-machine acceptance tests rather than simulated release-gate claims.
The release gate validates the launcher, package, updater, GUI, locks, migration, install/repair logic and local safety properties. It cannot perform an RSI account login or a complete live Star Citizen game session from this sandbox. Those remain real-machine acceptance tests.
+1 -1
View File
@@ -1 +1 @@
1.0.0
1.0.1
Binary file not shown.
+1 -1
View File
@@ -16,7 +16,7 @@ import (
)
var (
appVersion = "1.0.0"
appVersion = "1.0.1"
releaseRepo = "sendnwv/omarchy-sc"
)
+147 -43
View File
@@ -147,20 +147,10 @@ func (a *App) gameStatus() GameStatus {
Autopilot: uc.AutoMaintain,
System: sys,
}
if err := validatePrefixPath(gc.Prefix); err != nil {
st.Health = "hardware-blocked"
st.HardwareReason = err.Error()
return st
}
if hs.State == "blocked" {
st.Health = "hardware-blocked"
return st
}
if sys.State == "blocked" {
st.Health = "hardware-blocked"
st.HardwareReason = sys.Reason
return st
}
// Always inspect the existing stack before deciding the overall health.
// Support bundles must remain truthful even when hardware/preflight is
// blocked; otherwise a perfectly valid prefix/launcher appears as missing.
prefixPathErr := validatePrefixPath(gc.Prefix)
if prefixInitialized(gc.Prefix) {
st.PrefixState = "ready"
} else if fi, err := os.Stat(gc.Prefix); err == nil && fi.IsDir() {
@@ -179,6 +169,23 @@ func (a *App) gameStatus() GameStatus {
st.GameState = "partial"
}
// Health precedence is evaluated only after component discovery so diagnosis
// never loses useful state.
if prefixPathErr != nil {
st.Health = "hardware-blocked"
st.HardwareReason = prefixPathErr.Error()
return st
}
if hs.State == "blocked" {
st.Health = "hardware-blocked"
return st
}
if sys.State == "blocked" {
st.Health = "hardware-blocked"
st.HardwareReason = sys.Reason
return st
}
// Around 150 GiB of free space is the current LUG quick-start recommendation
// for a fresh install. Do not block an already-installed game because free
// space naturally drops after installation.
@@ -222,6 +229,90 @@ type hardwareInfo struct {
DiskFreeGiB int
}
type vulkanDevice struct {
Name string
Type string
APIMajor int
APIMinor int
}
func parseVulkanDevices(summary string) []vulkanDevice {
var devices []vulkanDevice
var current *vulkanDevice
flush := func() {
if current != nil && (current.Name != "" || current.Type != "" || current.APIMajor != 0) {
devices = append(devices, *current)
}
}
apiRE := regexp.MustCompile(`^([0-9]+)\.([0-9]+)`)
gpuRE := regexp.MustCompile(`^GPU[0-9]+:$`)
for _, raw := range strings.Split(summary, "\n") {
line := strings.TrimSpace(raw)
if gpuRE.MatchString(line) {
flush()
current = &vulkanDevice{}
continue
}
if current == nil {
continue
}
key, val, ok := strings.Cut(line, "=")
if !ok {
continue
}
key, val = strings.TrimSpace(key), strings.TrimSpace(val)
switch key {
case "deviceName":
current.Name = val
case "deviceType":
current.Type = val
case "apiVersion":
if m := apiRE.FindStringSubmatch(val); len(m) == 3 {
current.APIMajor, _ = strconv.Atoi(m[1])
current.APIMinor, _ = strconv.Atoi(m[2])
}
}
}
flush()
return devices
}
func softwareVulkanDevice(d vulkanDevice) bool {
low := strings.ToLower(d.Name + " " + d.Type)
return strings.Contains(low, "physical_device_type_cpu") ||
strings.Contains(low, "llvmpipe") || strings.Contains(low, "lavapipe") || strings.Contains(low, "softpipe")
}
func vulkanDeviceRank(d vulkanDevice) int {
if softwareVulkanDevice(d) {
return -1
}
switch strings.ToUpper(d.Type) {
case "PHYSICAL_DEVICE_TYPE_DISCRETE_GPU":
return 30
case "PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU":
return 20
case "PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU":
return 10
default:
if d.Name != "" {
return 1
}
}
return 0
}
func selectVulkanHardwareDevice(summary string) (vulkanDevice, bool) {
best, rank := vulkanDevice{}, -1
for _, d := range parseVulkanDevices(summary) {
r := vulkanDeviceRank(d)
if r > rank {
best, rank = d, r
}
}
return best, rank >= 0
}
func (a *App) hardwareStatus(prefix string) hardwareInfo {
h := hardwareInfo{State: "ready", Vulkan: "unknown"}
if runtime.GOARCH != "amd64" {
@@ -267,8 +358,8 @@ func (a *App) hardwareStatus(prefix string) hardwareInfo {
pci, _ := exec.Command("lspci", "-nnk").CombinedOutput()
p := string(pci)
if strings.TrimSpace(p) == "" {
// lspci/pciutils is optional on minimal Debian installations. Detect the
// QEMU VGA device through sysfs as a dependency-free fallback.
// lspci/pciutils is optional on minimal installations. Detect QEMU VGA
// through sysfs as a dependency-free fallback.
vendors, _ := filepath.Glob("/sys/class/drm/card*/device/vendor")
for _, vp := range vendors {
v, _ := os.ReadFile(vp)
@@ -287,45 +378,48 @@ func (a *App) hardwareStatus(prefix string) hardwareInfo {
}
}
}
lowp := strings.ToLower(p)
if strings.Contains(lowp, "1234:1111") || strings.Contains(lowp, "kernel driver in use: bochs") {
h.State = "blocked"
h.Vulkan = "blocked"
h.Reason = "QEMU Standard VGA/bochs-drm erkannt. Star Citizen benötigt eine echte Vulkan-GPU oder GPU-Passthrough."
return h
}
qemuFallback := strings.Contains(strings.ToLower(p), "1234:1111") || strings.Contains(strings.ToLower(p), "kernel driver in use: bochs")
if path, err := exec.LookPath("vulkaninfo"); err == nil {
cmd := exec.Command(path, "--summary")
out, err := cmd.CombinedOutput()
text := string(out)
low := strings.ToLower(text)
if err != nil || strings.Contains(text, "VK_ERROR_INCOMPATIBLE_DRIVER") {
h.State = "blocked"
h.Vulkan = "blocked"
h.Reason = "Vulkan ist nicht funktionsfähig."
return h
}
if strings.Contains(low, "device_type_cpu") || strings.Contains(low, "llvmpipe") || strings.Contains(low, "lavapipe") || strings.Contains(low, "softpipe") {
dev, hasHardware := selectVulkanHardwareDevice(text)
if !hasHardware {
h.State = "blocked"
h.Vulkan = "blocked"
h.Reason = "Es wurde nur ein Software-Vulkan-Gerät erkannt. Star Citizen benötigt eine echte Vulkan-GPU."
if qemuFallback {
h.Reason = "QEMU Standard VGA/bochs-drm erkannt und keine echte Vulkan-GPU verfügbar. Für Star Citizen ist GPU-Passthrough erforderlich."
} else {
h.Reason = "Es wurde nur ein Software-Vulkan-Gerät erkannt. Star Citizen benötigt eine echte Vulkan-GPU."
}
return h
}
if m := regexp.MustCompile(`(?m)deviceName\s*=\s*(.+)$`).FindStringSubmatch(text); len(m) == 2 {
h.GPU = strings.TrimSpace(m[1])
if dev.Name != "" {
h.GPU = dev.Name
}
if m := regexp.MustCompile(`(?m)apiVersion\s*=\s*([0-9]+)\.([0-9]+)`).FindStringSubmatch(text); len(m) == 3 {
major, _ := strconv.Atoi(m[1])
minor, _ := strconv.Atoi(m[2])
if major < 1 || (major == 1 && minor < 3) {
h.State = "blocked"
h.Vulkan = "blocked"
h.Reason = fmt.Sprintf("Vulkan %d.%d erkannt; der aktuelle DXVK-Stack benötigt Vulkan 1.3 oder neuer.", major, minor)
return h
}
if dev.APIMajor < 1 || (dev.APIMajor == 1 && dev.APIMinor < 3) {
h.State = "blocked"
h.Vulkan = "blocked"
h.Reason = fmt.Sprintf("Vulkan %d.%d erkannt; der aktuelle DXVK-Stack benötigt Vulkan 1.3 oder neuer.", dev.APIMajor, dev.APIMinor)
return h
}
h.Vulkan = "ready"
} else {
// Without vulkaninfo we can only make a provisional assessment. QEMU's
// bochs framebuffer is still a hard block because it is not a Vulkan GPU.
if qemuFallback {
h.State = "blocked"
h.Vulkan = "blocked"
h.Reason = "QEMU Standard VGA/bochs-drm erkannt. Star Citizen benötigt eine echte Vulkan-GPU oder GPU-Passthrough."
return h
}
matches, _ := filepath.Glob("/usr/share/vulkan/icd.d/*.json")
if len(matches) > 0 {
h.Vulkan = "probable"
@@ -886,17 +980,27 @@ func (a *App) writePowerShellMarker(prefix, version string) error {
}
func (a *App) probePowerShell(gc GameConfig, env []string) error {
_, _, wrapper64, _ := a.powerShellPaths(gc.Prefix)
core, _, wrapper64, _ := a.powerShellPaths(gc.Prefix)
runner, err := a.currentRunner()
if err != nil {
return err
}
wine := filepath.Join(runner, "bin", "wine")
probe := exec.Command(wine, wrapper64, "-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "Write-Output CITIZEN_POWERSHELL_OK")
probe.Env = env
out, err := probe.CombinedOutput()
if err != nil || !strings.Contains(string(out), "CITIZEN_POWERSHELL_OK") {
return fmt.Errorf("PowerShell Selbsttest: %s", formatCommandFailure(err, out))
// Validate PowerShell Core directly first. Then validate the compatibility
// wrapper using the invocation shape documented by the wrapper project.
// Wine does not reliably forward stdout from a Windows child spawned by
// another Windows executable, while wrapper v3+ deliberately propagates the
// child's exit status. A successful exit is therefore authoritative.
coreProbe := exec.Command(wine, core, "-NoLogo", "-NoProfile", "-NonInteractive", "-Command", "exit 0")
coreProbe.Env = env
if out, err := coreProbe.CombinedOutput(); err != nil {
return fmt.Errorf("PowerShell Core Selbsttest: %s", formatCommandFailure(err, out))
}
wrapperProbe := exec.Command(wine, wrapper64, "-NoLogo", "-NonInteractive", "exit 0")
wrapperProbe.Env = env
if out, err := wrapperProbe.CombinedOutput(); err != nil {
return fmt.Errorf("PowerShell Wrapper Selbsttest: %s", formatCommandFailure(err, out))
}
wait := exec.Command(filepath.Join(runner, "bin", "wineserver"), "-w")
wait.Env = env
+113
View File
@@ -179,3 +179,116 @@ func TestRequiredReleaseDigestFailsClosed(t *testing.T) {
t.Fatalf("known SHA-256 rejected: %v", err)
}
}
func TestVulkanHardwareSelectionIgnoresLLVMPipeWhenRealGPUExists(t *testing.T) {
summary := `Devices:
========
GPU0:
apiVersion = 1.4.305
deviceType = PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU
deviceName = AMD Radeon Graphics (RADV PHOENIX2)
GPU1:
apiVersion = 1.4.305
deviceType = PHYSICAL_DEVICE_TYPE_CPU
deviceName = llvmpipe (LLVM 19.1.7, 256 bits)
`
dev, ok := selectVulkanHardwareDevice(summary)
if !ok {
t.Fatal("real Vulkan GPU was rejected because a software ICD was also present")
}
if dev.Name != "AMD Radeon Graphics (RADV PHOENIX2)" {
t.Fatalf("selected GPU=%q", dev.Name)
}
if dev.APIMajor != 1 || dev.APIMinor != 4 {
t.Fatalf("selected Vulkan version=%d.%d", dev.APIMajor, dev.APIMinor)
}
}
func TestVulkanHardwareSelectionRejectsSoftwareOnly(t *testing.T) {
summary := `Devices:
========
GPU0:
apiVersion = 1.4.305
deviceType = PHYSICAL_DEVICE_TYPE_CPU
deviceName = llvmpipe (LLVM 19.1.7, 256 bits)
`
if dev, ok := selectVulkanHardwareDevice(summary); ok {
t.Fatalf("software-only Vulkan device accepted: %#v", dev)
}
}
func TestPowerShellProbeAcceptsSuccessfulWrapperWithoutStdout(t *testing.T) {
a := testApp(t)
prefix := filepath.Join(a.home, "Games", "star-citizen")
gc := GameConfig{Prefix: prefix}
core, _, wrapper64, _ := a.powerShellPaths(prefix)
for _, p := range []string{core, wrapper64} {
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, make([]byte, 2048), 0o644); err != nil {
t.Fatal(err)
}
}
runner := filepath.Join(a.vendorDir, "wine", "fixture")
bin := filepath.Join(runner, "bin")
if err := os.MkdirAll(bin, 0o755); err != nil {
t.Fatal(err)
}
// The fixture intentionally emits no stdout: this reproduces the real Wine
// behavior from the 1.0.0 support bundle.
for _, name := range []string{"wine", "wineserver"} {
p := filepath.Join(bin, name)
if err := os.WriteFile(p, []byte("#!/bin/sh\nexit 0\n"), 0o755); err != nil {
t.Fatal(err)
}
}
if err := os.MkdirAll(filepath.Join(a.vendorDir, "wine"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.Symlink(runner, filepath.Join(a.vendorDir, "wine", "current")); err != nil {
t.Fatal(err)
}
if err := a.probePowerShell(gc, os.Environ()); err != nil {
t.Fatalf("successful wrapper without stdout was rejected: %v", err)
}
}
func TestGameStatusStillReportsInstalledComponentsWhenHealthIsBlocked(t *testing.T) {
a := testApp(t)
prefix := filepath.Join(a.home, "Games", "star citizen") // intentionally invalid path: forces blocked health
gc := GameConfig{
Prefix: prefix,
LauncherEXE: filepath.Join(prefix, "drive_c", "Program Files", "Roberts Space Industries", "RSI Launcher", "RSI Launcher.exe"),
GameDir: filepath.Join(prefix, "drive_c", "Program Files", "Roberts Space Industries", "StarCitizen"),
}
for _, p := range []string{
filepath.Join(prefix, "drive_c"),
filepath.Dir(gc.LauncherEXE),
filepath.Join(gc.GameDir, "LIVE", "Bin64"),
} {
if err := os.MkdirAll(p, 0o755); err != nil {
t.Fatal(err)
}
}
for _, p := range []string{
filepath.Join(prefix, "system.reg"),
filepath.Join(prefix, "user.reg"),
gc.LauncherEXE,
filepath.Join(gc.GameDir, "LIVE", "Bin64", "StarCitizen.exe"),
} {
if err := os.WriteFile(p, []byte("fixture"), 0o644); err != nil {
t.Fatal(err)
}
}
if err := a.saveGameConfig(gc); err != nil {
t.Fatal(err)
}
st := a.gameStatus()
if st.Health != "hardware-blocked" {
t.Fatalf("health=%q want hardware-blocked", st.Health)
}
if st.PrefixState != "ready" || st.LauncherState != "ready" || st.GameState != "ready" {
t.Fatalf("blocked health hid real component state: prefix=%s launcher=%s game=%s", st.PrefixState, st.LauncherState, st.GameState)
}
}
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -12,7 +12,7 @@ Panel {
property var anchorItem: null
property var hostWidget: null
property string pluginVersion: "1.0.0"
property string pluginVersion: "1.0.1"
property string health: "checking"
property string depsState: "checking"
property string depsMissing: ""
@@ -106,7 +106,7 @@ Panel {
values[lines[i].slice(0, p)] = lines[i].slice(p + 1)
}
pluginVersion = values.plugin_version || "1.0.0"
pluginVersion = values.plugin_version || "1.0.1"
health = values.health || "setup"
depsState = values.deps || "missing"
depsMissing = values.deps_missing || ""
+1 -1
View File
@@ -2,7 +2,7 @@
set -u
PLUGIN_ID="local.omarchy-citizen"
PLUGIN_VERSION="1.0.0"
PLUGIN_VERSION="1.0.1"
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
XDG_DATA_HOME="${XDG_DATA_HOME:-$HOME/.local/share}"
+1 -1
View File
@@ -2,7 +2,7 @@
"schemaVersion": 1,
"id": "local.omarchy-citizen",
"name": "Citizen Launcher · Omarchy",
"version": "1.0.0",
"version": "1.0.1",
"author": "Community prototype",
"license": "MIT",
"description": "Optional Omarchy bar integration for the distro-neutral Citizen Launcher.",
+1 -1
View File
@@ -1,5 +1,5 @@
Name: citizen-launcher
Version: 1.0.0
Version: 1.0.1
Release: 1%{?dist}
Summary: Star Citizen launcher and self-maintaining Wine stack for Linux
License: MIT
@@ -13,6 +13,6 @@
<url type="homepage">https://github.com/sendnwv/omarchy-sc</url>
<categories><category>Game</category></categories>
<provides><binary>citizen-launcher</binary></provides>
<releases><release version="1.0.0" date="2026-08-31"/></releases>
<releases><release version="1.0.1" date="2026-08-31"/><release version="1.0.0" date="2026-08-31"/></releases>
<content_rating type="oars-1.1"/>
</component>