diff --git a/Core/Config.cs b/Core/Config.cs index 64b46e7..0552ec7 100644 --- a/Core/Config.cs +++ b/Core/Config.cs @@ -11,6 +11,7 @@ namespace undead_universal_patch_il2cpp.Core public static ConfigEntry SignalRHandshakeFix; public static ConfigEntry ImageSignaturePatch; public static ConfigEntry RegistrationPatch; + public static ConfigEntry AFKPatch; } public static class GenericConfigDefaults { @@ -21,6 +22,7 @@ namespace undead_universal_patch_il2cpp.Core public static bool SignalRHandshakeFix = false; public static bool ImageSignaturePatch = false; public static bool RegistrationPatch = false; + public static bool AFKPatch = false; } public static class AssetChangesConfig { @@ -70,21 +72,4 @@ namespace undead_universal_patch_il2cpp.Core public static string ServerAddress = "127.0.0.1"; public static int ServerPort = 5055; } - - public static class GalvanicConfig - { - public static ConfigEntry Enabled; - public static ConfigEntry RegenerateKeypair; - public static ConfigEntry Export; - public static ConfigEntry Import; - } - - public static class GalvanicConfigDefaults - { - public static bool Enabled = false; - public static bool RegenerateKeypair = false; - public static string Export = "IDoNotWantToExportMyKeys"; - public static bool Import = false; - } - } diff --git a/Core/Galvanic.cs b/Core/Galvanic.cs deleted file mode 100644 index e35d466..0000000 --- a/Core/Galvanic.cs +++ /dev/null @@ -1,239 +0,0 @@ -using System; -using System.Net.Http; -using System.Security.Cryptography; -using System.Text; -using UnityEngine; -using System.Text.Json; -using System.IO; - -// this entire file could be better - -namespace undead_universal_patch_il2cpp.Core -{ - public class GalvanicAuth - { - public static void PrepareKeys() - { - PlayerPrefs.DeleteKey("GalvanicPublicKey"); - PlayerPrefs.DeleteKey("GalvanicPrivateKey"); - using ECDsa ecdsa = ECDsa.Create(ECCurve.NamedCurves.nistP256); - byte[] publicKeyBytes = ecdsa.ExportSubjectPublicKeyInfo(); - string publicKeyBase64 = Convert.ToBase64String(publicKeyBytes); - - byte[] privateKeyBytes = ecdsa.ExportPkcs8PrivateKey(); - string privateKeyBase64 = Convert.ToBase64String(privateKeyBytes); - - PlayerPrefs.SetString("GalvanicPublicKey", publicKeyBase64); - PlayerPrefs.SetString("GalvanicPrivateKey", privateKeyBase64); - PlayerPrefs.Save(); - - Plugin.Log.LogInfo("Created new keypair for Galvanic authentication."); - } - - public static string GetPubKey() - { - string publicKey = PlayerPrefs.GetString("GalvanicPublicKey", null); - if (string.IsNullOrEmpty(publicKey)) - { - Plugin.Log.LogInfo("No keypair found, generating new."); - PrepareKeys(); - return GetPubKey(); - } else return publicKey; - } - - private static ECDsa GetPrivKey() - { - string privateKey = PlayerPrefs.GetString("GalvanicPrivateKey", null); - if (string.IsNullOrEmpty(privateKey)) - { - Plugin.Log.LogInfo("No keypair found, generating new."); - PrepareKeys(); - return GetPrivKey(); - } - - byte[] privKeyBytes = Convert.FromBase64String(privateKey); - ECDsa crypt = ECDsa.Create(); - crypt.ImportPkcs8PrivateKey(privKeyBytes, out _); - return crypt; - } - - public static string SignPayload(string message) - { - using ECDsa ecdsa = GetPrivKey(); - if (ecdsa == null) return null; - - byte[] payloadBytes = Encoding.UTF8.GetBytes(message); - byte[] signature = ecdsa.SignData(payloadBytes, HashAlgorithmName.SHA256); - - return Convert.ToBase64String(signature); - } - public static void Export() - { - File.WriteAllText("./galvanic_keys_export.txt", $"{GetPubKey()}\n{Convert.ToBase64String(GetPrivKey().ExportPkcs8PrivateKey())}"); - Plugin.Log.LogWarning("Galvanic Authentication keys were exported."); - } - public static void Import() - { - try - { - string imported = File.ReadAllText("./galvanic_keys_export.txt").ToString(); - string privkey = imported.Split("\n")[1]; - string pubkey = imported.Split("\n")[0]; - if (privkey == null || pubkey == null) throw new Exception("Either imported key was null"); - - PlayerPrefs.SetString("GalvanicPrivateKey", privkey); - PlayerPrefs.SetString("GalvanicPublicKey", pubkey); - PlayerPrefs.Save(); - } catch (Exception err) - { - Plugin.Log.LogError($"Could not import Galvanic Authentication keys: {err}"); - } - } - public static ServerInfoRes GetServerInfo() - { - UriBuilder nameserver = new(NameserverConfig.NewUrl.Value); - nameserver.Path = "/info"; - nameserver.Query = ""; - - using HttpClient client = new(); - var response = client.GetAsync(nameserver.ToString()).Result; - if (!response.IsSuccessStatusCode) return null; - - var str = response.Content.ReadAsStringAsync().Result; - var res = JsonSerializer.Deserialize(str); - - GalvanicCorrosion.Name = res.name; - GalvanicCorrosion.Id = res.id; - GalvanicCorrosion.Motd = res.motd; - GalvanicCorrosion.Patches = res.patches; - - return res; - } - } - - public static class GalvanicCorrosion - { - public static string Name { get; set; } = "Galvanic Corrosion"; - public static string Id { get; set; } = "galvanic-corrosion-default"; - public static string Motd { get; set; } = "A Galvanic Corrosion server."; - public static string[] Patches { get; set; } = []; - } - - public static class GalvanicWebAuth - { - public static string Token { get; private set; } = null; - public static void TokenExpiry() - { - string url = NameserverConfig.NewUrl.Value; - UriBuilder uri = new(url); - uri.Path = "/user/checkExpired"; - uri.Query = ""; - - HttpClient client = new(); - client.DefaultRequestHeaders.Add("GalvanicAuth", Token); - HttpResponseMessage res = client.GetAsync(uri.ToString()).Result; - if (res.IsSuccessStatusCode) - { - bool expired = JsonSerializer.Deserialize(res.Content.ReadAsStringAsync().Result.ToString()); - if (expired) GetToken(); - } - } - - public static void GetToken() - { - string url = NameserverConfig.NewUrl.Value; - UriBuilder uri = new(url) - { - Path = "/user/auth", - Query = "" - }; - - var info = GalvanicAuth.GetServerInfo(); - Plugin.Log.LogInfo($"Sending authentication request to server ID '{info.id}'"); - if (uri.Scheme == "http") Plugin.Log.LogWarning("The server is not secure! Please use HTTPS."); - - UserAuthPayload payload = new() - { - timestamp = ((DateTimeOffset)DateTime.Now).ToUnixTimeSeconds(), - nonce = Guid.NewGuid().ToString(), - server_id = info.id, - }; - - UserAuthRequest request = new() - { - message = payload, - signature = GalvanicAuth.SignPayload($"{JsonSerializer.Serialize(payload)}"), - client_id = UniqueID.GetUniqueId(), - pubkey = GalvanicAuth.GetPubKey() - }; - - HttpClient client = new(); - HttpResponseMessage res = client.PostAsync(uri.ToString(), new StringContent(JsonSerializer.Serialize(request, - new JsonSerializerOptions { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping - }), - Encoding.UTF8, - "application/json")).Result; - if (!res.IsSuccessStatusCode) - { - Plugin.Log.LogError("The GalvanicWeb auth response was not successful."); - return; - } - string str = res.Content.ReadAsStringAsync().Result; - TokenRes token = JsonSerializer.Deserialize(str); - - Token = token.token; - } - } - - public static class UniqueID - { - public static string GetUniqueId() - { - if (!PlayerPrefs.HasKey("UniqueId")) SaveUniqueId(GenerateUniqueId()); - return PlayerPrefs.GetString("UniqueId"); - } - public static void SaveUniqueId(string id) - { - PlayerPrefs.SetString("UniqueId", id); - PlayerPrefs.Save(); - } - public static string GenerateUniqueId() - { - byte[] buffer = new byte[128]; - new System.Random().NextBytes(buffer); - return Convert.ToBase64String(buffer).Substring(0, 128); - - // god help me - } - } - - public class ServerInfoRes - { - public string name { get; set; } - public string id { get; set; } - public string motd { get; set; } - public string[] patches { get; set; } - } - - class TokenRes - { - public string token { get; set; } - } - - class UserAuthPayload - { - public long timestamp { get; set; } - public string nonce { get; set; } - public string server_id { get; set; } - } - - class UserAuthRequest - { - public string client_id { get; set; } - public UserAuthPayload message { get; set; } - public string signature { get; set; } - public string pubkey { get; set; } - } -} \ No newline at end of file diff --git a/Patches/AFKPatch.cs b/Patches/AFKPatch.cs new file mode 100644 index 0000000..1a7feab --- /dev/null +++ b/Patches/AFKPatch.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using HarmonyLib; +using undead_universal_patch_il2cpp.Core; + +namespace undead_universal_patch_il2cpp.Patches; + +[HarmonyPatch] +public class AFKPatch +{ + static readonly string TargetTypeName = "Player"; + static readonly string TargetMethodName = "UpdateAFKStatus"; + static readonly string Description = "Always present, never AFK"; + + static bool Prepare() + { + if (AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName) == null) + { + Plugin.Log.LogWarning($"'{Description}' disabled. The method for this patch was not found."); + return false; + } + + Plugin.Log.LogInfo($"'{Description}' succeeded validation."); + return true; + } + static MethodBase TargetMethod() => AccessTools.Method(AccessTools.TypeByName(TargetTypeName), TargetMethodName); + static void Prefix(ref bool userIsPresent) + { + if (GenericConfig.AFKPatch.Value) userIsPresent = true; + } +} diff --git a/Patches/BestHTTP.cs b/Patches/BestHTTP.cs index b53ccf3..8e179d7 100644 --- a/Patches/BestHTTP.cs +++ b/Patches/BestHTTP.cs @@ -64,30 +64,6 @@ namespace undead_universal_patch_il2cpp.Patches if (newUri.ToString().Contains("ns.rec.net")) newUri = new Il2CppSystem.Uri(NameserverConfig.NewUrl.Value); - if (GalvanicConfig.Enabled.Value) - { - string[] applyHeader = [ - "/cachedlogin/forplatformid", - "/account/create", - "/connect/token" - ]; - foreach (string header in applyHeader) - { - if (newUri.PathAndQuery.Contains(header)) - { - // refresh the token if it expired - // this is somewhat inefficient, but we don't hook into many requests (see above) so it should be fine - GalvanicWebAuth.TokenExpiry(); - - Type httpRequestType = request.GetType(); - MethodInfo addHeaderMethod = httpRequestType.GetMethod("AddHeader"); - addHeaderMethod.Invoke(request, ["GalvanicAuth", GalvanicWebAuth.Token]); - - break; - } - } - } - if (GenericConfig.LogAllRequests.Value) Plugin.Log.LogInfo($"BestHTTP_Unob request a-URL: {newUri.ToString()}"); uriProperty.SetValue(request, NameserverConfig.Rewrite.Value ? newUri : uriInstance, null); } diff --git a/Plugin.cs b/Plugin.cs index eaa1d49..89f1ccf 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -49,6 +49,8 @@ public class Plugin : BasePlugin "\nWorks only if the server appends a properly formatted signature header (signature does not need to be valid)"); GenericConfig.RegistrationPatch = Config.Bind("Generic", "RegistrationPatch", GenericConfigDefaults.RegistrationPatch, "Always disable the registration prompt."); + GenericConfig.AFKPatch = Config.Bind("Generic", "AFKPatch", GenericConfigDefaults.AFKPatch, + "Always present patch. Never get kicked to dorm."); AssetChangesConfig.AGRoomChanges = Config.Bind("AssetChangesConfig", "AGRoomChanges", AssetChangesConfigDefaults.AGRoomChanges, $"Use patches from '{AGRoomChanges.config.path}' to change the properties of internal AGRooms. See README.md"); @@ -80,31 +82,7 @@ public class Plugin : BasePlugin NameserverConfig.NewUrl = Config.Bind("Nameserver", "NewUrl", NameserverConfigDefaults.NewUrl, "The new full URL to use when sending a nameserver request."); - GalvanicConfig.Enabled = Config.Bind("GalvanicCorrosion", "Enabled", GalvanicConfigDefaults.Enabled, - "Use Galvanic Corrosion features. Authenticates with a GC server." + - "\nThe host and protocol in Nameserver.NewUrl will be used for the server's base URL." + - "\nDo not enable if you either don't know what this does."); - GalvanicConfig.RegenerateKeypair = Config.Bind("GalvanicCorrosion", "RegenerateKeypair", GalvanicConfigDefaults.RegenerateKeypair, - "Regenerate the keypair upon startup. DO NOT ENABLE IF YOU WANT TO KEEP YOUR ACCOUNT."); - GalvanicConfig.Export = Config.Bind("GalvanicCorrosion", "Export", GalvanicConfigDefaults.Export, - "Export the keypair to a plaintext file in the game directory. Set to 'IWantToExportMyKeys' to enable." + - "\n**DO NOT ENABLE IF YOU DO NOT KNOW WHAT THIS DOES.**" + - "\n**DO NOT ENABLE THIS IF SOMEONE UNTRUSTWORTHY TOLD YOU TO.**" + - "\n**THESE KEYS CONTAIN THE CREDENTIALS THAT GRANT ACCESS TO YOUR ACCOUNTS.**"); - GalvanicConfig.Import = Config.Bind("GalvanicCorrosion", "Import", GalvanicConfigDefaults.Import, - "Import the Galvanic Authentication keys from a file." + - "\nBe sure to not enable this at the same time as `Export`, as that will cause the keys to" + - "\nbe unnecessarily written and read to and from the disk."); - _hi.PatchAll(); - if (GalvanicConfig.Enabled.Value && GalvanicConfig.RegenerateKeypair.Value) - { - Log.LogInfo("Regenerating keypair"); - GalvanicAuth.PrepareKeys(); - } - if (GalvanicConfig.Enabled.Value && GalvanicConfig.Export.Value == "IWantToExportMyKeys") GalvanicAuth.Export(); - if (GalvanicConfig.Enabled.Value && GalvanicConfig.Import.Value) GalvanicAuth.Import(); - if (GalvanicConfig.Enabled.Value) GalvanicWebAuth.GetToken(); Log.LogInfo("PATCH LIST START ========================="); foreach (var method in _hi.GetPatchedMethods()) Log.LogInfo($"- {method.ToString()}"); diff --git a/README.md b/README.md index cfd365b..8661e79 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,6 @@ Run BepInEx interop on your build and add the following assemblies to new folder ### Linux users -Wine currently has problems generating the keypair used in Galvanic -authentication. - -You can use a real Windows system to generate the keys for the first startup. - If you're unsure how to start your build on linux: - Install Steam