Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Avalonia desktop (net10 LTS): SDK 10 plus native X11/font/GL deps so the
# Modmanager builds and can at least smoke-run headless via xvfb.
FROM mcr.microsoft.com/dotnet/sdk:10.0-bookworm-slim

ARG USERNAME=vscode
ARG USER_UID=1000
ARG USER_GID=1000

RUN groupadd --gid "$USER_GID" "$USERNAME" \

Check warning on line 9 in .devcontainer/Dockerfile

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

.devcontainer/Dockerfile#L9

Pin versions in apt get install. Instead of `apt-get install <package>` use `apt-get install <package>=<version>`
&& useradd --uid "$USER_UID" --gid "$USER_GID" -m -s /bin/bash "$USERNAME" \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
git sudo xvfb \
libice6 libsm6 libx11-6 libxkbcommon0 libfontconfig1 libfreetype6 \
libgl1 libegl1 zlib1g dbus-x11 \
&& echo "$USERNAME ALL=(root) NOPASSWD:ALL" > "/etc/sudoers.d/$USERNAME" \
&& chmod 0440 "/etc/sudoers.d/$USERNAME" \
&& rm -rf /var/lib/apt/lists/*

USER "$USERNAME"
RUN dotnet --list-sdks
20 changes: 20 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "greg modmanager (avalonia, net10)",
"build": {
"dockerfile": "Dockerfile"
},
"remoteUser": "vscode",
"containerEnv": {
"DOTNET_CLI_TELEMETRY_OPTOUT": "1",
"DOTNET_NOLOGO": "1"
},
"mounts": [
"source=greg-modmanager-nuget,target=/home/vscode/.nuget/packages,type=volume"
],
"customizations": {
"vscode": {
"extensions": ["ms-dotnettools.csharp"]
}
},
"postCreateCommand": "dotnet restore GregModmanager.sln"
}
6 changes: 6 additions & 0 deletions .github/workflows/build-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,12 @@ jobs:
--self-contained true /p:PublishTrimmed=true /p:TrimMode=full \
/p:PublishSingleFile=true /p:DebugType=none /p:DebugSymbols=false \
-o ./publish/linux-x64
rm -f ./publish/linux-x64/steam_api64.dll
for steam_library in libsteam_api64.so libsteam_api.so; do
if [ -f "src/GregModmanager.Core/${steam_library}" ]; then
cp "src/GregModmanager.Core/${steam_library}" "./publish/linux-x64/${steam_library}"
fi
done

- name: Create tarball
id: package
Expand Down
52 changes: 52 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Headless publish accepts `--changelog <text>` (alias `--change-note`) so
Steam Workshop updates carry a change note for version control.

- Shared Terminal Core components for secondary, compact, danger, and active-tab
buttons as well as reusable card and content-surface containers.
- Docker Buildx targets for deterministic test runs, Windows cross-publish,
and macOS x64/arm64 cross-publish; a Windows-container Dockerfile is
available for compatible Windows Docker hosts.
Expand All @@ -22,6 +27,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **Linux Steam publish**: replaced the broken Facepunch `Editor` publish path
with direct native `ISteamUGC` calls via reflection (`NativePublishAsync`).
`SetItemContent`/`SetItemPreview` through the `Editor` wrapper returned
`FileNotFound` on Linux even though every file existed and was readable.
The preview is staged to a temp copy before the upload (locked originals —
file manager, image viewer, or our own UI — no longer block Steam), live
progress and elapsed time are reported during the upload, and the wait loop
exits early on `CommittingChanges` instead of hanging. `TrimmerRoots.xml`
is now referenced from the Avalonia project so trimmed release builds keep
the `ISteamUGC` update methods.
- **Linux Steam connection**: replaced the bundled Linux `libsteam_api.so` /
`libsteam_api64.so` (Steamworks SDK 1.62 interface set, `SteamApps_v009`) with
the SDK 1.61 build (`SteamApps_v008`/`SteamFriends_v017`) matching
`Cherry.Facepunch.Steamworks 2.5.0`. `SteamClient.Init` failed with
`EntryPointNotFoundException` before, so the app permanently showed
"Steam Disconnected" on Linux; Workshop browse, subscribe, and publish work
again against a running Steam client.
- **Project editing**: each project card now has an explicit Edit/Bearbeiten
button (reliable `Click` handling alongside tap-to-open). Opening a project
can no longer fail silently: navigation and load errors are logged and shown
as a dialog instead of leaving the Projects page without feedback.
Navigation resolves the main window via the DI singleton with a visual-tree
fallback.
- **Merged Projects/My Uploads page**: the separate My Uploads page is gone.
The Projects page now has two tabs — local projects (search, refresh, edit)
and Workshop uploads (refresh, import selected, edit/add-update/download/view
on Steam, paging). Importing an item refreshes the local list and opens the
editor directly. The sidebar entry and profile-menu link point here.
- **Steam native-load diagnostics**: `SteamApiNativeLoader` now records every
searched candidate path, so the "not found" hint actually lists where the
loader looked instead of an empty path list.
- **Modstore authentication routing**: desktop login, token exchange, logout,
and Better Auth now use `datacentermods.com` as the primary service and
fail over to the trusted `datacentermods.home` endpoint only when the public
endpoint is unreachable or unavailable.
- **Steam fallback**: invalid Steam API initializations no longer cause project
opening or the My Uploads list to crash; local projects remain editable when
Workshop metadata cannot be refreshed.
- **Workshop editing**: the editor and Mod Store now use consistent tab and
action states, while item actions remain usable on narrow desktop windows.
- **Linux Steam detection**: native Steam API discovery now also checks the
Flatpak Steam installation and accepts whitespace-formatted Steam library
VDF files.
- **Linux release artifacts**: portable CI builds exclude the Windows Steam DLL
and include both supported Linux Steam API library names.
- **Linux protocol registration**: desktop entries correctly quote application
paths and refresh the actual applications directory.
- **CI Build Error**: Replaced MAUI `Preferences.Default` and Avalonia `SettingsPage` references in Core services with platform-agnostic `S.Preferences` and `AppSettings` APIs, restoring Windows/Linux build compatibility.
- **Build consistency**: aligned GitHub Actions with the SDK pinned in
`global.json`, updated non-MelonLoader package/action dependencies, and
Expand Down
3 changes: 2 additions & 1 deletion EXTERNAL_DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ a release.

| Asset | Platforms | Distribution note |
| --- | --- | --- |
| Steam native library | Windows DLL or Linux shared object, depending on publish RID | governed by Valve Steamworks terms; do not present it as open source |
| Steam native library (Windows `steam_api64.dll`) | Windows x64 publish output | governed by Valve Steamworks terms; do not present it as open source |
| Steam native library (Linux `libsteam_api.so` / `libsteam_api64.so`, Steamworks SDK 1.61 interface set: `SteamApps_v008`/`SteamFriends_v017`) | Linux x64 publish output, sourced from the `Facepunch.Steamworks.Dll 1.61.0` NuGet redistributable (`runtimes/linux-x64/native/libsteam_api.so`) to match `Cherry.Facepunch.Steamworks 2.5.0` | governed by Valve Steamworks terms; do not present it as open source. Newer SDK builds (e.g. 1.62.0 with `SteamApps_v009`) are **not** compatible with the managed wrapper and break `SteamClient.Init` on Linux |
| `steam_appid.txt` | platform publish output where configured | project configuration, not a library |
| bundled fonts | desktop resources | see the licence files beside the vendored fonts |
| Visual C++ Redistributable | Windows when required by the installed runtime/components | governed by Microsoft terms |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ is built separately from the main solution and copied to the Avalonia assets by

## Technology

- .NET 9 (`net9.0`) and Avalonia 12.1.1
- .NET 10 (`net10.0`) and Avalonia 12.1.1
- Cherry.Facepunch.Steamworks 2.5.0
- Microsoft.Extensions.DependencyInjection 10.0.10
- xUnit for automated tests
Expand Down
11 changes: 8 additions & 3 deletions api-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Stand: `v1.6.1-pre.13`

| Zweck | Desktop-Aufruf | Webapp-Status |
|---|---|---|
| Browser-Login starten | `GET /auth/login` auf `https://datacentermods.home` | Implementiert in `src/app/auth/login/route.ts` |
| Code gegen Session tauschen | `POST /auth/token` auf `https://api.datacentermods.home` | Implementiert in `src/app/auth/token/route.ts` |
| Browser-Login starten | `GET /auth/login` auf `https://datacentermods.com`, Fallback `https://datacentermods.home` | Implementiert in `src/app/auth/login/route.ts` |
| Code gegen Session tauschen | `POST /auth/token` auf `https://datacentermods.com`, Fallback `https://datacentermods.home` | Implementiert in `src/app/auth/token/route.ts` |
| Refresh prüfen | `POST /auth/token` mit `state=refresh` | Implementiert; prüft die Better-Auth-Session gegen Ablauf/Widerruf |
| Logout | `POST /auth/logout` mit Bearer-Sessiontoken | Implementiert in `src/app/auth/logout/route.ts` |
| Webapp-Session | Better Auth `/api/auth/*` | Bestehende Webapp-Authentifizierung bleibt unverändert |
Expand All @@ -26,9 +26,14 @@ Stand: `v1.6.1-pre.13`
Der Starter `/usr/local/bin/gregmodmanager-local-test` setzt:

- `MODSTORE_WEB_URL=https://datacentermods.home`
- `MODSTORE_API_URL=https://api.datacentermods.home`
- `MODSTORE_API_URL=https://datacentermods.home`
- `IS_LOCAL_TEST_BUILD=TRUE`

Normal builds use `https://datacentermods.com` first. Optional
`MODSTORE_WEB_FALLBACK_URL` and `MODSTORE_API_FALLBACK_URL` overrides can
replace the trusted `https://datacentermods.home` fallback. Authentication
failures such as HTTP 401 or 403 never trigger failover.

Die lokale Webapp muss danach neu gebaut/gestartet werden. Außerdem müssen `datacentermods.home` und `api.datacentermods.home` auf die lokale Reverse-Proxy-Adresse zeigen; aktuell ist `api.datacentermods.home` auf diesem System noch nicht auflösbar.

## Nicht-Webapp-APIs
Expand Down
2 changes: 1 addition & 1 deletion build/installer/gregModmanager.iss
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ german.WelcomeLabel2=Hiermit wird {#MyAppName} {#MyAppVersion} auf Ihrem Compute
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce

[Files]
Source: "..\..\src\GregModmanager.Avalonia\bin\Release\net9.0\win-x64\publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\..\src\GregModmanager.Avalonia\bin\Release\net10.0\win-x64\publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs

[InstallDelete]
Type: filesandordirs; Name: "{app}\*"
Expand Down
2 changes: 1 addition & 1 deletion build/scripts/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ if ($wantSign -and $SigningMode -in @('auto', 'self-signed')) {
}
$projPath = Join-Path $repoRoot 'src\GregModmanager.Avalonia\GregModmanager.Avalonia.csproj'
$iss = Join-Path $repoRoot 'build\installer\gregModmanager.iss'
$winPublishDir = Join-Path $repoRoot 'src\GregModmanager.Avalonia\bin\Release\net9.0\win-x64\publish'
$winPublishDir = Join-Path $repoRoot 'src\GregModmanager.Avalonia\bin\Release\net10.0\win-x64\publish'
$linuxPublishDir = Join-Path $repoRoot 'artifacts\publish\linux-x64'
$installerOutDir = Join-Path $repoRoot 'build\installer\Output'
$artifactsDir = Join-Path $repoRoot 'artifacts'
Expand Down
2 changes: 1 addition & 1 deletion build/scripts/install-local.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ if ([string]::IsNullOrWhiteSpace($InstallDir)) {
$InstallDir = Join-Path $env:LOCALAPPDATA "Programs\$AppFolderName"
}

$publishRel = Join-Path $repoRoot "src\GregModmanager.Avalonia\bin\Release\net9.0\win-x64\publish"
$publishRel = Join-Path $repoRoot "src\GregModmanager.Avalonia\bin\Release\net10.0\win-x64\publish"
$exePath = Join-Path $InstallDir $ExeName
$desktop = [Environment]::GetFolderPath([Environment+SpecialFolder]::Desktop)
$startMenuPrograms = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs'
Expand Down
2 changes: 1 addition & 1 deletion build/scripts/start.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
$buildPath = Join-Path $repoRoot "bin\$Configuration\net9.0-windows10.0.19041.0\$Platform"
$buildPath = Join-Path $repoRoot "bin\$Configuration\net10.0-windows10.0.19041.0\$Platform"
$exePath = Join-Path $buildPath 'GregModmanager.exe'

if (-not (Test-Path $exePath)) {
Expand Down
6 changes: 3 additions & 3 deletions current_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,9 @@ Der eigentliche lokale Steam-Workshop-Upload, Update, Download und Fortschritt b

- Webapp erzeugt `greg://install?modId=<id>`-Installationslinks.
- Desktop registriert und verarbeitet das `greg://`-Protokoll.
- Desktop-Testprofil unterstützt:
- Browser/Login: `https://datacentermods.home`;
- API: `https://api.datacentermods.home`.
- Desktop-Authentifizierung verwendet `https://datacentermods.com` als
primären Browser-/API-Endpunkt und `https://datacentermods.home` als
vertrauenswürdigen Verfügbarkeits-Fallback.
- Webapp-Desktop-Auth-Bridge im Quellcode:
- `GET /auth/login`;
- `POST /auth/token`;
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ FROM mcr.microsoft.com/dotnet/runtime:9.0-noble AS runtime9
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS restore
WORKDIR /src

# The solution is built by SDK 10 (global.json) but testhost targets net9.0.
# The solution is built by SDK 10 (global.json) but testhost targets net10.0.
COPY --from=runtime9 /usr/share/dotnet/shared/Microsoft.NETCore.App/ /usr/share/dotnet/shared/Microsoft.NETCore.App/

COPY global.json GregModmanager.sln ./
Expand Down
4 changes: 2 additions & 2 deletions docker/Dockerfile.windows
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0-windowsservercore-ltsc2022 AS build
SHELL ["powershell", "-NoProfile", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
WORKDIR C:\src

# The projects target net9.0 while global.json selects SDK 10. Copy the matching
# runtime into the SDK image so the test stage can execute net9.0 test binaries.
# The projects target net10.0 while global.json selects SDK 10. Copy the matching
# runtime into the SDK image so the test stage can execute net10.0 test binaries.
COPY --from=runtime9 ["C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\", "C:\\Program Files\\dotnet\\shared\\Microsoft.NETCore.App\\"]

COPY . .
Expand Down
2 changes: 1 addition & 1 deletion docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Artifacts are written to `artifacts/docker/`, which is ignored by Git.
It requires a Windows host with Docker configured for **Windows containers**
and a host version compatible with `windowsservercore-ltsc2022`.

The project targets `net9.0` while `global.json` selects SDK 10. The image
The project targets `net10.0` while `global.json` selects SDK 10. The image
copies the .NET 9 runtime into the SDK image so its `test` target can execute
the test binaries without a manual image patch.

Expand Down
9 changes: 7 additions & 2 deletions docs/codebase/INTEGRATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
| System | Type | Purpose | Auth | Criticality | Evidence |
|---|---|---|---|---|---|
| Steamworks | native SDK | Steam init and Workshop browse/download/upload | Steam client/session | high | `SteamWorkshopService.cs` |
| Modstore services | HTTPS API | desktop account/session bridge and configured service calls | bearer/session | high | `Services/Auth/` |
| Modstore services | HTTPS API | desktop account/session bridge and configured service calls; `datacentermods.com` primary with trusted `.home` availability fallback | bearer/session | high | `Services/Auth/` |
| GitHub releases | HTTPS API | MelonLoader/SteamModfix downloads | public API/User-Agent | medium | installer services |
| Local filesystem | local state/game files | workspace, projects, logs, deployment | OS permissions | high | `WorkspaceService`, `AppSettings` |

Expand All @@ -33,7 +33,12 @@ logs or committed configuration.

## Reliability

Installer clients use finite HTTP timeouts. Cancellation and result records exist in download/install services, but no common retry policy or circuit breaker exists. Steam native loading tries multiple platform paths.
Authentication clients use finite HTTP timeouts and retry the explicitly trusted
`.home` endpoint only for transport failures, missing routes, timeouts, or server
availability failures. Authentication failures such as 401 and 403 are not
retried against another host. Cancellation and result records exist in
download/install services, but no app-wide retry policy or circuit breaker
exists. Steam native loading tries multiple platform paths.

## Observability

Expand Down
5 changes: 3 additions & 2 deletions docs/codebase/STACK.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
| Area | Value | Evidence |
|---|---|---|
| Language | C# | `src/GregModmanager.Core/` |
| Runtime | .NET 9 (`net9.0`) | `src/GregModmanager.Core/GregModmanager.Core.csproj` |
| Runtime | .NET 10 (`net10.0`) | `src/GregModmanager.Core/GregModmanager.Core.csproj` |
| UI | Avalonia 12.1.1 | `src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj` |
| Build | .NET SDK/MSBuild | `GregModmanager.sln`, `global.json` |

Expand Down Expand Up @@ -47,7 +47,8 @@ Trimming/single-file publishing is enabled in the Avalonia project.
| --- | --- | --- |
| `DATA_CENTER_GAME_DIR` | override automatic game-root discovery | no |
| `IS_LOCAL_BUILD`, `IS_LOCAL_TEST_BUILD` | select local development endpoints | no |
| `MODSTORE_WEB_URL`, `MODSTORE_API_URL` | override web/API base URLs | no, but use trusted HTTPS endpoints |
| `MODSTORE_WEB_URL`, `MODSTORE_API_URL` | override primary web/API base URLs | no, but use trusted HTTPS endpoints |
| `MODSTORE_WEB_FALLBACK_URL`, `MODSTORE_API_FALLBACK_URL` | override the trusted fallback URLs (default `https://datacentermods.home`) | no, but security-sensitive |
| `MELONLOADER_RELEASES_URL`, `MELONLOADER_LATEST_API_URL` | override MelonLoader release lookup | no |
| `AUTH_LOGIN_URL_FORMAT`, `AUTH_CALLBACK_REDIRECT_URI` | override desktop authentication flow | no, but security-sensitive |
| `TELEMETRY_URL`, `GIT_SERVER_URL` | override telemetry/Git endpoints | telemetry credentials are secret; URLs are not |
Expand Down
14 changes: 13 additions & 1 deletion src/GregModmanager.Avalonia/GregModmanager.Avalonia.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<ApplicationManifest>app.manifest</ApplicationManifest>
Expand All @@ -23,6 +23,12 @@
<SatelliteResourceLanguages>en;de;fr;es;it;ja;pl;ru;zh</SatelliteResourceLanguages>
</PropertyGroup>

<!-- Preserve Facepunch.Steamworks assemblies from trimming — we access
ISteamUGC methods via reflection and the trimmer removes them. -->
<ItemGroup>
<TrimmerRootDescriptor Include="..\GregModmanager.Core\TrimmerRoots.xml" />
</ItemGroup>

<!-- Default RuntimeIdentifier: win-x64 on Windows, linux-x64 on Linux -->
<PropertyGroup Condition="'$(RuntimeIdentifier)' == '' AND $([MSBuild]::IsOSPlatform('Windows'))">
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
Expand Down Expand Up @@ -55,6 +61,12 @@

<ItemGroup>
<!-- Steamworks native libraries must remain external to the single-file bundle. -->
<Content Include="..\GregModmanager.Core\steam_api64.dll" Condition="'$(RuntimeIdentifier)' == 'win-x64' AND Exists('..\GregModmanager.Core\steam_api64.dll')">
<Link>steam_api64.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<Content Include="..\GregModmanager.Core\libsteam_api64.so" Condition="'$(RuntimeIdentifier)' == 'linux-x64' AND Exists('..\GregModmanager.Core\libsteam_api64.so')">
<Link>libsteam_api64.so</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
Expand Down
Loading
Loading