using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("addzeey.PeakShock")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.3.0.0")] [assembly: AssemblyInformationalVersion("0.3.0+879aaaa4fbb2a8c92c75128c80f227247491bdaa")] [assembly: AssemblyProduct("addzeey.PeakShock")] [assembly: AssemblyTitle("PeakShock")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.3.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } public interface IShockController { void EnqueueShock(int intensity, int duration, string? code = null); } namespace BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace PeakShock { internal class ShockRequestQueue : IDisposable { private ConcurrentQueue> _taskQueue; private SemaphoreSlim _signal; private CancellationTokenSource _cts; private Task _worker; private readonly object _lock = new object(); private bool _disposed; public ShockRequestQueue() { Start(); } public void Enqueue(Func task) { if (_disposed) { throw new ObjectDisposedException("ShockRequestQueue"); } _taskQueue.Enqueue(task); _signal.Release(); } public void Enqueue(Func task, T args) { Func task2 = task; T args2 = args; if (_disposed) { throw new ObjectDisposedException("ShockRequestQueue"); } _taskQueue.Enqueue(() => task2(args2)); _signal.Release(); } public void Start() { _taskQueue = new ConcurrentQueue>(); _signal = new SemaphoreSlim(0); _cts = new CancellationTokenSource(); _worker = Task.Run((Func)ProcessQueueAsync); } private async Task ProcessQueueAsync() { while (!_cts.Token.IsCancellationRequested) { await _signal.WaitAsync(_cts.Token); if (_taskQueue.TryDequeue(out Func result)) { try { await result(); } catch (Exception ex) { Plugin.Log.LogError((object)ex.ToString()); } } } } public async Task StopAsync() { if (_disposed) { return; } _cts.Cancel(); _signal.Release(); try { await _worker; } catch (Exception ex) { Plugin.Log.LogError((object)ex.ToString()); } } public async Task ResetAsync() { lock (_lock) { if (!_worker.IsCompleted) { StopAsync().Wait(); } Start(); } } public void Dispose() { if (!_disposed) { StopAsync().Wait(); _cts.Dispose(); _signal.Dispose(); _disposed = true; } } } public class OpenShockController : IShockController { private readonly ShockRequestQueue _queue = new ShockRequestQueue(); private readonly string _apiUrl; private readonly string _deviceId; private readonly string _apiKey; private readonly HttpClient _client = new HttpClient(); private DateTime _lastShockTime = DateTime.MinValue; private TimeSpan ShockCooldown => TimeSpan.FromSeconds(1.0 + (double)Math.Max(0f, Plugin.ShockCooldownSeconds.Value)); public OpenShockController() { _apiUrl = Plugin.OpenShockApiUrl.Value; _deviceId = Plugin.OpenShockDeviceId.Value; _apiKey = Plugin.OpenShockApiKey.Value; } public void EnqueueShock(int intensity, int duration, string? code = null) { string code2 = code; DateTime utcNow = DateTime.UtcNow; if (utcNow - _lastShockTime < ShockCooldown) { Plugin.Log.LogInfo((object)"[PeakShock] OpenShock shock skipped due to cooldown."); return; } _lastShockTime = utcNow; _queue.Enqueue(() => TriggerShockInternal(intensity, duration, code2)); } private async Task TriggerShockInternal(int intensity, int duration, string? code) { int num = Math.Clamp(duration * 1000, 300, 65535); if (string.IsNullOrEmpty(_apiUrl) || string.IsNullOrEmpty(_apiKey)) { Plugin.Log.LogWarning((object)$"[PeakShock] Would send OpenShock (intensity={intensity}, duration={num}), but OpenShock credentials are not set."); return; } string text = ((!string.IsNullOrEmpty(code)) ? code : _deviceId); if (string.IsNullOrEmpty(text)) { Plugin.Log.LogWarning((object)"[PeakShock] No deviceId or share code provided for OpenShock."); return; } Plugin.Log.LogInfo((object)$"[PeakShock] Sending OpenShock: id={text}, intensity={intensity}, duration={num}"); var anon = new { Shocks = new[] { new { Id = text, Type = 1, Intensity = intensity, Duration = num } }, CustomName = "Integrations.PeakShock" }; string content = JsonConvert.SerializeObject((object)anon); StringContent content2 = new StringContent(content, Encoding.UTF8, "application/json"); HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, _apiUrl + "/2/shockers/control") { Content = content2 }; httpRequestMessage.Headers.Add("OpenShockToken", _apiKey); httpRequestMessage.Headers.Add("User-Agent", "PeakShock/" + Plugin.Version); try { HttpResponseMessage response = await _client.SendAsync(httpRequestMessage); if (!response.IsSuccessStatusCode) { string arg = await response.Content.ReadAsStringAsync(); Plugin.Log.LogError((object)$"OpenShock API error: {response.StatusCode} {arg}"); } } catch (Exception arg2) { Plugin.Log.LogError((object)$"OpenShock API exception: {arg2}"); } } } public class PiShockController : IShockController { private const string PiShockIdLookupUrl = "https://pishock-lookup.addzeey.com/"; private const string PiShockV3DocsUrl = "https://docs.pishock.com/pishock/pishock-v3-documentation.html"; private readonly HttpClient _client = new HttpClient(); private readonly ShockRequestQueue _queue = new ShockRequestQueue(); private DateTime _lastShockTime = DateTime.MinValue; private TimeSpan ShockCooldown => TimeSpan.FromSeconds(1f + Math.Max(0f, Plugin.ShockCooldownSeconds.Value)); public void TriggerShock(int intensity, int duration = 1, string? shockerIdOverride = null) { DateTime utcNow = DateTime.UtcNow; if (utcNow - _lastShockTime < ShockCooldown) { Plugin.Log.LogInfo((object)"[PeakShock] Shock skipped due to cooldown."); return; } _lastShockTime = utcNow; string shockerId = (string.IsNullOrWhiteSpace(shockerIdOverride) ? Plugin.PiShockShockerId.Value : shockerIdOverride); Plugin.Log.LogInfo((object)$"[PeakShock] Enqueue shock: intensity={intensity}, duration={duration}s, shockerId={shockerId}"); _queue.Enqueue(() => TriggerShockInternal(intensity, duration, shockerId)); } public void EnqueueShock(int intensity, int duration, string? code = null) { TriggerShock(intensity, duration, code); } private async Task TriggerShockInternal(int intensity, int duration, string shockerId) { string value = Plugin.PiShockAPIKey.Value; if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(shockerId)) { Plugin.Log.LogWarning((object)("[PeakShock] PiShock shock skipped because configuration is incomplete. Required values: APIKey and ShockerId. Current values present: " + $"APIKey={!string.IsNullOrWhiteSpace(value)}, ShockerId={!string.IsNullOrWhiteSpace(shockerId)}. " + "Use the shocker id lookup tool: https://pishock-lookup.addzeey.com/")); return; } int num = duration * 1000; Plugin.Log.LogInfo((object)$"[PeakShock] Sending shock: intensity={intensity}, duration={num}ms, shockerId={shockerId}"); string content = JsonConvert.SerializeObject((object)new { AgentName = "PeakShock Mod", Intensity = intensity, Duration = num, Operation = 0 }); StringContent content2 = new StringContent(content, Encoding.UTF8, "application/json"); try { string text = Plugin.PiShockApiUrl.Value.TrimEnd('/'); HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, text + "/Shockers/" + Uri.EscapeDataString(shockerId)) { Content = content2 }; httpRequestMessage.Headers.Add("X-PiShock-Api-Key", value); httpRequestMessage.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); Plugin.Log.LogInfo((object)$"[PeakShock] Sending request to PiShock API ({httpRequestMessage.RequestUri}): Intensity={intensity}, Duration={num}ms, ShockerId={shockerId}"); HttpResponseMessage response = await _client.SendAsync(httpRequestMessage); if (!response.IsSuccessStatusCode) { string responseBody = await response.Content.ReadAsStringAsync(); Plugin.Log.LogError((object)$"[PeakShock] PiShock API error: {(int)response.StatusCode} {response.StatusCode}. {GetStatusExplanation(response.StatusCode, shockerId)} Response body: {FormatResponseBody(responseBody)}"); } else { Plugin.Log.LogInfo((object)("[PeakShock] PiShock API accepted the shock command for shockerId=" + shockerId + ".")); } } catch (Exception arg) { Plugin.Log.LogError((object)$"[PeakShock] PiShock API exception while sending shock to shockerId={shockerId}: {arg}"); } } private static string GetStatusExplanation(HttpStatusCode statusCode, string shockerId) { return statusCode switch { HttpStatusCode.Unauthorized => "Authentication failed. Verify the PiShock APIKey is configured correctly.", HttpStatusCode.Forbidden => "PiShock rejected this request for the authenticated account.", HttpStatusCode.NotFound => "PiShock could not find an accessible share for shockerId=" + shockerId + ". Verify the configured ShockerId by using the lookup tool: https://pishock-lookup.addzeey.com/", HttpStatusCode.MethodNotAllowed => "The configured share does not allow shock operations for this device.", HttpStatusCode.NotAcceptable => "The PiShock device is not V3. Ask the user to flash V3 firmware and use PiShock Discord support if needed: https://docs.pishock.com/pishock/pishock-v3-documentation.html", HttpStatusCode.Gone => "The PiShock share is locked.", HttpStatusCode.PreconditionFailed => "The requested intensity is out of bounds for this device or share.", HttpStatusCode.RequestedRangeNotSatisfiable => "The requested duration exceeds PiShock limits. The current integration sends duration in milliseconds.", HttpStatusCode.ServiceUnavailable => "The PiShock share or shocker is paused.", _ => "Unexpected PiShock response. Check the response body and current PiShock API docs.", }; } private static string FormatResponseBody(string responseBody) { if (!string.IsNullOrWhiteSpace(responseBody)) { return responseBody; } return ""; } } [BepInPlugin("addzeey.PeakShock", "PeakShock", "0.3.0")] public class Plugin : BaseUnityPlugin { public enum ShockProvider { PiShock, OpenShock } public const string PluginVersion = "0.3.0"; private Harmony? _harmony; public const string Id = "addzeey.PeakShock"; internal static ManualLogSource Log { get; private set; } internal static ConfigFile CFG { get; private set; } internal static PiShockController PiShockController { get; private set; } internal static ConfigEntry PiShockApiUrl { get; private set; } internal static ConfigEntry PiShockAPIKey { get; private set; } internal static ConfigEntry PiShockShockerId { get; private set; } internal static ConfigEntry MinShock { get; private set; } internal static ConfigEntry MaxShock { get; private set; } internal static ConfigEntry DeathShock { get; private set; } internal static ConfigEntry DeathDuration { get; private set; } internal static ConfigEntry EnableInjuryShock { get; private set; } internal static ConfigEntry EnablePoisonShock { get; private set; } internal static ConfigEntry EnableColdShock { get; private set; } internal static ConfigEntry EnableHotShock { get; private set; } internal static ConfigEntry EnableThornsShock { get; private set; } internal static ConfigEntry EnableWebShock { get; private set; } internal static ConfigEntry ShockCooldownSeconds { get; private set; } internal static ConfigEntry ShockProviderType { get; private set; } internal static ConfigEntry OpenShockApiUrl { get; private set; } internal static ConfigEntry OpenShockDeviceId { get; private set; } internal static ConfigEntry OpenShockApiKey { get; private set; } internal static IShockController ShockController { get; private set; } public static string Name => "PeakShock"; public static string Version => "0.3.0"; private void Awake() { //IL_038c: Unknown result type (might be due to invalid IL or missing references) //IL_0396: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; CFG = ((BaseUnityPlugin)this).Config; PiShockApiUrl = CFG.Bind("PiShock", "ApiUrl", "https://api.pishock.com", "PiShock API URL"); PiShockAPIKey = CFG.Bind("PiShock", "APIKey", "", "Your PiShock API Key"); PiShockShockerId = CFG.Bind("PiShock", "ShockerId", "", "Your PiShock shocker id. Use the lookup tool if you do not know it yet."); MinShock = CFG.Bind("Shock", "MinShock", 10, "Minimum shock intensity (1-100)"); MaxShock = CFG.Bind("Shock", "MaxShock", 100, "Maximum shock intensity (1-100)"); DeathShock = CFG.Bind("Shock", "DeathShock", 80, "Shock intensity on death (1-100)"); DeathDuration = CFG.Bind("Shock", "DeathDuration", 2, "Shock duration on death (seconds)"); EnableInjuryShock = CFG.Bind("ShockTypes", "EnableInjuryShock", true, "Enable shock for Injury damage"); EnablePoisonShock = CFG.Bind("ShockTypes", "EnablePoisonShock", false, "Enable shock for Poison damage"); EnableColdShock = CFG.Bind("ShockTypes", "EnableColdShock", false, "Enable shock for Cold damage"); EnableHotShock = CFG.Bind("ShockTypes", "EnableHotShock", false, "Enable shock for Hot/Fire damage"); EnableThornsShock = CFG.Bind("ShockTypes", "EnableThornsShock", false, "Enable shock for Thorns damage"); EnableWebShock = CFG.Bind("ShockTypes", "EnableWebShock", false, "Enable shock for Web/Spider web"); ShockCooldownSeconds = CFG.Bind("Shock", "ShockCooldownSeconds", 2f, "Minimum seconds between shocks (prevents shock spam)"); ShockProviderType = CFG.Bind("Shock", "Provider", ShockProvider.PiShock, "Choose PiShock or OpenShock"); OpenShockApiUrl = CFG.Bind("OpenShock", "ApiUrl", "https://api.openshock.app", "OpenShock API URL"); OpenShockDeviceId = CFG.Bind("OpenShock", "DeviceId", "", "OpenShock Device ID"); OpenShockApiKey = CFG.Bind("OpenShock", "ApiKey", "", "OpenShock API Key"); Log.LogInfo((object)$"[PeakShock] Config:\nMinShock={MinShock.Value}\nMaxShock={MaxShock.Value}\nDeathShock={DeathShock.Value}\nDeathDuration={DeathDuration.Value}\nEnableInjuryShock={EnableInjuryShock.Value}\nEnablePoisonShock={EnablePoisonShock.Value}\nEnableColdShock={EnableColdShock.Value}\nEnableHotShock={EnableHotShock.Value}\nEnableThornsShock={EnableThornsShock.Value}\nEnableWebShock={EnableWebShock.Value}\nShockCooldownSeconds={ShockCooldownSeconds.Value}"); if (ShockProviderType.Value == ShockProvider.PiShock) { ShockController = new PiShockController(); Log.LogInfo((object)"[PeakShock] Initialized PiShockController"); } else { ShockController = new OpenShockController(); Log.LogInfo((object)"[PeakShock] Initialized OpenShockController"); } _harmony = new Harmony("com.yourname.PeakShock"); _harmony.PatchAll(Assembly.GetExecutingAssembly()); Log.LogInfo((object)("Plugin " + Name + " is loaded!")); } } } namespace PeakShock.PeakShock.Patches { [HarmonyPatch(typeof(Character), "RPCA_Die")] public class CharacterDeathPatch { [HarmonyPostfix] public static void Postfix(Character __instance) { if (__instance.IsLocal) { int intensity = Plugin.DeathShock.Value; int duration = Plugin.DeathDuration.Value; Plugin.Log.LogInfo((object)$"[PeakShock] Player died. Triggering death shock: {intensity}% for {duration}s"); Task.Run(delegate { Plugin.ShockController.EnqueueShock(intensity, duration); }); } } } [HarmonyPatch(typeof(CharacterAfflictions), "AddStatus")] public class CharacterAfflictions_AddStatus_Patch { private static float damageReceivedBelowThreshold; private static Dictionary GetShockTypeEnabled() { return new Dictionary { { (STATUSTYPE)0, Plugin.EnableInjuryShock.Value }, { (STATUSTYPE)3, Plugin.EnablePoisonShock.Value }, { (STATUSTYPE)2, Plugin.EnableColdShock.Value }, { (STATUSTYPE)8, Plugin.EnableHotShock.Value }, { (STATUSTYPE)9, Plugin.EnableThornsShock.Value }, { (STATUSTYPE)11, Plugin.EnableWebShock.Value } }; } [HarmonyPostfix] public static void Postfix(CharacterAfflictions __instance, bool __result, STATUSTYPE statusType, float amount, bool fromRPC, bool playEffects, bool notify) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) if (!__result || !__instance.character.IsLocal || (Object)(object)__instance.character != (Object)(object)Character.localCharacter || amount <= 0f) { return; } if (__instance.character.data.dead || __instance.character.data.fullyPassedOut || __instance.character.data.passedOut) { Plugin.Log.LogInfo((object)$"[PeakShock] Ignored status effect shock ({statusType}) because player is dead or passed out."); return; } int value = Plugin.MinShock.Value; int value2 = Plugin.MaxShock.Value; Dictionary shockTypeEnabled = GetShockTypeEnabled(); if (!shockTypeEnabled.TryGetValue(statusType, out var value3)) { return; } if (!value3) { Plugin.Log.LogInfo((object)$"[PeakShock] Ignored {statusType} shock (disabled in config)."); } else if (amount < 0.01f) { damageReceivedBelowThreshold += amount; if (damageReceivedBelowThreshold >= 0.01f) { Plugin.Log.LogInfo((object)$"[PeakShock] Accumulated damage {damageReceivedBelowThreshold} from {statusType} exceeds threshold, triggering shock."); int intensity = Mathf.Clamp(Mathf.RoundToInt(damageReceivedBelowThreshold * (float)value2), value, value2); Task.Run(delegate { Plugin.ShockController.EnqueueShock(intensity, 1); }); damageReceivedBelowThreshold = 0f; } } else { int intensity2 = Mathf.Clamp(Mathf.RoundToInt(amount * (float)value2), value, value2); Plugin.Log.LogInfo((object)$"[PeakShock] Status effect {statusType} damage: {amount}, shock: {intensity2}%"); Task.Run(delegate { Plugin.ShockController.EnqueueShock(intensity2, 1); }); } } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }