diff --git a/BSO.csproj b/BSO.csproj index 165a19b..203fed3 100644 --- a/BSO.csproj +++ b/BSO.csproj @@ -5,7 +5,7 @@ latest BSO BSO - 0.5.0 + 0.5.1 x64 true disable diff --git a/Bootstrap.cs b/Bootstrap.cs index f4ba9d5..753c839 100644 --- a/Bootstrap.cs +++ b/Bootstrap.cs @@ -19,7 +19,7 @@ namespace BSO /// public static unsafe class Bootstrap { - public const string ModVersion = "0.5.0"; + public const string ModVersion = "0.5.1"; static HostApi* host; static int frameErrors; diff --git a/CHANGELOG.md b/CHANGELOG.md index b31e725..afab5b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ Neue Version: hier einen Abschnitt `## ` anlegen, `` in `BSO.csproj` und `Bootstrap.ModVersion` erhöhen, dann `.\build.ps1 -Release`. +## 0.5.1 +- F8 entfernt: verbunden wird nur automatisch beim Start über die Lobby +- Bricht die Verbindung ab, verbindet BSO alle 10 Sekunden von selbst neu +- Ohne Lobby gestartet: Hinweis „Allein unterwegs“ statt Server-Adresse + ## 0.5.0 - Lobby: mit Freunden auf dem Lobby-Server spielen - ueber die BSO-Website, ohne Portfreigabe - Anzeige "Lobby von ..." statt der Adresse diff --git a/Config.cs b/Config.cs index 48bf9d0..50e3182 100644 --- a/Config.cs +++ b/Config.cs @@ -15,7 +15,7 @@ namespace BSO public string ServerLabel { get; set; } /// BSO-Relay (Lobby-Server) - schreibt der Launcher. public string Relay { get; set; } = "https://bso-relay.mbm.wtf"; - /// Beim Spielstart automatisch verbinden (sonst F8). + /// Beim Spielstart automatisch verbinden (Start ueber die Lobby) - sonst allein, ohne Server. public bool AutoConnect { get; set; } /// Spiel-Ticket der BSO-Website - schreibt der Launcher bei SPIELEN hinein (24 h gueltig). public string Ticket { get; set; } diff --git a/Input.cs b/Input.cs index 86a28fe..575020e 100644 --- a/Input.cs +++ b/Input.cs @@ -3,17 +3,17 @@ using System.Runtime.InteropServices; namespace BSO { - /// Tasten der Mod (F8-F11): "gerade gedrueckt" erkennen, nur wenn das Spielfenster vorne ist. + /// Tasten der Mod (F9-F11): "gerade gedrueckt" erkennen, nur wenn das Spielfenster vorne ist. public static class Input { [DllImport("user32.dll")] static extern short GetAsyncKeyState(int key); [DllImport("user32.dll")] static extern nint GetForegroundWindow(); [DllImport("user32.dll")] static extern uint GetWindowThreadProcessId(nint window, out uint process); - public const int F8 = 0x77, F9 = 0x78, F10 = 0x79, F11 = 0x7A; + public const int F9 = 0x78, F10 = 0x79, F11 = 0x7A; static readonly bool[] down = new bool[256], pressed = new bool[256]; - static readonly int[] watched = { F8, F9, F10, F11 }; + static readonly int[] watched = { F9, F10, F11 }; /// Einmal pro Bild vor allen Pressed-Abfragen. public static void Update() diff --git a/Multiplayer.cs b/Multiplayer.cs index 6cf1765..59caba6 100644 --- a/Multiplayer.cs +++ b/Multiplayer.cs @@ -10,7 +10,7 @@ using BSO.Net; namespace BSO { /// - /// Verbindung zum BSO-Server (wie MSCO per IP): F8 verbindet/trennt, Server und Name stehen in BSO.json. Laeuft im + /// Verbindung zum BSO-Server: automatisch beim Start ueber die Lobby (bricht sie ab, alle 10 s neu), Server und Name stehen in BSO.json. Laeuft im /// Hauptthread: Verbindungsaufbau wird nur abgefragt (nicht blockierend), empfangen wird ueber Connection.Poll(). /// Schickt den eigenen Zustand 10x pro Sekunde, alle 2 s einen Ping. /// @@ -82,19 +82,20 @@ namespace BSO static Task connectTask; static Connection conn; static readonly Stopwatch connectClock = new(), sendClock = new(), pingClock = new(); - static bool autoConnectDone; + static readonly Stopwatch retryClock = new(); + static bool autoConnectDone, rejected; + const int RetryMs = 10000; public static void Update() { - if (!autoConnectDone && LocalPlayer.InScene) + // Verbunden wird nur automatisch (Start ueber die Lobby im Launcher). Bricht die Verbindung ab, alle 10 s neu - + // ausser der Server hat abgelehnt oder das Konto passt nicht (dann hilft nur neu SPIELEN im Launcher). + if (Config.Current.AutoConnect && LocalPlayer.InScene && Status == NetStatus.Offline && !rejected + && (!autoConnectDone || retryClock.ElapsedMilliseconds >= RetryMs)) { autoConnectDone = true; - if (Config.Current.AutoConnect) Connect(); - } - if (Input.Pressed(Input.F8)) - { - if (Status == NetStatus.Offline) Connect(); - else Disconnect("Getrennt mit F8"); + retryClock.Restart(); + if (AccountProblem == null) Connect(); } if (Status == NetStatus.Connecting) PollConnect(); @@ -234,6 +235,7 @@ namespace BSO break; case MsgType.Reject: + rejected = true; // kein automatisches Neu-Verbinden Disconnect("Abgelehnt: " + r.ReadString()); break; @@ -270,6 +272,7 @@ namespace BSO LastError = reason; Players.Clear(); PingMs = -1; + retryClock.Restart(); Bootstrap.Log("Getrennt: " + reason); } diff --git a/PositionHud.cs b/PositionHud.cs index 7cf5026..3ad350f 100644 --- a/PositionHud.cs +++ b/PositionHud.cs @@ -41,7 +41,7 @@ namespace BSO PlayerState me = LocalPlayer.State; var text = new List { - $"BSO {Bootstrap.ModVersion} [F8] Online [F9] Anzeige [F10] Entities [F11] Vollbild/Fenster", + $"BSO {Bootstrap.ModVersion} [F9] Anzeige [F10] Entities [F11] Vollbild/Fenster", me.Mode switch { PlayerMode.InBus => $"Du: im Bus {LocalPlayer.Bus.Pos} {LocalPlayer.Speed * 0.036f:0} km/h", @@ -62,8 +62,11 @@ namespace BSO case Multiplayer.NetStatus.Offline when Multiplayer.AccountProblem != null: text.Add(Multiplayer.AccountProblem); return; + case Multiplayer.NetStatus.Offline when !Config.Current.AutoConnect: + text.Add("Allein unterwegs - zusammen spielen: im Launcher unter Freunde & Lobby"); + return; case Multiplayer.NetStatus.Offline: - text.Add($"Offline - [F8] verbinden mit {Multiplayer.ServerDisplay} als {Multiplayer.PlayerName}"); + text.Add($"Offline - verbinde gleich neu mit {Multiplayer.ServerDisplay} als {Multiplayer.PlayerName}"); if (Multiplayer.LastError.Length > 0) text.Add(" " + Multiplayer.LastError); return; case Multiplayer.NetStatus.Connecting: @@ -77,7 +80,7 @@ namespace BSO } string ping = Multiplayer.PingMs >= 0 ? $"{Multiplayer.PingMs} ms" : "..."; - text.Add($"Online: \"{Multiplayer.ServerName}\" ({ping}) - {Multiplayer.Players.Count + 1} Spieler [F8] trennen"); + text.Add($"Online: \"{Multiplayer.ServerName}\" ({ping}) - {Multiplayer.Players.Count + 1} Spieler"); foreach (Multiplayer.Remote p in Multiplayer.Players.Values.OrderBy(p => p.HasState ? p.State.DistanceTo(me) : float.MaxValue)) { if (!p.HasState || p.State.Mode == PlayerMode.Menu) { text.Add($" {p.Name,-16} im Menue"); continue; } diff --git a/README.md b/README.md index fb23dc6..08012bf 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,6 @@ Test ohne Spiel: `Loader\ProxyTest.cpp` lädt die Proxy-DLL wie das Spiel (Bauen | Taste | | |---|---| -| **F8** | mit dem Server verbinden / trennen | | **F9** | Anzeige ein/aus | | **F10** | alle Objekte der Szene nach `Bin_High_Win64\BSO\entities.txt` (Diagnose) | | **F11** | Vollbild im Fenster ↔ normales Fenster (nur wenn im Spiel „Vollbild“ aus ist) | diff --git a/release.ps1 b/release.ps1 index cd7ce65..9e53edb 100644 --- a/release.ps1 +++ b/release.ps1 @@ -1,13 +1,15 @@ -# Signiert ein Release und laedt es auf die BSO-Website (wie release.ps1 bei MSCO). -# .\release.ps1 -Product mod|launcher|server -Version 0.2.0 -File dist\BSO-Mod.zip [-Changelog "..."] -# Laeuft auch automatisch bei "dotnet publish -p:BsoRelease=true" (Launcher und Server, siehe .csproj). +# Signiert ein Release und veroeffentlicht es als Gitea-Release auf git.mbm.wtf (Launcher und Website holen es von dort). +# .\release.ps1 -Product mod|launcher -Version 0.5.0 -File dist\BSO-Mod.zip [-Changelog "..."] +# Laeuft auch automatisch bei "dotnet publish -p:BsoRelease=true" (Launcher) bzw. "build.ps1 -Release" (Mod). +# - Repo: mod -> MrMaho/BSO-Mod, launcher -> MrMaho/BSO-Launcher; Tag v (wird auf dem Standard-Branch angelegt). # - Ohne -Changelog: der Abschnitt "## " aus CHANGELOG.md neben diesem Skript. -# - Ist -File ein Ordner (Server: publish\), wird er ohne .pdb-Dateien gezippt. -# Braucht: Release-Schluessel (BSO-Launcher\new-release-key.ps1), BSO_RELEASE_TOKEN (= RELEASE_TOKEN der Website), -# optional BSO_MASTER_URL (Standard https://bso.mbm.wtf). Beide auch als Windows-Benutzervariable. -# Signiert wird "BSO-RELEASE|produkt|version|sha256" - der Launcher prueft das mit dem eingebauten oeffentlichen Schluessel. +# - Dateien im Release: die Datei (Launcher als BSO-Launcher-Setup.exe) und bso-release.json mit Version, SHA-256 und +# Signatur. Signiert wird "BSO-RELEASE|produkt|version|sha256" - der Launcher prueft das mit dem eingebauten +# oeffentlichen Schluessel, egal woher die Datei kommt. +# Braucht: Release-Schluessel (BSO-Launcher\new-release-key.ps1) und BSO_GITEA_TOKEN (Gitea: Einstellungen -> Anwendungen +# -> Token mit "repository: Lesen und Schreiben"), auch als Windows-Benutzervariable. Optional BSO_GITEA_URL. param( - [Parameter(Mandatory)][ValidateSet("mod", "launcher", "server")][string]$Product, + [Parameter(Mandatory)][ValidateSet("mod", "launcher")][string]$Product, [Parameter(Mandatory)][string]$Version, [Parameter(Mandatory)][string]$File, [string]$Changelog = "" @@ -22,12 +24,14 @@ function Setting([string]$name) { if (-not $v) { $v = [Environment]::GetEnvironmentVariable($name, "User") } return $v } -$master = ((Setting "BSO_MASTER_URL"), "https://bso.mbm.wtf" | Where-Object { $_ } | Select-Object -First 1).TrimEnd('/') -$token = Setting "BSO_RELEASE_TOKEN" -if (-not $token) { Write-Host "BSO_RELEASE_TOKEN nicht gesetzt - nicht hochgeladen"; return } +$gitea = ((Setting "BSO_GITEA_URL"), "https://git.mbm.wtf" | Where-Object { $_ } | Select-Object -First 1).TrimEnd('/') +$token = Setting "BSO_GITEA_TOKEN" +if (-not $token) { Write-Host "BSO_GITEA_TOKEN nicht gesetzt - kein Release angelegt"; return } $keyFile = Join-Path $env:APPDATA "BSO\release-key.dpapi" if (-not (Test-Path $keyFile)) { throw "Kein Release-Schluessel ($keyFile) - erst new-release-key.ps1 im BSO-Launcher-Repo" } $Version = $Version.Split('+')[0] +$repo = @{ mod = "BSO-Mod"; launcher = "BSO-Launcher" }[$Product] +$api = "$gitea/api/v1/repos/MrMaho/$repo" # Changelog aus CHANGELOG.md: alles unter "## " bis zur naechsten "## "-Zeile if (-not $Changelog) { @@ -44,15 +48,8 @@ if (-not $Changelog) { if ($Changelog) { Write-Host "Changelog aus CHANGELOG.md ($Version)" } else { Write-Host "Kein Changelog fuer $Version in CHANGELOG.md" } } -# Ordner (dotnet publish) -> zip $path = (Resolve-Path $File).Path -if (Test-Path $path -PathType Container) { - $zip = Join-Path $env:TEMP "BSO-$Product-$Version.zip" - Remove-Item $zip -ErrorAction SilentlyContinue - Compress-Archive -Path (Get-ChildItem $path -File | Where-Object Extension -ne ".pdb").FullName -DestinationPath $zip - $path = $zip -} - +$fileName = if ($Product -eq "launcher") { "BSO-Launcher-Setup.exe" } else { Split-Path $path -Leaf } $data = [IO.File]::ReadAllBytes($path) $sha = ([System.Security.Cryptography.SHA256]::Create().ComputeHash($data) | ForEach-Object { $_.ToString("x2") }) -join "" $private = [System.Security.Cryptography.ProtectedData]::Unprotect([IO.File]::ReadAllBytes($keyFile), $null, @@ -62,18 +59,27 @@ $rsa.FromXmlString([Text.Encoding]::UTF8.GetString($private)) $message = [Text.Encoding]::UTF8.GetBytes("BSO-RELEASE|$Product|$Version|$sha") $signature = [Convert]::ToBase64String($rsa.SignData($message, [System.Security.Cryptography.HashAlgorithmName]::SHA256, [System.Security.Cryptography.RSASignaturePadding]::Pkcs1)) +$manifest = [ordered]@{ product = $Product; version = $Version; sha256 = $sha; size = $data.Length; file = $fileName; signature = $signature } | + ConvertTo-Json -Compress $http = New-Object System.Net.Http.HttpClient $http.Timeout = [TimeSpan]::FromMinutes(10) -$http.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", $token) -$form = New-Object System.Net.Http.MultipartFormDataContent -foreach ($pair in @(@("product", $Product), @("version", $Version), @("changelog", $Changelog), @("signature", $signature))) { - $form.Add((New-Object System.Net.Http.StringContent($pair[1])), "`"$($pair[0])`"") -} -$content = New-Object System.Net.Http.ByteArrayContent(, $data) -$form.Add($content, "`"file`"", "`"$(Split-Path $path -Leaf)`"") -$res = $http.PostAsync("$master/api/releases/upload", $form).Result +$http.DefaultRequestHeaders.Authorization = New-Object System.Net.Http.Headers.AuthenticationHeaderValue("token", $token) + +# Release anlegen (Tag v) +$json = @{ tag_name = "v$Version"; name = "$Product $Version"; body = $Changelog; draft = $false; prerelease = $false } | ConvertTo-Json -Compress +$res = $http.PostAsync("$api/releases", (New-Object System.Net.Http.StringContent($json, [Text.Encoding]::UTF8, "application/json"))).Result $body = $res.Content.ReadAsStringAsync().Result -if ($res.StatusCode -eq [Net.HttpStatusCode]::Conflict) { Write-Host "$Product $Version gibt es schon auf $master - Version in der .csproj erhoehen"; return } -if (-not $res.IsSuccessStatusCode) { throw "Hochladen fehlgeschlagen ($([int]$res.StatusCode)): $body" } -Write-Host "$Product $Version hochgeladen nach $master (sha256 $sha)" +if ($res.StatusCode -eq [Net.HttpStatusCode]::Conflict) { Write-Host "$repo v$Version gibt es schon - Version erhoehen"; return } +if (-not $res.IsSuccessStatusCode) { throw "Release anlegen fehlgeschlagen ($([int]$res.StatusCode)): $body" } +$id = ($body | ConvertFrom-Json).id + +function Upload([string]$name, [byte[]]$bytes) { + $form = New-Object System.Net.Http.MultipartFormDataContent + $form.Add((New-Object System.Net.Http.ByteArrayContent(, $bytes)), "`"attachment`"", "`"$name`"") + $r = $http.PostAsync("$api/releases/$id/assets?name=$([Uri]::EscapeDataString($name))", $form).Result + if (-not $r.IsSuccessStatusCode) { throw "Hochladen von $name fehlgeschlagen ($([int]$r.StatusCode)): $($r.Content.ReadAsStringAsync().Result)" } +} +Upload $fileName $data +Upload "bso-release.json" ([Text.Encoding]::UTF8.GetBytes($manifest)) +Write-Host "$Product $Version veroeffentlicht: $gitea/MrMaho/$repo/releases/tag/v$Version (sha256 $sha)"