using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LethalConfig;
using LethalConfig.ConfigItems;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("LethalAnalytics")]
[assembly: AssemblyDescription("Allows mod creators to see how their mods are used, easily catch errors, and learn about their audience.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("LethalAnalytics")]
[assembly: AssemblyCopyright("Copyright © RoosterBooster007 2024")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("34f2d91f-5d5e-4278-bc2d-b5f62637a5d1")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace LethalAnalytics;
public static class AnalyticsManager
{
public static bool telemetricsEnabled => (bool)((ConfigEntryBase)LethalAnalytics.enableTele).BoxedValue;
public static GASession registerGASession(string modId, string modName, string modVersion, string modDesc, string measurementId, string screenTitle = "MainMenu", string renewScreenTitle = "InGame", bool sendSystemInfo = true, int sessionLengthMins = 30)
{
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Expected O, but got Unknown
modName = Regex.Replace(modName, "[^A-Za-z0-9 ]", "");
modName = modName.Trim();
ConfigEntry<bool> val = LethalAnalytics.configFile.Bind<bool>("Mods", modName, true, modDesc + "\n\n[Important: This entry indicates whether a mod receives analytics updates. Consult the description above.]");
BoolCheckBoxConfigItem val2 = new BoolCheckBoxConfigItem(val, false);
LethalConfigManager.AddConfigItem((BaseConfigItem)(object)val2);
GASession result = new GASession(modId, modName, modVersion, measurementId, screenTitle, renewScreenTitle, sendSystemInfo, val, sessionLengthMins);
LethalAnalytics.mls.LogInfo((object)(modName + " v" + modVersion + " successfully registered a GA property!"));
return result;
}
}
public class GASession
{
private string modId;
private string modName;
private string modVersion;
private string measurementId;
private string screenTitle;
private string renewScreenTitle;
private bool sendSystemInfo;
private ConfigEntry<bool> enableTele;
private int sessionLengthMins;
private HttpClient HTTPclient = new HttpClient();
private DateTimeOffset sessionStartTime = DateTimeOffset.UtcNow;
private DateTimeOffset lastEng = DateTimeOffset.UtcNow;
private int GAHitCount = 1;
private int GASessionCount = 1;
private string GASessionID = Regex.Replace(Guid.NewGuid().ToString(), "[^\\d]", "").Substring(0, 10);
public bool telemetricsEnabled => (bool)((ConfigEntryBase)enableTele).BoxedValue;
internal GASession(string modId, string modName, string modVersion, string measurementId, string screenTitle, string renewScreenTitle, bool sendSystemInfo, ConfigEntry<bool> enableTele, int sessionLengthMins)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_000b: Expected O, but got Unknown
this.modId = modId;
this.modName = modName;
this.modVersion = modVersion;
this.measurementId = measurementId;
this.screenTitle = screenTitle;
this.renewScreenTitle = renewScreenTitle;
this.sendSystemInfo = sendSystemInfo;
this.enableTele = enableTele;
this.sessionLengthMins = sessionLengthMins;
Patches.mainMenuLaunched += OnMenuLaunch;
Patches.userClosingLC += OnLCClosing;
if (isFirstRun())
{
SendUserGA("page_view", startingSession: true, "&_fv=1");
}
else
{
SendUserGA("page_view", startingSession: true, "");
}
}
private void OnLCClosing(object sender, EventArgs e)
{
sendGAEvent("state", "session_end", new Dictionary<string, string> { ["closed"] = "true" });
}
private void OnMenuLaunch(object sender, EventArgs e)
{
if (recentlyUpdated())
{
sendGAEvent("state", "update", new Dictionary<string, string> { ["version"] = modVersion });
}
}
private static string getSystemArch()
{
if (Environment.Is64BitProcess)
{
return "x86_64";
}
return "x86";
}
private static string getSystemBits()
{
if (Environment.Is64BitProcess)
{
return "64";
}
return "32";
}
private bool isFirstRun()
{
if (!PlayerPrefs.HasKey(modId + ".newUser"))
{
PlayerPrefs.SetInt(modId + ".newUser", 1);
PlayerPrefs.Save();
return true;
}
return false;
}
private bool recentlyUpdated()
{
if (PlayerPrefs.GetString(modId + ".v") != modVersion)
{
PlayerPrefs.SetString(modId + ".v", modVersion);
PlayerPrefs.Save();
return true;
}
return false;
}
private void startNewGASession()
{
GASessionCount++;
GASessionID = Regex.Replace(Guid.NewGuid().ToString(), "[^\\d]", "").Substring(0, 10);
sessionStartTime = DateTimeOffset.UtcNow;
lastEng = DateTime.UtcNow;
GAHitCount = 1;
SendUserGA("page_view", startingSession: false, "");
}
public void sendGAEvent(string event_category, string event_name, Dictionary<string, string> event_params, bool isEngaged = true)
{
//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
//IL_021b: Unknown result type (might be due to invalid IL or missing references)
//IL_0220: Unknown result type (might be due to invalid IL or missing references)
if (!(bool)((ConfigEntryBase)LethalAnalytics.enableTele).BoxedValue || !(bool)((ConfigEntryBase)enableTele).BoxedValue)
{
return;
}
if ((DateTime.UtcNow - lastEng).TotalMinutes >= (double)sessionLengthMins)
{
startNewGASession();
}
event_name = WebUtility.UrlEncode(event_name);
event_category = WebUtility.UrlEncode(event_category);
string text = "";
foreach (KeyValuePair<string, string> event_param in event_params)
{
text = ((!int.TryParse(event_param.Value, out var result)) ? (text + "&ep." + WebUtility.UrlEncode(event_param.Key) + "=" + WebUtility.UrlEncode(event_param.Value)) : (text + "&epn." + WebUtility.UrlEncode(event_param.Key) + "=" + result));
}
string text2 = "";
if (isEngaged)
{
text2 = text2 + "&_et = " + (long)Math.Floor((DateTime.UtcNow - lastEng).TotalMilliseconds);
}
GAHitCount++;
HttpClient hTTPclient = HTTPclient;
string[] obj = new string[31]
{
"https://www.google-analytics.com/g/collect?v=2&tid=",
measurementId,
"&cid=",
SystemInfo.deviceUniqueIdentifier,
"&sid=",
GASessionID,
"&ul=",
CultureInfo.CurrentCulture.Name,
"&sr=",
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null
};
Resolution currentResolution = Screen.currentResolution;
obj[9] = ((Resolution)(ref currentResolution)).width.ToString();
obj[10] = "x";
currentResolution = Screen.currentResolution;
obj[11] = ((Resolution)(ref currentResolution)).height.ToString();
obj[12] = "&uap=";
obj[13] = WebUtility.UrlEncode(Environment.OSVersion.Platform.ToString());
obj[14] = "&uam=";
obj[15] = WebUtility.UrlEncode(Application.version);
obj[16] = "&uapv=";
obj[17] = WebUtility.UrlEncode(modVersion);
obj[18] = "&en=";
obj[19] = event_name;
obj[20] = "&ep.category=";
obj[21] = event_category;
obj[22] = text;
obj[23] = text2;
obj[24] = "&tfd=";
obj[25] = ((long)Math.Floor((DateTime.UtcNow - sessionStartTime).TotalMilliseconds)).ToString();
obj[26] = "&uamb=0&uaw=0&pscdl=noapi&_s=";
obj[27] = GAHitCount.ToString();
obj[28] = "&sct=";
obj[29] = GASessionCount.ToString();
obj[30] = "&seg=1&_ee=1&npa=0&dma=0&frm=0&are=1";
hTTPclient.GetAsync(string.Concat(obj));
if (!LethalAnalytics.recentModList.Contains(modName))
{
LethalAnalytics.recentModList.Add(modName);
}
LethalAnalytics.totalEventsSent++;
if (isEngaged)
{
lastEng = DateTime.UtcNow;
}
}
private void SendUserGA(string e, bool startingSession, string args)
{
//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
//IL_0113: Unknown result type (might be due to invalid IL or missing references)
//IL_0118: Unknown result type (might be due to invalid IL or missing references)
if ((bool)((ConfigEntryBase)LethalAnalytics.enableTele).BoxedValue && (bool)((ConfigEntryBase)enableTele).BoxedValue)
{
string text = "";
string text2 = "";
if (sendSystemInfo)
{
text = text + "&ep.cpu=" + SystemInfo.processorType + "&ep.gpu=" + SystemInfo.graphicsDeviceName;
}
text2 = ((!startingSession) ? renewScreenTitle : screenTitle);
HttpClient hTTPclient = HTTPclient;
string[] obj = new string[33]
{
"https://www.google-analytics.com/g/collect?v=2&tid=",
measurementId,
"&cid=",
SystemInfo.deviceUniqueIdentifier,
"&sid=",
GASessionID,
"&ul=",
CultureInfo.CurrentCulture.Name,
"&sr=",
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null
};
Resolution currentResolution = Screen.currentResolution;
obj[9] = ((Resolution)(ref currentResolution)).width.ToString();
obj[10] = "x";
currentResolution = Screen.currentResolution;
obj[11] = ((Resolution)(ref currentResolution)).height.ToString();
obj[12] = "&uap=";
obj[13] = WebUtility.UrlEncode(Environment.OSVersion.Platform.ToString());
obj[14] = "&uam=";
obj[15] = WebUtility.UrlEncode(Application.version);
obj[16] = "&uapv=";
obj[17] = WebUtility.UrlEncode(modVersion);
obj[18] = "&en=";
obj[19] = e;
obj[20] = text;
obj[21] = "&tfd=";
obj[22] = ((long)Math.Floor((DateTime.UtcNow - sessionStartTime).TotalMilliseconds)).ToString();
obj[23] = "&uaa=";
obj[24] = getSystemArch();
obj[25] = "&uab=";
obj[26] = getSystemBits();
obj[27] = "&uamb=0&uaw=0&dt=";
obj[28] = WebUtility.UrlEncode(text2 + " (v" + modVersion + ")");
obj[29] = "&are=1&frm=0&pscdl=noapi&seg=1&npa=0&_s=1&sct=";
obj[30] = GASessionCount.ToString();
obj[31] = "&dma=0&_ss=1&_nsi=1&_ee=1";
obj[32] = args;
hTTPclient.GetAsync(string.Concat(obj));
if (!LethalAnalytics.recentModList.Contains(modName))
{
LethalAnalytics.recentModList.Add(modName);
}
LethalAnalytics.totalEventsSent++;
}
}
}
internal class Patches
{
[HarmonyPatch(typeof(MenuManager))]
internal class MenuPatch
{
[HarmonyPatch("Awake")]
[HarmonyPostfix]
public static void patchUpdateEvent()
{
Patches.mainMenuLaunched?.Invoke(null, EventArgs.Empty);
}
}
[HarmonyPatch(typeof(QuickMenuManager))]
public class QMenuPatch
{
[HarmonyPatch("OpenQuickMenu")]
[HarmonyPostfix]
public static void patchOpen()
{
Patches.quickMenuOpened?.Invoke(null, EventArgs.Empty);
}
}
[HarmonyPatch(typeof(GameNetworkManager))]
public class LeavePatch
{
[HarmonyPatch("OnDisable")]
[HarmonyPrefix]
public static void patchQuit()
{
Patches.userClosingLC?.Invoke(null, EventArgs.Empty);
}
}
internal static event EventHandler mainMenuLaunched;
internal static event EventHandler quickMenuOpened;
internal static event EventHandler userClosingLC;
}
[BepInPlugin("net.RB007.LethalAnalytics", "LethalAnalytics", "1.0.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public class LethalAnalytics : BaseUnityPlugin
{
private const string modGUID = "net.RB007.LethalAnalytics";
private const string modName = "LethalAnalytics";
private const string modVersion = "1.0.0";
private readonly Harmony harmony = new Harmony("net.RB007.LethalAnalytics");
internal static ManualLogSource mls;
internal static int totalEventsSent = 0;
internal static int prevTotalEventsSent = 0;
internal static List<string> recentModList = new List<string>();
internal Timer _timer;
internal static ConfigEntry<bool> enableTele;
internal static ConfigEntry<int> eventsSent;
internal static ConfigFile configFile;
internal static GASession gaSession;
private void Awake()
{
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
//IL_005c: Expected O, but got Unknown
//IL_008d: Unknown result type (might be due to invalid IL or missing references)
//IL_0093: Expected O, but got Unknown
mls = Logger.CreateLogSource("net.RB007.LethalAnalytics");
mls.LogInfo((object)"LethalAnalytics is enabling...");
mls.LogInfo((object)"Reading config...");
enableTele = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enable telemetry", true, "When enabled, some anonymous user data/input actions may be uploaded to various analytics sites. This helps mod creators understand what features are used the most and what they can improve. :) \n\nCheck each mod listed below to see what data is collected.\n\nSupported analytics sites:\n - Google Analytics");
BoolCheckBoxConfigItem val = new BoolCheckBoxConfigItem(enableTele, false);
LethalConfigManager.AddConfigItem((BaseConfigItem)(object)val);
eventsSent = ((BaseUnityPlugin)this).Config.Bind<int>("Info", "Events sent this session", totalEventsSent, "[Read-only] This shows how many total analytics events have been sent, via the plugins listed below, while you've been online.");
IntInputFieldConfigItem val2 = new IntInputFieldConfigItem(eventsSent, false);
LethalConfigManager.AddConfigItem((BaseConfigItem)(object)val2);
configFile = ((BaseUnityPlugin)this).Config;
mls.LogInfo((object)"Config loaded!");
mls.LogInfo((object)"Patching game...");
harmony.PatchAll();
mls.LogInfo((object)"Patched all!");
Patches.quickMenuOpened += OnQMenuOpen;
gaSession = new GASession("net.RB007.LethalAnalytics", "LethalAnalytics", "1.0.0", "G-KQFXJ36ZK6", "MainMenu", "InGame", sendSystemInfo: true, enableTele, 30);
_timer = new Timer(SentEventsInfo, null, TimeSpan.FromMinutes(3.0), TimeSpan.FromMinutes(3.0));
mls.LogInfo((object)("LethalAnalytics started! CID: " + SystemInfo.deviceUniqueIdentifier));
}
private void SentEventsInfo(object state)
{
string text = "";
foreach (string recentMod in recentModList)
{
text = text + recentMod + ",";
}
gaSession.sendGAEvent("event", "sent_events", new Dictionary<string, string>
{
["events_count"] = (totalEventsSent - prevTotalEventsSent).ToString(),
["mods_count"] = recentModList.Count.ToString(),
["mods_list"] = text
});
prevTotalEventsSent = totalEventsSent;
}
private void OnQMenuOpen(object sender, EventArgs e)
{
((ConfigEntryBase)eventsSent).BoxedValue = totalEventsSent;
gaSession.sendGAEvent("event", "menu_open", new Dictionary<string, string>());
}
}