Skip to content

Commit 4374dbc

Browse files
DEATHB4DEFEATike709ike709PJB3005
authored
Cherry Pick Some Space Wizards Commits (#23)
Co-authored-by: ike709 <[email protected]> Co-authored-by: ike709 <[email protected]> Co-authored-by: Pieter-Jan Briers <[email protected]> Co-authored-by: PJB3005 <[email protected]>
1 parent b92970d commit 4374dbc

18 files changed

+310
-62
lines changed

.github/workflows/build-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Setup .NET
1818
uses: actions/setup-dotnet@v3
1919
with:
20-
dotnet-version: 7.0.x
20+
dotnet-version: 9.0.x
2121
- name: Install dependencies
2222
run: dotnet restore
2323
- name: Build

.github/workflows/publish-release.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ jobs:
1515
with:
1616
submodules: 'recursive'
1717

18+
- name: Setup .NET
19+
uses: actions/setup-dotnet@v3
20+
with:
21+
dotnet-version: 9.0.x
22+
1823
- name: Create build
1924
run: ./publish.sh
2025

Launcher.props

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@
66
1. Packaging scripts.
77
2. Download .NET runtime script.
88
3. Local dev SS14.Loader launching code.
9+
4. build-test.yml GitHub Actions workflow
910
-->
10-
<TargetFramework>net8.0</TargetFramework>
11-
<Version>1.2.1</Version>
11+
<TargetFramework>net9.0</TargetFramework>
12+
<Version>1.3.1</Version>
13+
14+
<!--
15+
CET breaks for people on older, but still supported Windows versions:
16+
https://github.com/space-wizards/SS14.Launcher/issues/198
17+
-->
18+
<CETCompat>false</CETCompat>
1219
</PropertyGroup>
1320
</Project>

SS14.Launcher/Assets/Locale/en-US/text.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ connecting-update-status-loading-into-db = Storing assets in database…
4747
connecting-update-status-loading-content-bundle = Loading content bundle…
4848
connecting-update-status-unknown = You shouldn't see this
4949
50+
connecting-privacy-policy-text = This server requires that you accept its privacy policy before connecting.
51+
connecting-privacy-policy-text-version-changed = This server has updated its privacy policy since the last time you played. You must accept the new version before connecting.
52+
connecting-privacy-policy-view = View privacy policy
53+
connecting-privacy-policy-accept = Accept (continue)
54+
connecting-privacy-policy-decline = Decline (disconnect)
55+
5056
## Strings for the "direct connect" dialog window.
5157

5258
direct-connect-title = Direct Connect…

SS14.Launcher/Helpers.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Threading;
99
using System.Threading.Tasks;
1010
using Mono.Unix;
11+
using Serilog;
1112
using TerraFX.Interop.Windows;
1213

1314
namespace SS14.Launcher;
@@ -88,6 +89,27 @@ await Task.Run(async () =>
8889
}, cancel);
8990
}
9091

