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
11 changes: 11 additions & 0 deletions Source/Client/Settings/MpSettingsUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ public static void DoGeneralSettings(MpSettings settings, Rect inRect, Rect page
listing.TextFieldNumericLabeled("Desync radius: ", ref settings.desyncTracesRadius, ref desyncRadiusBuffer, 1f,
200f);

if (MpVersion.IsDebug && FileAssoc.IsSupported())
{
if (FileAssoc.IsRegistered())
{
if (listing.ButtonText("Remove file associations")) FileAssoc.Remove();
}
else
{
if (listing.ButtonText("Register file associations")) FileAssoc.Register();
}
}
#if DEBUG
using (MpStyle.Set(TextAnchor.MiddleCenter))
if (listing.ButtonTextLabeled("Desync tracing mode", settings.desyncTracingMode.ToString()))
Expand Down
77 changes: 77 additions & 0 deletions Source/Client/Util/FileAssoc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#nullable enable
using System;
using System.Diagnostics;
using System.IO;
using Microsoft.Win32;
using UnityEngine;

namespace Multiplayer.Client.Util;

/// Only Windows support for now. Calling methods of this class from another OS is a no-op.
public static class FileAssoc
{
public const string ReplayProgId = "RimworldMultiplayer.Replay.1";
public const string ReplayExtension = ".rwmts"; // rimworld multiplayer save

// Create the associations only for the current user.
// RimWorld is not installed system-wide, so this works fine and avoids requiring admin rights.
private static RegistryKey GetAssociationsRootKey() =>
CreateSubKeyOrThrow(Registry.CurrentUser, @"Software\Classes");

public static void Register()
{
if (!IsSupported()) return;
var appPath = Process.GetCurrentProcess().MainModule?.FileName ?? Environment.GetCommandLineArgs()[0];
appPath = Path.GetFullPath(appPath);
RegisterApp(appPath);
RegisterExtension(ReplayExtension);
}

public static void Remove()
{
if (!IsSupported()) return;
var rootKey = GetAssociationsRootKey();
rootKey.DeleteSubKeyTree(ReplayProgId, throwOnMissingSubKey: false);
rootKey.DeleteSubKey(ReplayExtension, throwOnMissingSubKey: false);
}

public static bool IsRegistered()
{
if (!IsSupported()) return false;
var rootKey = GetAssociationsRootKey();
return rootKey.OpenSubKey(ReplayProgId) != null || rootKey.OpenSubKey(ReplayExtension) != null;
}

public static bool IsSupported() => Application.platform == RuntimePlatform.WindowsPlayer;

private static void RegisterApp(string appPath)
{
// https://learn.microsoft.com/en-us/windows/win32/shell/fa-progids
var rootKey = GetAssociationsRootKey();
using var progIdKey = CreateSubKeyOrThrow(rootKey, ReplayProgId);
progIdKey.SetValue("", "RimWorld Multiplayer Save");
progIdKey.SetValue("FriendlyTypeName", "RimWorld Multiplayer Save");

using (var defaultIcon = CreateSubKeyOrThrow(progIdKey, "DefaultIcon"))
{
defaultIcon.SetValue("", $"\"{appPath}\",0");
}

using (var command = CreateSubKeyOrThrow(progIdKey, @"shell\open\command"))
{
command.SetValue("", $"\"{appPath}\" -{MultiplayerStatic.MpHostReplayCmdLineArg}=\"%1\"",
RegistryValueKind.ExpandString);
}
}

private static void RegisterExtension(string ext)
{
if (!ext.StartsWith(".")) ext = "." + ext;
using var extKey = CreateSubKeyOrThrow(GetAssociationsRootKey(), ext);
extKey.SetValue("", ReplayProgId);
}

private static RegistryKey CreateSubKeyOrThrow(RegistryKey parent, string subkey) =>
parent.CreateSubKey(subkey) ??
throw new InvalidOperationException($"Cannot access registry key {parent.Name}\\{subkey}");
}
Loading