using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using FishNet;
using FishNet.Broadcast;
using FishNet.Connection;
using FishNet.Managing.Server;
using FishNet.Object;
using FishNet.Serializing;
using FishNet.Transporting;
using HarmonyLib;
using HowtoFishIdentityAPI.Runtime;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace HowtoFishIdentityAPI
{
[BepInPlugin("IceBoxStudio.HowToFish.IdentityAPI", "HowtoFishIdentityAPI", "1.0.0")]
public sealed class HowtoFishIdentityPlugin : BaseUnityPlugin
{
private void Awake()
{
//IL_0005: Unknown result type (might be due to invalid IL or missing references)
new Harmony("IceBoxStudio.HowToFish.IdentityAPI").PatchAll(Assembly.GetExecutingAssembly());
((BaseUnityPlugin)this).Logger.LogInfo((object)"HowtoFishIdentityAPI 1.0.0 initialized.");
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "IceBoxStudio.HowToFish.IdentityAPI";
public const string PLUGIN_NAME = "HowtoFishIdentityAPI";
public const string PLUGIN_VERSION = "1.0.0";
}
}
namespace HowtoFishIdentityAPI.Runtime
{
internal static class ClientIdentity
{
private static string _privateKey;
private static string _publicKey;
internal static string PublicKey
{
get
{
Load();
return _publicKey ?? string.Empty;
}
}
internal static bool Sign(string challenge, out string proof)
{
proof = string.Empty;
Load();
if (string.IsNullOrEmpty(_privateKey) || !TryBytes(challenge, 32, out var bytes))
{
return false;
}
try
{
using RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider();
rSACryptoServiceProvider.FromXmlString(_privateKey);
proof = Convert.ToBase64String(rSACryptoServiceProvider.SignData(bytes, "SHA256"));
return true;
}
catch (Exception)
{
return false;
}
}
internal static bool Verify(string publicKey, string challenge, string proof)
{
if (!TryPublicKey(publicKey, out var xml) || !TryBytes(challenge, 32, out var bytes) || !TryBytes(proof, 512, out var bytes2))
{
return false;
}
try
{
using RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider();
rSACryptoServiceProvider.FromXmlString(xml);
return rSACryptoServiceProvider.VerifyData(bytes, "SHA256", bytes2);
}
catch (Exception)
{
return false;
}
}
internal static bool TryPublicKey(string key, out string xml)
{
xml = string.Empty;
if (!TryBytes(key, 2048, out var bytes))
{
return false;
}
try
{
string text = Encoding.UTF8.GetString(bytes);
if (text.IndexOf("<D>", StringComparison.OrdinalIgnoreCase) >= 0)
{
return false;
}
using RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider();
rSACryptoServiceProvider.FromXmlString(text);
xml = rSACryptoServiceProvider.ToXmlString(includePrivateParameters: false);
return true;
}
catch (Exception)
{
return false;
}
}
private static void Load()
{
if (_privateKey != null)
{
return;
}
try
{
string text = Path.Combine(Application.persistentDataPath, "Saves");
string text2 = Path.Combine(text, "HowtoFishIdentityAPI.identity");
string text3 = Path.Combine(text, "PersonalMoney.identity");
if (!File.Exists(text2) && File.Exists(text3))
{
Directory.CreateDirectory(text);
File.Copy(text3, text2);
}
if (File.Exists(text2))
{
using (RSACryptoServiceProvider rSACryptoServiceProvider = new RSACryptoServiceProvider())
{
rSACryptoServiceProvider.FromXmlString(File.ReadAllText(text2));
_privateKey = rSACryptoServiceProvider.ToXmlString(includePrivateParameters: true);
_publicKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(rSACryptoServiceProvider.ToXmlString(includePrivateParameters: false)));
return;
}
}
using (RSACryptoServiceProvider rSACryptoServiceProvider2 = new RSACryptoServiceProvider(2048))
{
_privateKey = rSACryptoServiceProvider2.ToXmlString(includePrivateParameters: true);
_publicKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(rSACryptoServiceProvider2.ToXmlString(includePrivateParameters: false)));
}
Directory.CreateDirectory(text);
File.WriteAllText(text2, _privateKey);
}
catch (Exception)
{
_privateKey = string.Empty;
_publicKey = string.Empty;
}
}
private static bool TryBytes(string value, int maxLength, out byte[] bytes)
{
bytes = null;
if (string.IsNullOrEmpty(value) || value.Length > (maxLength + 2) / 3 * 4)
{
return false;
}
try
{
bytes = Convert.FromBase64String(value);
return bytes.Length != 0 && bytes.Length <= maxLength;
}
catch (FormatException)
{
return false;
}
}
}
public struct IdentityHelloBroadcast : IBroadcast
{
public string PublicKey;
public IdentityHelloBroadcast(string publicKey)
{
PublicKey = publicKey;
}
}
public struct IdentityChallengeBroadcast : IBroadcast
{
public string Value;
public IdentityChallengeBroadcast(string value)
{
Value = value;
}
}
public struct IdentityProofBroadcast : IBroadcast
{
public string Proof;
public IdentityProofBroadcast(string proof)
{
Proof = proof;
}
}
internal sealed class Challenge
{
internal string PublicKey;
internal string Value;
internal DateTime Expires;
}
internal static class IdentityRuntime
{
private static readonly Dictionary<int, string> Keys = new Dictionary<int, string>();
private static readonly HashSet<int> VerifiedClients = new HashSet<int>();
private static readonly Dictionary<int, Challenge> Challenges = new Dictionary<int, Challenge>();
private static bool _serializersReady;
private static bool _clientRegistered;
private static bool _serverRegistered;
internal static event Action<NetworkConnection> Verified;
internal static bool IsVerified(NetworkConnection connection)
{
if (connection != (NetworkConnection)null)
{
return VerifiedClients.Contains(connection.ClientId);
}
return false;
}
internal static bool TryGetKey(Player player, out string key)
{
key = null;
if ((Object)(object)player == (Object)null || ((NetworkBehaviour)player).Owner == (NetworkConnection)null || !IsVerified(((NetworkBehaviour)player).Owner))
{
return false;
}
string text = SteamKey(player);
if (text != null)
{
key = text;
return true;
}
return Keys.TryGetValue(((NetworkBehaviour)player).Owner.ClientId, out key);
}
internal static bool TryGetKey(NetworkConnection connection, out string key)
{
key = null;
if (connection != (NetworkConnection)null && IsVerified(connection))
{
return Keys.TryGetValue(connection.ClientId, out key);
}
return false;
}
internal static bool IsSteam(Player player)
{
return SteamKey(player) != null;
}
internal static bool IsSteam(NetworkConnection connection)
{
if (TryGetKey(connection, out var key))
{
return key.StartsWith("steam:", StringComparison.Ordinal);
}
return false;
}
internal static void PlayerStarted(Player player)
{
if ((Object)(object)player == (Object)null || ((NetworkBehaviour)player).Owner == (NetworkConnection)null)
{
return;
}
EnsureNetwork();
if (InstanceFinder.IsServerStarted)
{
if (((NetworkBehaviour)player).Owner.IsLocalClient)
{
if (IsSteam(player))
{
Mark(((NetworkBehaviour)player).Owner, SteamKey(player));
}
else
{
Hello(((NetworkBehaviour)player).Owner, ClientIdentity.PublicKey);
}
}
}
else if (((NetworkBehaviour)player).Owner.IsLocalClient)
{
SendHello(player);
}
}
internal static void PlayerStopped(Player player)
{
if (!((Object)(object)player == (Object)null) && !(((NetworkBehaviour)player).Owner == (NetworkConnection)null))
{
VerifiedClients.Remove(((NetworkBehaviour)player).Owner.ClientId);
Keys.Remove(((NetworkBehaviour)player).Owner.ClientId);
Challenges.Remove(((NetworkBehaviour)player).Owner.ClientId);
}
}
internal static void Stop()
{
VerifiedClients.Clear();
Keys.Clear();
Challenges.Clear();
_clientRegistered = false;
_serverRegistered = false;
}
private static void EnsureNetwork()
{
if (!_serializersReady)
{
GenericWriter<IdentityHelloBroadcast>.SetWrite((Action<Writer, IdentityHelloBroadcast>)delegate(Writer writer, IdentityHelloBroadcast message)
{
writer.WriteString(message.PublicKey);
});
GenericReader<IdentityHelloBroadcast>.SetRead((Func<Reader, IdentityHelloBroadcast>)((Reader reader) => new IdentityHelloBroadcast(reader.ReadStringAllocated())));
GenericWriter<IdentityChallengeBroadcast>.SetWrite((Action<Writer, IdentityChallengeBroadcast>)delegate(Writer writer, IdentityChallengeBroadcast message)
{
writer.WriteString(message.Value);
});
GenericReader<IdentityChallengeBroadcast>.SetRead((Func<Reader, IdentityChallengeBroadcast>)((Reader reader) => new IdentityChallengeBroadcast(reader.ReadStringAllocated())));
GenericWriter<IdentityProofBroadcast>.SetWrite((Action<Writer, IdentityProofBroadcast>)delegate(Writer writer, IdentityProofBroadcast message)
{
writer.WriteString(message.Proof);
});
GenericReader<IdentityProofBroadcast>.SetRead((Func<Reader, IdentityProofBroadcast>)((Reader reader) => new IdentityProofBroadcast(reader.ReadStringAllocated())));
_serializersReady = true;
}
if (!_clientRegistered && (Object)(object)InstanceFinder.ClientManager != (Object)null)
{
InstanceFinder.ClientManager.RegisterBroadcast<IdentityChallengeBroadcast>((Action<IdentityChallengeBroadcast, Channel>)OnChallenge);
_clientRegistered = true;
}
if (!_serverRegistered && (Object)(object)InstanceFinder.ServerManager != (Object)null)
{
InstanceFinder.ServerManager.RegisterBroadcast<IdentityHelloBroadcast>((Action<NetworkConnection, IdentityHelloBroadcast, Channel>)OnHello, true);
InstanceFinder.ServerManager.RegisterBroadcast<IdentityProofBroadcast>((Action<NetworkConnection, IdentityProofBroadcast, Channel>)OnProof, true);
_serverRegistered = true;
}
}
private static void OnHello(NetworkConnection connection, IdentityHelloBroadcast message, Channel channel)
{
Hello(connection, message.PublicKey);
}
private static void OnProof(NetworkConnection connection, IdentityProofBroadcast message, Channel channel)
{
Proof(connection, message.Proof);
}
internal static void Hello(NetworkConnection connection, string publicKey)
{
Player val = FindPlayer(connection);
if (connection == (NetworkConnection)null || (Object)(object)val == (Object)null)
{
return;
}
string text = SteamKey(val);
if (text != null)
{
Mark(connection, text);
}
else
{
if (!ClientIdentity.TryPublicKey(publicKey, out var _))
{
return;
}
if (connection.IsLocalClient)
{
Mark(connection, MakeKey(publicKey));
return;
}
byte[] array = new byte[32];
using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create())
{
randomNumberGenerator.GetBytes(array);
}
Challenge challenge = new Challenge
{
PublicKey = publicKey,
Value = Convert.ToBase64String(array),
Expires = DateTime.UtcNow.AddSeconds(20.0)
};
Challenges[connection.ClientId] = challenge;
EnsureNetwork();
ServerManager serverManager = InstanceFinder.ServerManager;
if (serverManager != null)
{
serverManager.Broadcast<IdentityChallengeBroadcast>(connection, new IdentityChallengeBroadcast(challenge.Value), true, (Channel)0);
}
}
}
internal static void Proof(NetworkConnection connection, string proof)
{
if (!(connection == (NetworkConnection)null) && Challenges.TryGetValue(connection.ClientId, out var value))
{
Challenges.Remove(connection.ClientId);
if (!(value.Expires < DateTime.UtcNow) && ClientIdentity.Verify(value.PublicKey, value.Value, proof))
{
Mark(connection, MakeKey(value.PublicKey));
}
}
}
private static void OnChallenge(IdentityChallengeBroadcast message, Channel channel)
{
if (ClientIdentity.Sign(message.Value, out var proof) && !((Object)(object)InstanceFinder.ClientManager == (Object)null))
{
InstanceFinder.ClientManager.Broadcast<IdentityProofBroadcast>(new IdentityProofBroadcast(proof), (Channel)0);
}
}
private static void SendHello(Player player)
{
if (!((Object)(object)player == (Object)null) && !((Object)(object)InstanceFinder.ClientManager == (Object)null))
{
string text = (ConnectionManager.IsUsingSteam ? "steam" : ClientIdentity.PublicKey);
if (!string.IsNullOrEmpty(text))
{
InstanceFinder.ClientManager.Broadcast<IdentityHelloBroadcast>(new IdentityHelloBroadcast(text), (Channel)0);
}
}
}
private static void Mark(NetworkConnection connection, string key)
{
if (!(connection == (NetworkConnection)null) && !string.IsNullOrEmpty(key))
{
Keys[connection.ClientId] = key;
VerifiedClients.Add(connection.ClientId);
IdentityRuntime.Verified?.Invoke(connection);
}
}
private static string MakeKey(string publicKey)
{
if (!ClientIdentity.TryPublicKey(publicKey, out var xml))
{
return null;
}
using SHA256 sHA = SHA256.Create();
byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(xml));
return "key:" + BitConverter.ToString(array).Replace("-", string.Empty).ToLowerInvariant();
}
private static string SteamKey(Player player)
{
if ((Object)(object)player == (Object)null || !ConnectionManager.IsUsingSteam)
{
return null;
}
if (player.SteamID != 0L)
{
return "steam:" + player.SteamID;
}
if (((NetworkBehaviour)player).Owner != (NetworkConnection)null && ulong.TryParse(((NetworkBehaviour)player).Owner.GetAddress(), out var result) && result != 0L)
{
return "steam:" + result;
}
return null;
}
private static Player FindPlayer(NetworkConnection connection)
{
if (connection == (NetworkConnection)null)
{
return null;
}
foreach (Player player in PlayerManager.Players)
{
if ((Object)(object)player != (Object)null && ((NetworkBehaviour)player).Owner != (NetworkConnection)null && ((NetworkBehaviour)player).Owner.ClientId == connection.ClientId)
{
return player;
}
}
return null;
}
}
}
namespace HowtoFishIdentityAPI.Patches
{
[HarmonyPatch(typeof(Player), "OnStartClient")]
internal static class PlayerStartPatch
{
private static void Postfix(Player __instance)
{
IdentityRuntime.PlayerStarted(__instance);
}
}
[HarmonyPatch(typeof(Player), "OnStopClient")]
internal static class PlayerStopPatch
{
private static void Postfix(Player __instance)
{
IdentityRuntime.PlayerStopped(__instance);
}
}
[HarmonyPatch(typeof(Server), "OnStopServer")]
internal static class ServerStopPatch
{
private static void Postfix()
{
IdentityRuntime.Stop();
}
}
}
namespace HowtoFishIdentityAPI.Api
{
public static class IdentityApi
{
public static event Action<NetworkConnection> Verified
{
add
{
IdentityRuntime.Verified += value;
}
remove
{
IdentityRuntime.Verified -= value;
}
}
public static bool IsVerified(NetworkConnection connection)
{
return IdentityRuntime.IsVerified(connection);
}
public static bool IsVerified(Player player)
{
if ((Object)(object)player != (Object)null)
{
return IsVerified(((NetworkBehaviour)player).Owner);
}
return false;
}
public static bool TryGetKey(NetworkConnection connection, out string key)
{
return IdentityRuntime.TryGetKey(connection, out key);
}
public static bool TryGetKey(Player player, out string key)
{
return IdentityRuntime.TryGetKey(player, out key);
}
public static bool IsSteam(Player player)
{
return IdentityRuntime.IsSteam(player);
}
public static bool IsSteam(NetworkConnection connection)
{
return IdentityRuntime.IsSteam(connection);
}
}
}