92+
/// <summary>
93+
/// Open a URI provided by a game server in the user's browser. Refuse to open anything other than http/https.
94+
/// </summary>
95+
/// <param name="uri">The URI to open.</param>
96+
public static void SafeOpenServerUri(string uri)
97+
{
98+
if (!Uri.TryCreate(uri, UriKind.Absolute, out var parsedUri))
99+
{
100+
Log.Error("Unable to parse URI in server-provided link: {Link}", uri);
101+
return;
102+
}
103+
104+
if (parsedUri.Scheme is not ("http" or "https"))
105+
{
106+
Log.Error("Refusing to open server-provided link {Link}, only http/https are allowed", parsedUri);
107+
return;
108+
}
109+
110+
OpenUri(parsedUri.ToString());
111+
}
112+
91113
public static void OpenUri(Uri uri)
92114
{
93115
OpenUri(uri.ToString());

SS14.Launcher/Models/Connector.cs

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ public class Connector : ReactiveObject
3838
private bool _clientExitedBadly;
3939
private readonly HttpClient _http;
4040

41+
private TaskCompletionSource<PrivacyPolicyAcceptResult>? _acceptPrivacyPolicyTcs;
42+
private ServerPrivacyPolicyInfo? _serverPrivacyPolicyInfo;
43+
private bool _privacyPolicyDifferentVersion;
44+
4145
public Connector()
4246
{
4347
_updater = Locator.Current.GetRequiredService<Updater>();
@@ -59,6 +63,13 @@ public bool ClientExitedBadly
5963
private set => this.RaiseAndSetIfChanged(ref _clientExitedBadly, value);
6064
}
6165

66+
public ServerPrivacyPolicyInfo? PrivacyPolicyInfo => _serverPrivacyPolicyInfo;
67+
public bool PrivacyPolicyDifferentVersion
68+
{
69+
get => _privacyPolicyDifferentVersion;
70+
private set => this.RaiseAndSetIfChanged(ref _privacyPolicyDifferentVersion, value);
71+
}
72+
6273
public async void Connect(string address, CancellationToken cancel = default)
6374
{
6475
try
@@ -75,6 +86,10 @@ public async void Connect(string address, CancellationToken cancel = default)
7586
Log.Information(e, "Cancelled connect");
7687
Status = ConnectionStatus.Cancelled;
7788
}
89+
finally
90+
{
91+
Cleanup();
92+
}
7893
}
7994

8095
public async void LaunchContentBundle(IStorageFile file, CancellationToken cancel = default)
@@ -95,6 +110,10 @@ public async void LaunchContentBundle(IStorageFile file, CancellationToken cance
95110
Log.Information(e, "Cancelled launch");
96111
Status = ConnectionStatus.Cancelled;
97112
}
113+
finally
114+
{
115+
Cleanup();
116+
}
98117
}
99118

100119
private async Task ConnectInternalAsync(string address, CancellationToken cancel)
@@ -103,6 +122,8 @@ private async Task ConnectInternalAsync(string address, CancellationToken cancel
103122

104123
var (info, parsedAddr, infoAddr) = await GetServerInfoAsync(address, cancel);
105124

125+
await HandlePrivacyPolicyAsync(info, cancel);
126+
106127
// Run update.
107128
Status = ConnectionStatus.Updating;
108129

@@ -113,6 +134,80 @@ private async Task ConnectInternalAsync(string address, CancellationToken cancel
113134
await LaunchClientWrap(installation, info, info.BuildInformation, connectAddress, parsedAddr, false, cancel);
114135
}
115136

137+
private async Task HandlePrivacyPolicyAsync(ServerInfo info, CancellationToken cancel)
138+
{
139+
if (info.PrivacyPolicy == null)
140+
{
141+
// Server has no privacy policy configured, nothing to do.
142+
return;
143+
}
144+
145+
var identifier = info.PrivacyPolicy.Identifier;
146+
var version = info.PrivacyPolicy.Version;
147+
148+
if (_cfg.HasAcceptedPrivacyPolicy(identifier, out var acceptedVersion))
149+
{
150+
if (version == acceptedVersion)
151+
{
152+
Log.Debug(
153+
"User has previously accepted privacy policy {Identifier} with version {Version}",
154+
identifier,
155+
acceptedVersion);
156+
157+
// User has previously accepted privacy policy, update last connected time in DB at least.
158+
_cfg.UpdateConnectedToPrivacyPolicy(identifier);
159+
_cfg.CommitConfig();
160+
return;
161+
}
162+
else
163+
{
164+
Log.Debug("User previously accepted privacy policy but version has changed!");
165+
PrivacyPolicyDifferentVersion = true;
166+
}
167+
}
168+
169+
// Ask user for privacy policy acceptance by waiting here.
170+
Log.Debug("Prompting user for privacy policy acceptance: {Identifer} version {Version}", identifier, version);
171+
_serverPrivacyPolicyInfo = info.PrivacyPolicy;
172+
_acceptPrivacyPolicyTcs = new TaskCompletionSource<PrivacyPolicyAcceptResult>();
173+
174+
Status = ConnectionStatus.AwaitingPrivacyPolicyAcceptance;
175+
var result = await _acceptPrivacyPolicyTcs.Task.WaitAsync(cancel);
176+
177+
if (result == PrivacyPolicyAcceptResult.Accepted)
178+
{
179+
// Yippee they're ok with it.
180+
Log.Debug("User accepted privacy policy");
181+
_cfg.AcceptPrivacyPolicy(identifier, version);
182+
_cfg.CommitConfig();
183+
return;
184+
}
185+
186+
// They're not ok with it. Just throw cancellation so the code cleans up I guess.
187+
// We could just have the connection screen treat "deny" as a cancellation op directly,
188+
// but that would make the logs less clear.
189+
Log.Information("User denied privacy policy, cancelling connection attempt!");
190+
throw new OperationCanceledException();
191+
}
192+
193+
public void ConfirmPrivacyPolicy(PrivacyPolicyAcceptResult result)
194+
{
195+
if (_acceptPrivacyPolicyTcs == null)
196+
{
197+
Log.Error("_acceptPrivacyPolicyTcs is null???");
198+
return;
199+
}
200+
201+
_acceptPrivacyPolicyTcs.SetResult(result);
202+
}
203+
204+
private void Cleanup()
205+
{
206+
_serverPrivacyPolicyInfo = null;
207+
_acceptPrivacyPolicyTcs = null;
208+
PrivacyPolicyDifferentVersion = default;
209+
}
210+
116211
private async Task LaunchContentBundleInternal(IStorageFile file, CancellationToken cancel)
117212
{
118213
Status = ConnectionStatus.Updating;
@@ -600,7 +695,7 @@ private static async Task<ProcessStartInfo> GetLoaderStartInfo()
600695
basePath = Path.GetFullPath(Path.Combine(
601696
LauncherPaths.DirLauncherInstall,
602697
"..", "..", "..", "..",
603-
"SS14.Loader", "bin", "Debug", "net8.0"));
698+
"SS14.Loader", "bin", "Debug", "net9.0"));
604699
}
605700

606701
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
@@ -671,6 +766,7 @@ public enum ConnectionStatus
671766
Updating,
672767
UpdateError,
673768
Connecting,
769+
AwaitingPrivacyPolicyAcceptance,
674770
ConnectionFailed,
675771
StartingClient,
676772
ClientRunning,
@@ -714,3 +810,9 @@ public sealed record ContentBundleBaseBuild(
714810
[property: JsonPropertyName("manifest_url")] string? ManifestUrl,
715811
[property: JsonPropertyName("manifest_hash")] string? ManifestHash
716812
);
813+
814+
public enum PrivacyPolicyAcceptResult
815+
{
816+
Denied,
817+
Accepted,
818+
}

SS14.Launcher/Models/Data/DataManager.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ public sealed class DataManager : ReactiveObject
6161
private readonly List<DbCommand> _dbCommandQueue = new();
6262
private readonly SemaphoreSlim _dbWritingSemaphore = new(1);
6363

64+
// Privacy policy IDs accepted along with the last accepted version.
65+
private readonly Dictionary<string, string> _acceptedPrivacyPolicies = new();
66+
6467
static DataManager()
6568
{
6669
SqlMapper.AddTypeHandler(new GuidTypeHandler());
@@ -267,6 +270,43 @@ public void SetHubs(List<Hub> hubs)
267270
CommitConfig();
268271
}
269272

273+
public bool HasAcceptedPrivacyPolicy(string privacyPolicy, [NotNullWhen(true)] out string? version)
274+
{
275+
return _acceptedPrivacyPolicies.TryGetValue(privacyPolicy, out version);
276+
}
277+
278+
public void AcceptPrivacyPolicy(string privacyPolicy, string version)
279+
{
280+
if (_acceptedPrivacyPolicies.ContainsKey(privacyPolicy))
281+
{
282+
// Accepting new version
283+
AddDbCommand(db => db.Execute("""
284+
UPDATE AcceptedPrivacyPolicy
285+
SET Version = @Version, LastConnected = DATETIME('now')
286+
WHERE Identifier = @Identifier
287+
""", new { Identifier = privacyPolicy, Version = version }));
288+
}
289+
else
290+
{
291+
// Accepting new privacy policy entirely.
292+
AddDbCommand(db => db.Execute("""
293+
INSERT OR REPLACE INTO AcceptedPrivacyPolicy (Identifier, Version, AcceptedTime, LastConnected)
294+
VALUES (@Identifier, @Version, DATETIME('now'), DATETIME('now'))
295+
""", new { Identifier = privacyPolicy, Version = version }));
296+
}
297+
298+
_acceptedPrivacyPolicies[privacyPolicy] = version;
299+
}
300+
301+
public void UpdateConnectedToPrivacyPolicy(string privacyPolicy)
302+
{
303+
AddDbCommand(db => db.Execute("""
304+
UPDATE AcceptedPrivacyPolicy
305+
SET LastConnected = DATETIME('now')
306+
WHERE Version = @Version
307+
""", new { Version = privacyPolicy }));
308+
}
309+
270310
/// <summary>
271311
/// Loads config file from disk, or resets the loaded config to default if the config doesn't exist on disk.
272312
/// </summary>
@@ -364,6 +404,12 @@ private void LoadSqliteConfig(SqliteConnection sqliteConnection)
364404
_filters.UnionWith(sqliteConnection.Query<ServerFilter>("SELECT Category, Data FROM ServerFilter"));
365405
_hubs.AddRange(sqliteConnection.Query<Hub>("SELECT Address,Priority FROM Hub"));
366406

407+
foreach (var (identifier, version) in sqliteConnection.Query<(string, string)>(
408+
"SELECT Identifier, Version FROM AcceptedPrivacyPolicy"))
409+
{
410+
_acceptedPrivacyPolicies[identifier] = version;
411+
}
412+
367413
// Avoid DB commands from config load.
368414
_dbCommandQueue.Clear();
369415
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
-- Represents privacy policies that have been accepted by the user.
2+
-- Each policy is stored with its server-unique identifier and version value.
3+
CREATE TABLE AcceptedPrivacyPolicy(
4+
-- The "identifier" field from the privacy_policy server info response.
5+
Identifier TEXT NOT NULL PRIMARY KEY,
6+
7+
-- The "version" field from the privacy_policy server info response.
8+
Version TEXT NOT NULL,
9+
10+
-- The time the user accepted the privacy policy for the first time.
11+
AcceptedTime DATETIME NOT NULL,
12+
13+
-- The last time the user connected to a server using this privacy policy.
14+
-- Intended to enable culling of this table for servers the user has not connected to in a long time,
15+
-- though this is not currently implemented at the time of writing.
16+
LastConnected DATETIME NOT NULL
17+
);

SS14.Launcher/Models/ServerInfo.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ public sealed class ServerInfo
1515

1616
[JsonPropertyName("desc")] public string? Desc { get; set; }
1717
[JsonPropertyName("links")] public ServerInfoLink[]? Links { get; set; }
18+
19+
[JsonPropertyName("privacy_policy")] public ServerPrivacyPolicyInfo? PrivacyPolicy { get; set; }
1820
}
1921

2022
public sealed record ServerInfoLink(string Name, string? Icon, string Url);
@@ -67,3 +69,10 @@ public enum AuthMode
6769
Required = 1,
6870
Disabled = 2
6971
}
72+
73+
public sealed record ServerPrivacyPolicyInfo(
74+
[property: JsonPropertyName("link")] string Link,
75+
[property: JsonPropertyName("identifier")]
76+
string Identifier,
77+
[property: JsonPropertyName("version")]
78+
string Version);

0 commit comments

Comments
 (0)