108 lines
3.2 KiB
C#
108 lines
3.2 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using BepInEx;
|
|
|
|
namespace Bd2LocalIdentity;
|
|
|
|
// The server's UpdateAgeGate response is intentionally empty. Once that
|
|
// response succeeds, the only client-side fact needed on the next login is
|
|
// that this local installation has completed the age-gate flow. Do not keep
|
|
// the date of birth: it is not consumed by the client after the request and
|
|
// retaining it would add unnecessary personal data to a local plugin file.
|
|
internal static class AgeGateState
|
|
{
|
|
private const string FileName = "bd2.localidentity.age-gate.state";
|
|
private const string Content = "format=1\nconfirmed=true\n";
|
|
private static readonly object Gate = new();
|
|
private static bool? confirmed;
|
|
|
|
internal static bool IsConfirmed()
|
|
{
|
|
lock (Gate)
|
|
{
|
|
if (confirmed.HasValue)
|
|
{
|
|
return confirmed.Value;
|
|
}
|
|
|
|
try
|
|
{
|
|
string path = StatePath();
|
|
confirmed = File.Exists(path) &&
|
|
string.Equals(File.ReadAllText(path, Encoding.UTF8), Content, StringComparison.Ordinal);
|
|
}
|
|
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
|
|
{
|
|
confirmed = false;
|
|
Plugin.LogWarning("Could not read local age-gate state: " + ex.Message);
|
|
}
|
|
|
|
return confirmed.Value;
|
|
}
|
|
}
|
|
|
|
internal static void MarkConfirmed()
|
|
{
|
|
lock (Gate)
|
|
{
|
|
if (confirmed == true)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string path = StatePath();
|
|
string directory = Path.GetDirectoryName(path);
|
|
if (string.IsNullOrEmpty(directory))
|
|
{
|
|
throw new IOException("BepInEx configuration directory is unavailable.");
|
|
}
|
|
|
|
Directory.CreateDirectory(directory);
|
|
string temporaryPath = path + "." + Guid.NewGuid().ToString("N") + ".tmp";
|
|
try
|
|
{
|
|
WriteThrough(temporaryPath, Content);
|
|
if (File.Exists(path))
|
|
{
|
|
// Both files are in BepInEx/config, so replacement is an
|
|
// atomic same-volume operation on the supported Windows client.
|
|
File.Replace(temporaryPath, path, null);
|
|
}
|
|
else
|
|
{
|
|
File.Move(temporaryPath, path);
|
|
}
|
|
|
|
confirmed = true;
|
|
}
|
|
finally
|
|
{
|
|
if (File.Exists(temporaryPath))
|
|
{
|
|
File.Delete(temporaryPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string StatePath()
|
|
{
|
|
return Path.Combine(Paths.ConfigPath, FileName);
|
|
}
|
|
|
|
private static void WriteThrough(string path, string content)
|
|
{
|
|
byte[] bytes = new UTF8Encoding(false).GetBytes(content);
|
|
using var stream = new FileStream(
|
|
path,
|
|
FileMode.CreateNew,
|
|
FileAccess.Write,
|
|
FileShare.None,
|
|
4096,
|
|
FileOptions.WriteThrough);
|
|
stream.Write(bytes, 0, bytes.Length);
|
|
stream.Flush(true);
|
|
}
|
|
}
|