Files
bd2/plugins/CaptureEnvironment/CaptureStorageIsolation.cs
T

109 lines
4.1 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using BD2.GameNames;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using static BD2.GameNames.Game;
namespace Bd2CaptureEnvironment;
internal static class CaptureStorageIsolation
{
private const string PlayerPrefsPrefix = "BD2OfficialCapture:" + Bd2Build.Versions.Game + ":";
private static ManualLogSource Log;
internal static string DataDirectory { get; private set; }
internal static string GameDataDirectory { get; private set; }
internal static void Initialize(string root, ManualLogSource log)
{
Log = log;
DataDirectory = Path.GetFullPath(Path.Combine(root, "IsolatedUserData"));
GameDataDirectory = Path.Combine(DataDirectory, "Data", "t");
EnsurePrivateGameDataDirectory();
}
private static void EnsurePrivateGameDataDirectory()
{
foreach (string path in new[]
{
DataDirectory,
Path.Combine(DataDirectory, "Data"),
GameDataDirectory
})
{
if (Directory.Exists(path) &&
(new DirectoryInfo(path).Attributes & FileAttributes.ReparsePoint) != 0)
throw new InvalidOperationException("Isolated GameData path is linked: " + path);
Directory.CreateDirectory(path);
}
}
internal static void Install(Harmony harmony)
{
PropertyInfo persistent = typeof(Application).GetGameProperty(
"persistentDataPath", BindingFlags.Static | BindingFlags.Public);
MethodInfo getter = (persistent?.GetGetMethod()) ?? throw new MissingMethodException("Application.persistentDataPath getter not found");
harmony.Patch(getter, prefix: new HarmonyMethod(
typeof(CaptureStorageIsolation), nameof(PersistentDataPathPrefix)));
var names = new HashSet<string>(StringComparer.Ordinal)
{
"GetString", "SetString", "GetInt", "SetInt", "GetFloat", "SetFloat",
"HasKey", "DeleteKey"
};
foreach (MethodInfo method in typeof(PlayerPrefs).GetMethods(
BindingFlags.Static | BindingFlags.Public))
{
ParameterInfo[] parameters = method.GetParameters();
if (!names.Contains(method.Name) || parameters.Length == 0 ||
parameters[0].ParameterType != typeof(string))
continue;
harmony.Patch(method, prefix: new HarmonyMethod(
typeof(CaptureStorageIsolation), nameof(PlayerPrefsKeyPrefix)));
}
MethodInfo deleteAll = typeof(PlayerPrefs).GetGameMethod(
"DeleteAll", BindingFlags.Static | BindingFlags.Public,
null, Type.EmptyTypes, null);
if (deleteAll != null)
harmony.Patch(deleteAll, prefix: new HarmonyMethod(
typeof(CaptureStorageIsolation), nameof(BlockPlayerPrefsDeleteAll)));
}
private static bool PersistentDataPathPrefix(ref string __result)
{
__result = DataDirectory;
return false;
}
private static void PlayerPrefsKeyPrefix(ref string key)
{
if (key != null && !key.StartsWith(PlayerPrefsPrefix, StringComparison.Ordinal))
key = PlayerPrefsPrefix + key;
}
private static bool BlockPlayerPrefsDeleteAll()
{
Log?.LogWarning("Blocked PlayerPrefs.DeleteAll to protect the primary client profile");
return false;
}
internal static void InstallGameDataPath(Harmony harmony)
{
MethodInfo gameDataPath = Game.Method<BDNetwork.NetworkManager>(network => network.GetPachedGameDataPath());
if (gameDataPath == null || gameDataPath.ReturnType != typeof(string))
throw new MissingMethodException("NetworkManager.GetPachedGameDataPath not found");
harmony.Patch(gameDataPath, prefix: new HarmonyMethod(
typeof(CaptureStorageIsolation), nameof(GameDataPathPrefix)));
Log?.LogInfo("Private installed GameData path hook installed");
}
private static bool GameDataPathPrefix(ref string __result)
{
__result = GameDataDirectory;
return false;
}
}