using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using DooDesch;
using EnemyDropLoot;
using EnemyDropLoot.Config;
using EnemyDropLoot.Managers;
using EnemyDropLoot.Services;
using HarmonyLib;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using MimicAPI.GameAPI;
using ReluProtocol;
using ReluProtocol.Enum;
using Steamworks;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "EnemyDropLoot", "1.2.0", "DooDesch", null)]
[assembly: MelonGame("ReLUGames", "MIMESIS")]
[assembly: MelonOptionalDependencies(new string[] { "MimicAPI" })]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("DooDesch")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © DooDesch")]
[assembly: AssemblyFileVersion("1.2.0.0")]
[assembly: AssemblyInformationalVersion("1.2.0+78af3d3194119c25cf20a513c331c0fa91be1585")]
[assembly: AssemblyProduct("EnemyDropLoot")]
[assembly: AssemblyTitle("EnemyDropLoot")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially created with the assistance of Generative AI.")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic/OpenAI")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.2.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[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]
[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]
[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;
}
}
}
namespace DooDesch
{
internal sealed class HostSettings
{
[HarmonyPatch(typeof(SteamInviteDispatcher), "OnLobbyCreated")]
private static class CreatedPatch
{
private static void Postfix(LobbyCreated_t callback)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0007: Invalid comparison between Unknown and I4
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Unknown result type (might be due to invalid IL or missing references)
if ((int)callback.m_eResult != 1)
{
return;
}
foreach (HostSettings instance in Instances)
{
instance.Begin(new CSteamID(callback.m_ulSteamIDLobby), host: true);
}
}
}
[HarmonyPatch(typeof(SteamInviteDispatcher), "OnLobbyEntered")]
private static class EnteredPatch
{
private static void Prefix(LobbyEnter_t callback)
{
//IL_0000: Unknown result type (might be due to invalid IL or missing references)
//IL_000c: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_0035: Unknown result type (might be due to invalid IL or missing references)
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
if (callback.m_EChatRoomEnterResponse != 1)
{
return;
}
CSteamID val = default(CSteamID);
((CSteamID)(ref val))..ctor(callback.m_ulSteamIDLobby);
foreach (HostSettings instance in Instances)
{
if (!instance.isHost || !(instance.lobby == val))
{
instance.Begin(val, host: false);
}
}
}
}
[HarmonyPatch(typeof(SteamMatchmaking), "LeaveLobby")]
private static class LeavePatch
{
private static void Prefix(CSteamID steamIDLobby)
{
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Unknown result type (might be due to invalid IL or missing references)
foreach (HostSettings instance in Instances)
{
if (instance.lobby == steamIDLobby)
{
instance.Reset();
}
}
}
}
[HarmonyPatch(typeof(MainMenu), "Start")]
private static class MenuPatch
{
private static void Prefix()
{
foreach (HostSettings instance in Instances)
{
instance.Reset();
}
}
}
private static readonly List<HostSettings> Instances = new List<HostSettings>();
private static Callback<LobbyDataUpdate_t> dataCallback;
private static float nextPoll;
private readonly string modId;
private readonly string lobbyKey;
private readonly int schemaVersion;
private readonly List<ISetting> settings = new List<ISetting>();
private readonly string modernKey;
private string modernPayload;
private string lastModernPayload;
private long modernRevision;
private bool legacyHost;
private Dictionary<string, object> snapshot;
private CSteamID lobby;
private CSteamID owner;
private bool isHost;
private bool hostCreated;
private bool authorityValid;
private bool ownerChanged;
private bool published;
private string hostPayload;
private string warningReason;
private int warningRepeats;
internal bool IsInSession => lobby != CSteamID.Nil;
internal bool IsHost
{
get
{
if (isHost)
{
return authorityValid;
}
return false;
}
}
internal bool UsingLegacyHost
{
get
{
if (!isHost && authorityValid && legacyHost)
{
return modernRevision == 0;
}
return false;
}
}
internal event Action EffectiveValuesChanged;
internal event Action LiveValuesChanged;
internal HostSettings(string modId, int schemaVersion = 1)
{
if (string.IsNullOrWhiteSpace(modId))
{
throw new ArgumentException("A mod ID is required.", "modId");
}
if (schemaVersion < 1)
{
throw new ArgumentOutOfRangeException("schemaVersion");
}
this.modId = modId;
this.schemaVersion = schemaVersion;
lobbyKey = "doodesch." + modId.ToLowerInvariant() + ".settings.v" + schemaVersion;
modernKey = lobbyKey + ".extended";
if (Encoding.UTF8.GetByteCount(modernKey) >= 255)
{
throw new ArgumentException("The mod ID exceeds the Steam lobby key limit.", "modId");
}
foreach (HostSettings instance in Instances)
{
if (instance.lobbyKey == lobbyKey)
{
throw new ArgumentException("The mod already has a settings snapshot.", "modId");
}
}
Instances.Add(this);
}
internal HostSetting<T> Register<T>(MelonPreferences_Entry<T> entry, T vanillaValue, Func<T, bool> validate = null, bool live = false, bool legacy = true, Func<T> localValue = null)
{
//IL_005b: Unknown result type (might be due to invalid IL or missing references)
//IL_0060: Unknown result type (might be due to invalid IL or missing references)
if (entry == null)
{
throw new ArgumentNullException("entry");
}
if (string.IsNullOrWhiteSpace(((MelonPreferences_Entry)entry).Identifier))
{
throw new ArgumentException("A setting identifier is required.", "entry");
}
if (((MelonPreferences_Entry)entry).Identifier.IndexOfAny(new char[3] { '\n', '\r', '\t' }) >= 0)
{
throw new ArgumentException("Setting identifiers cannot contain wire delimiters.", "entry");
}
if (lobby != CSteamID.Nil)
{
throw new InvalidOperationException("Register settings before entering a lobby.");
}
if (typeof(T) != typeof(bool) && typeof(T) != typeof(int) && typeof(T) != typeof(float) && typeof(T) != typeof(string))
{
throw new NotSupportedException("Host settings support bool, int, float and string values.");
}
if (legacy && typeof(T) == typeof(string))
{
throw new ArgumentException("String settings must use the extended snapshot.", "legacy");
}
foreach (ISetting setting in settings)
{
if (setting.Key == ((MelonPreferences_Entry)entry).Identifier)
{
throw new ArgumentException("Duplicate setting: " + ((MelonPreferences_Entry)entry).Identifier, "entry");
}
}
HostSetting<T> hostSetting = new HostSetting<T>(this, entry, vanillaValue, validate, live, legacy, localValue);
if (!hostSetting.IsValid(vanillaValue))
{
throw new ArgumentException("The vanilla value is invalid: " + ((MelonPreferences_Entry)entry).Identifier, "vanillaValue");
}
if (!hostSetting.IsValid(entry.DefaultValue))
{
throw new ArgumentException("The configured default is invalid: " + ((MelonPreferences_Entry)entry).Identifier, "entry");
}
settings.Add(hostSetting);
return hostSetting;
}
internal T GetValue<T>(string key, T localValue, T vanillaValue, bool live)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
if (lobby == CSteamID.Nil)
{
return localValue;
}
if (live && !authorityValid)
{
return vanillaValue;
}
if (snapshot == null || !snapshot.TryGetValue(key, out object value))
{
return vanillaValue;
}
return (T)value;
}
internal static void Update()
{
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
if (Time.realtimeSinceStartup < nextPoll)
{
return;
}
nextPoll = Time.realtimeSinceStartup + 1f;
foreach (HostSettings instance in Instances)
{
if (instance.lobby != CSteamID.Nil)
{
instance.Refresh();
}
}
}
private void Begin(CSteamID newLobby, bool host)
{
//IL_000f: Unknown result type (might be due to invalid IL or missing references)
//IL_0014: Unknown result type (might be due to invalid IL or missing references)
//IL_0029: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_003d: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0075: Unknown result type (might be due to invalid IL or missing references)
//IL_007a: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_008b: Unknown result type (might be due to invalid IL or missing references)
//IL_0090: Unknown result type (might be due to invalid IL or missing references)
//IL_0095: Unknown result type (might be due to invalid IL or missing references)
//IL_0097: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
if (settings.Count == 0 || (lobby == newLobby && (!host || hostCreated)))
{
return;
}
if (lobby != newLobby)
{
Reset();
}
lobby = newLobby;
isHost = false;
hostCreated = host;
authorityValid = false;
ownerChanged = false;
snapshot = null;
published = false;
hostPayload = null;
owner = CSteamID.Nil;
try
{
owner = SteamMatchmaking.GetLobbyOwner(lobby);
CSteamID steamID = SteamUser.GetSteamID();
if (owner == CSteamID.Nil || (host ? (owner != steamID) : (owner == steamID)))
{
owner = CSteamID.Nil;
Warn("Lobby owner does not identify the expected host; using vanilla settings.");
Changed();
return;
}
isHost = host;
authorityValid = true;
if (host)
{
CaptureHostSnapshot();
}
else
{
CaptureClientSnapshot();
}
}
catch (Exception ex)
{
Warn("Initial lobby settings failed; vanilla settings are frozen for this session: " + ex.GetType().Name + ": " + ex.Message);
}
Refresh();
Changed();
}
private static void EnsureCallback()
{
if (dataCallback == null)
{
dataCallback = Callback<LobbyDataUpdate_t>.Create((DispatchDelegate<LobbyDataUpdate_t>)OnLobbyDataUpdated);
}
}
private static void OnLobbyDataUpdated(LobbyDataUpdate_t callback)
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Unknown result type (might be due to invalid IL or missing references)
//IL_002e: Unknown result type (might be due to invalid IL or missing references)
//IL_0036: Unknown result type (might be due to invalid IL or missing references)
foreach (HostSettings instance in Instances)
{
if (instance.lobby.m_SteamID == callback.m_ulSteamIDLobby && callback.m_ulSteamIDMember == callback.m_ulSteamIDLobby)
{
if (callback.m_bSuccess == 0)
{
instance.Warn("Steam lobby data update failed; frozen session settings are retained.");
}
else
{
instance.Refresh();
}
}
}
}
private void CaptureHostSnapshot()
{
Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
StringBuilder stringBuilder = new StringBuilder(schemaVersion.ToString(CultureInfo.InvariantCulture));
for (int i = 0; i < settings.Count; i++)
{
ISetting setting = settings[i];
object localValue = setting.LocalValue;
dictionary.Add(setting.Key, localValue);
if (setting.Legacy)
{
stringBuilder.Append('\n').Append(setting.Key).Append('\t')
.Append(Format(localValue));
}
}
string s = stringBuilder.ToString();
if (Encoding.UTF8.GetByteCount(s) + 1 > 8192)
{
throw new FormatException("Settings snapshot exceeds the Steam metadata limit.");
}
hostPayload = s;
snapshot = dictionary;
modernRevision = 1L;
modernPayload = BuildModern(dictionary, modernRevision);
}
private unsafe void CaptureClientSnapshot()
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
string lobbyData = SteamMatchmaking.GetLobbyData(lobby, modernKey);
if (!string.IsNullOrEmpty(lobbyData))
{
snapshot = ParseModern(lobbyData, out modernRevision);
lastModernPayload = lobbyData;
MelonLogger.Msg("[" + modId + "] Host settings captured; live values follow the host, lobby values remain fixed.");
return;
}
string lobbyData2 = SteamMatchmaking.GetLobbyData(lobby, lobbyKey);
if (string.IsNullOrEmpty(lobbyData2))
{
Warn("Host snapshot is absent; vanilla settings are frozen for this session.");
return;
}
snapshot = ParseSnapshot(lobbyData2);
legacyHost = true;
string[] obj = new string[5] { "[", modId, "] Host settings snapshot frozen for lobby ", null, null };
CSteamID val = lobby;
obj[3] = ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString();
obj[4] = ".";
MelonLogger.Msg(string.Concat(obj));
}
private void Refresh()
{
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0042: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Unknown result type (might be due to invalid IL or missing references)
//IL_0051: Unknown result type (might be due to invalid IL or missing references)
//IL_002d: Unknown result type (might be due to invalid IL or missing references)
//IL_002f: Unknown result type (might be due to invalid IL or missing references)
bool flag = false;
try
{
EnsureCallback();
CSteamID lobbyOwner = SteamMatchmaking.GetLobbyOwner(lobby);
if (ownerChanged || owner == CSteamID.Nil || lobbyOwner != owner)
{
authorityValid = false;
if (lobbyOwner != CSteamID.Nil && lobbyOwner != owner)
{
ownerChanged = true;
}
Warn("Lobby owner changed or is unavailable; authority revoked and frozen session settings retained.");
return;
}
bool num = !authorityValid;
authorityValid = true;
flag = true;
if (num)
{
MelonLogger.Msg("[" + modId + "] Pinned lobby authority recovered; frozen settings retained.");
}
if (isHost)
{
if (hostPayload != null)
{
if (published)
{
RefreshHostLive();
}
else
{
Publish();
}
}
}
else
{
RefreshClientLive();
}
}
catch (Exception ex)
{
authorityValid = flag;
Warn("Refresh lobby settings failed; frozen session settings retained: " + ex.GetType().Name + ": " + ex.Message);
}
}
private unsafe void Publish()
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_001f: Unknown result type (might be due to invalid IL or missing references)
//IL_0086: Unknown result type (might be due to invalid IL or missing references)
//IL_008b: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Unknown result type (might be due to invalid IL or missing references)
//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
//IL_00ec: Unknown result type (might be due to invalid IL or missing references)
if (SteamMatchmaking.GetLobbyData(lobby, lobbyKey) != hostPayload && !SteamMatchmaking.SetLobbyData(lobby, lobbyKey, hostPayload))
{
CSteamID val = lobby;
Warn("SetLobbyData rejected the settings snapshot for lobby " + ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString() + ".");
}
else if (SteamMatchmaking.GetLobbyData(lobby, lobbyKey) != hostPayload)
{
CSteamID val = lobby;
Warn("Settings snapshot readback differs for lobby " + ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString() + ".");
}
else if (PublishModern(modernPayload))
{
published = true;
Recover();
string[] obj = new string[5] { "[", modId, "] Settings snapshot confirmed by local lobby readback for ", null, null };
CSteamID val = lobby;
obj[3] = ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString();
obj[4] = ".";
MelonLogger.Msg(string.Concat(obj));
}
}
private Dictionary<string, object> ParseSnapshot(string payload)
{
if (Encoding.UTF8.GetByteCount(payload) + 1 > 8192)
{
throw new FormatException("Settings snapshot exceeds the Steam metadata limit.");
}
string[] array = payload.Split('\n');
if (array[0] != schemaVersion.ToString(CultureInfo.InvariantCulture))
{
throw new FormatException("Settings snapshot schema mismatch.");
}
if (array.Length != settings.FindAll((ISetting s) => s.Legacy).Count + 1)
{
throw new FormatException("Settings snapshot has missing or extra keys.");
}
Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
for (int num = 1; num < array.Length; num++)
{
string text = array[num];
int num2 = text.IndexOf('\t');
if (num2 <= 0 || num2 != text.LastIndexOf('\t') || text.IndexOf('\r') >= 0)
{
throw new FormatException("Settings snapshot has an invalid entry delimiter.");
}
string key = text.Substring(0, num2);
if (dictionary.ContainsKey(key))
{
throw new FormatException("Settings snapshot has a duplicate or missing key.");
}
ISetting setting = settings.Find((ISetting candidate) => candidate.Key == key && candidate.Legacy);
if (setting == null)
{
throw new FormatException("Settings snapshot has an unknown key: " + key);
}
dictionary.Add(key, setting.Parse(text.Substring(num2 + 1)));
}
return dictionary;
}
private string BuildModern(Dictionary<string, object> values, long revision)
{
StringBuilder stringBuilder = new StringBuilder("2\t" + revision.ToString(CultureInfo.InvariantCulture));
foreach (ISetting setting in settings)
{
stringBuilder.Append('\n').Append(setting.Key).Append('\t')
.Append(Format(values[setting.Key]));
}
string text = stringBuilder.ToString();
if (Encoding.UTF8.GetByteCount(text) + 1 > 8192)
{
throw new FormatException("Extended settings exceed the Steam metadata limit.");
}
return text;
}
private Dictionary<string, object> ParseModern(string payload, out long revision)
{
if (Encoding.UTF8.GetByteCount(payload) + 1 > 8192)
{
throw new FormatException("Extended settings exceed the Steam metadata limit.");
}
string[] array = payload.Split('\n');
string[] array2 = array[0].Split('\t');
if (array2.Length != 2 || array2[0] != "2" || !long.TryParse(array2[1], NumberStyles.None, CultureInfo.InvariantCulture, out revision) || revision < 1)
{
throw new FormatException("Invalid extended settings schema or revision.");
}
Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
for (int i = 1; i < array.Length; i++)
{
int num = array[i].IndexOf('\t');
if (num <= 0 || num != array[i].LastIndexOf('\t') || array[i].IndexOf('\r') >= 0)
{
throw new FormatException("Invalid extended settings delimiter.");
}
string key = array[i].Substring(0, num);
if (!hashSet.Add(key))
{
throw new FormatException("Duplicate extended setting: " + key);
}
ISetting setting = settings.Find((ISetting s) => s.Key == key);
if (setting != null)
{
dictionary.Add(key, setting.Parse(array[i].Substring(num + 1)));
}
}
return dictionary;
}
private bool PublishModern(string payload)
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_001a: Unknown result type (might be due to invalid IL or missing references)
if (SteamMatchmaking.GetLobbyData(lobby, modernKey) != payload && !SteamMatchmaking.SetLobbyData(lobby, modernKey, payload))
{
Warn("SetLobbyData rejected the extended settings; last confirmed values retained.");
return false;
}
if (SteamMatchmaking.GetLobbyData(lobby, modernKey) != payload)
{
Warn("Extended settings readback differs; last confirmed values retained.");
return false;
}
return true;
}
private void RefreshHostLive()
{
if (snapshot == null)
{
return;
}
Dictionary<string, object> dictionary = new Dictionary<string, object>(snapshot, StringComparer.Ordinal);
bool flag = false;
foreach (ISetting setting in settings)
{
if (setting.Live)
{
object localValue = setting.LocalValue;
if (!dictionary.TryGetValue(setting.Key, out var value) || !object.Equals(value, localValue))
{
dictionary[setting.Key] = localValue;
flag = true;
}
}
}
if (!flag)
{
if (PublishModern(modernPayload))
{
Recover();
}
return;
}
long revision = checked(modernRevision + 1);
string payload = BuildModern(dictionary, revision);
if (PublishModern(payload))
{
snapshot = dictionary;
modernRevision = revision;
modernPayload = payload;
Recover();
Changed(this.LiveValuesChanged);
}
}
private void RefreshClientLive()
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
string lobbyData = SteamMatchmaking.GetLobbyData(lobby, modernKey);
if (string.IsNullOrEmpty(lobbyData) || lobbyData == lastModernPayload)
{
return;
}
long revision;
Dictionary<string, object> dictionary = ParseModern(lobbyData, out revision);
if (revision <= modernRevision)
{
Warn("Extended settings revision did not advance; last confirmed values retained.");
return;
}
Dictionary<string, object> dictionary2 = ((snapshot == null) ? new Dictionary<string, object>(StringComparer.Ordinal) : new Dictionary<string, object>(snapshot, StringComparer.Ordinal));
bool flag = false;
foreach (ISetting setting in settings)
{
if (setting.Live && dictionary.TryGetValue(setting.Key, out var value) && (!dictionary2.TryGetValue(setting.Key, out var value2) || !object.Equals(value2, value)))
{
dictionary2[setting.Key] = value;
flag = true;
}
}
snapshot = dictionary2;
modernRevision = revision;
lastModernPayload = lobbyData;
Recover();
if (flag)
{
Changed(this.LiveValuesChanged);
}
}
private static string Format(object value)
{
if (value is string s)
{
return "s:" + Convert.ToBase64String(Encoding.UTF8.GetBytes(s));
}
if (value is float num)
{
return num.ToString("R", CultureInfo.InvariantCulture);
}
if (value is bool)
{
if (!(bool)value)
{
return "false";
}
return "true";
}
return ((int)value).ToString(CultureInfo.InvariantCulture);
}
private void Warn(string reason)
{
if (warningReason == reason)
{
warningRepeats++;
return;
}
if (warningRepeats > 0)
{
MelonLogger.Warning("[" + modId + "] Previous settings warning repeated " + warningRepeats + " times.");
}
warningReason = reason;
warningRepeats = 0;
MelonLogger.Warning("[" + modId + "] " + reason);
}
private void Recover()
{
if (warningReason != null)
{
MelonLogger.Msg("[" + modId + "] Lobby settings recovered after " + (warningRepeats + 1) + " warning occurrences.");
warningReason = null;
warningRepeats = 0;
}
}
private void Changed()
{
Changed(this.EffectiveValuesChanged);
}
private void Changed(Action handlers)
{
if (handlers == null)
{
return;
}
Delegate[] invocationList = handlers.GetInvocationList();
for (int i = 0; i < invocationList.Length; i++)
{
Action action = (Action)invocationList[i];
try
{
action();
}
catch (Exception ex)
{
MelonLogger.Error("[" + modId + "] Apply effective settings failed.", ex);
}
}
}
private void Reset()
{
//IL_0001: Unknown result type (might be due to invalid IL or missing references)
//IL_0006: Unknown result type (might be due to invalid IL or missing references)
//IL_0011: Unknown result type (might be due to invalid IL or missing references)
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0021: Unknown result type (might be due to invalid IL or missing references)
bool num = lobby != CSteamID.Nil;
lobby = CSteamID.Nil;
owner = CSteamID.Nil;
isHost = false;
hostCreated = false;
authorityValid = false;
ownerChanged = false;
published = false;
snapshot = null;
hostPayload = null;
modernPayload = null;
lastModernPayload = null;
modernRevision = 0L;
legacyHost = false;
warningReason = null;
warningRepeats = 0;
if (num)
{
MelonLogger.Msg("[" + modId + "] Session settings cleared; local settings restored.");
Changed();
}
}
}
internal interface ISetting
{
string Key { get; }
object LocalValue { get; }
bool Live { get; }
bool Legacy { get; }
bool IsValid(object value);
object Parse(string value);
}
internal sealed class HostSetting<T> : ISetting
{
private readonly HostSettings owner;
private readonly MelonPreferences_Entry<T> entry;
private readonly T vanillaValue;
private readonly Func<T, bool> validate;
private readonly Func<T> localValue;
private bool invalidLocal;
public bool Live { get; }
public bool Legacy { get; }
internal T Value => owner.GetValue(Key, (T)LocalValue, vanillaValue, Live);
public string Key => ((MelonPreferences_Entry)entry).Identifier;
public object LocalValue
{
get
{
T val = ((localValue == null) ? entry.Value : localValue());
if (IsValid(val))
{
if (invalidLocal)
{
MelonLogger.Msg("Local setting recovered: " + Key);
}
invalidLocal = false;
return val;
}
if (!invalidLocal)
{
MelonLogger.Warning("Invalid local setting " + Key + "; using its configured default without overwriting the file.");
}
invalidLocal = true;
return entry.DefaultValue;
}
}
internal HostSetting(HostSettings owner, MelonPreferences_Entry<T> entry, T vanillaValue, Func<T, bool> validate, bool live, bool legacy, Func<T> localValue)
{
this.owner = owner;
this.entry = entry;
this.vanillaValue = vanillaValue;
this.validate = validate;
this.localValue = localValue;
Live = live;
Legacy = legacy;
}
public bool IsValid(object value)
{
if (!(value is T arg))
{
return false;
}
if (value is float f && (float.IsNaN(f) || float.IsInfinity(f)))
{
return false;
}
if (value is string text && (Encoding.UTF8.GetByteCount(text) > 1024 || text.IndexOf('\0') >= 0))
{
return false;
}
if (validate != null)
{
return validate(arg);
}
return true;
}
public object Parse(string value)
{
object obj;
int result2;
float result3;
if (typeof(T) == typeof(bool) && bool.TryParse(value, out var result))
{
obj = result;
}
else if (typeof(T) == typeof(int) && int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result2))
{
obj = result2;
}
else if (typeof(T) == typeof(float) && float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out result3))
{
obj = result3;
}
else
{
if (!(typeof(T) == typeof(string)) || !value.StartsWith("s:", StringComparison.Ordinal))
{
throw new FormatException("Invalid settings value: " + Key);
}
obj = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true).GetString(Convert.FromBase64String(value.Substring(2)));
}
if (!IsValid(obj))
{
throw new FormatException("Settings value is out of range: " + Key);
}
return obj;
}
}
internal static class ModVersion
{
internal const string Current = "1.2.0";
}
}
namespace EnemyDropLoot
{
public sealed class Core : MelonMod
{
public override void OnInitializeMelon()
{
EnemyDropLootPreferences.Initialize();
MelonLogger.Msg($"EnemyDropLoot initialized. Enabled={EnemyDropLootPreferences.Enabled}, DropChance={EnemyDropLootPreferences.DropChance:P0}, MaxDropsPerKill={EnemyDropLootPreferences.MaxDropsPerKill}");
}
public override void OnUpdate()
{
HostSettings.Update();
}
}
}
namespace EnemyDropLoot.Services
{
internal static class LootSpawnService
{
internal static bool TrySpawnLoot(VMonster monster, int itemMasterId)
{
//IL_002c: Unknown result type (might be due to invalid IL or missing references)
//IL_0031: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_003e: Unknown result type (might be due to invalid IL or missing references)
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: Unknown result type (might be due to invalid IL or missing references)
//IL_0062: Unknown result type (might be due to invalid IL or missing references)
//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
//IL_00db: Unknown result type (might be due to invalid IL or missing references)
VWorld vWorld = CoreAPI.GetVWorld();
if (vWorld == null)
{
MelonLogger.Warning($"EnemyDropLoot: Skipping loot item {itemMasterId} for monster {((VActor)monster).MasterID}: world is unavailable.");
return false;
}
Vector3 positionVector = ((VActor)monster).PositionVector;
Vector3 val = vWorld.FindNearestPoly(positionVector, 100f);
if (val == NavMeshConstants.INVALID_POSITION)
{
MelonLogger.Warning($"EnemyDropLoot: Skipping loot item {itemMasterId} for monster {((VActor)monster).MasterID}: no valid navmesh position near {positionVector}.");
return false;
}
ItemElement newItemElement = ((VActor)monster).VRoom.GetNewItemElement(itemMasterId, false, 1, 0, 0, 0);
if (newItemElement == null)
{
MelonLogger.Warning($"EnemyDropLoot: Failed to create loot item {itemMasterId} for monster {((VActor)monster).MasterID}.");
return false;
}
PosWithRot val2 = ((VActor)monster).Position.Clone();
val2.x = val.x;
val2.y = val.y;
val2.z = val.z;
if (((VActor)monster).VRoom.SpawnLootingObject(newItemElement, val2, ((VActor)monster).IsIndoor, (ReasonOfSpawn)4, 0, 0, 0L, false, false) == 0)
{
MelonLogger.Warning($"EnemyDropLoot: Failed to spawn loot item {itemMasterId} for monster {((VActor)monster).MasterID}.");
return false;
}
return true;
}
}
}
namespace EnemyDropLoot.Patches
{
[HarmonyPatch(typeof(DungeonRoom), "Initialize")]
internal static class DungeonRoomInitializePatch
{
private static void Postfix(DungeonRoom __instance)
{
LootPoolManager.ConfigureForDungeon(__instance);
}
}
[HarmonyPatch(typeof(IVroom), "OnVacateRoom", new Type[] { typeof(bool) })]
internal static class DungeonRoomVacatePatch
{
private static void Postfix(IVroom __instance)
{
if (__instance is DungeonRoom)
{
LootPoolManager.Reset();
}
}
}
[HarmonyPatch(typeof(InventoryController), "OnDead")]
internal static class FakeItemDeathDropPatch
{
private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
{
MethodInfo methodInfo = AccessTools.Method(typeof(InventoryController), "ExtractAllItemElement", new Type[1] { typeof(bool) }, (Type[])null);
MethodInfo operand = AccessTools.Method(typeof(FakeItemDeathDropPatch), "ExtractDeathDrops", (Type[])null, (Type[])null);
List<CodeInstruction> list = instructions.ToList();
int num = 0;
foreach (CodeInstruction item in list)
{
if (CodeInstructionExtensions.Calls(item, methodInfo))
{
item.opcode = OpCodes.Call;
item.operand = operand;
num++;
}
}
if (num != 1)
{
throw new InvalidOperationException($"EnemyDropLoot: InventoryController.OnDead must extract inventory exactly once; found {num} calls.");
}
return list;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private static Dictionary<int, ItemElement> ExtractDeathDrops(InventoryController inventory, bool turnOff)
{
Dictionary<int, ItemElement> dictionary = inventory.ExtractAllItemElement(turnOff);
if (!EnemyDropLootPreferences.RemoveFakeItemDrops)
{
return dictionary;
}
int[] array = (from item in dictionary
where item.Value.IsFake
select item.Key).ToArray();
foreach (int key in array)
{
dictionary.Remove(key);
}
return dictionary;
}
}
[HarmonyPatch(typeof(VMonster), "OnDying")]
internal static class VMonsterOnDyingPatch
{
private static void Postfix(VMonster __instance)
{
LootPoolManager.TryHandleMonsterDeath(__instance);
}
}
}
namespace EnemyDropLoot.Managers
{
internal static class LootPoolManager
{
private static readonly List<RandomSpawnedItemActorData> LootSources = new List<RandomSpawnedItemActorData>();
private static readonly List<int> FallbackItemIds = new List<int>();
private static IVroom _activeRoom = null;
private static bool _hasActiveRoom;
private static string _allowText;
private static string _blockText;
private static HashSet<int> _allowed = new HashSet<int>();
private static HashSet<int> _blocked = new HashSet<int>();
private static bool _emptyFilterReported;
internal static void Reset()
{
LootSources.Clear();
FallbackItemIds.Clear();
_hasActiveRoom = false;
_emptyFilterReported = false;
}
internal static void ConfigureForDungeon(DungeonRoom room)
{
//IL_0049: Unknown result type (might be due to invalid IL or missing references)
//IL_004f: Invalid comparison between Unknown and I4
Reset();
_activeRoom = (IVroom)(object)room;
_hasActiveRoom = true;
try
{
Dictionary<int, SpawnedActorData> fieldValue = ReflectionHelper.GetFieldValue<Dictionary<int, SpawnedActorData>>((object)room, "_spawnedActorDatas");
if (fieldValue == null)
{
MelonLogger.Warning("EnemyDropLoot: Unable to fetch spawn data for current dungeon.");
return;
}
foreach (SpawnedActorData value in fieldValue.Values)
{
if (value == null || (int)value.MarkerType != 1)
{
continue;
}
RandomSpawnedItemActorData val = (RandomSpawnedItemActorData)(object)((value is RandomSpawnedItemActorData) ? value : null);
if (val == null)
{
continue;
}
LootSources.Add(val);
foreach (KeyValuePair<int, (int, int)> candidate in val.Candidates)
{
if (!FallbackItemIds.Contains(candidate.Key))
{
FallbackItemIds.Add(candidate.Key);
}
}
}
MelonLogger.Msg("EnemyDropLoot: Loaded {0} loot spawn bundles with {1} unique items.", new object[2] { LootSources.Count, FallbackItemIds.Count });
}
catch (Exception arg)
{
MelonLogger.Error($"EnemyDropLoot: Failed to read dungeon loot pool. {arg}");
}
}
internal static void TryHandleMonsterDeath(VMonster monster)
{
if (!EnemyDropLootPreferences.Enabled || LootSources.Count == 0 || !_hasActiveRoom || ((VActor)monster).VRoom != _activeRoom)
{
return;
}
int num = EnemyDropLootPreferences.RollDropCount();
if (num <= 0)
{
return;
}
for (int i = 0; i < num; i++)
{
if (!TryPickItem(out var itemMasterId))
{
break;
}
if (!LootSpawnService.TrySpawnLoot(monster, itemMasterId))
{
break;
}
}
}
private static bool TryPickItem(out int itemMasterId)
{
itemMasterId = 0;
if (LootSources.Count == 0)
{
return false;
}
RefreshFilters();
if (_allowed.Count != 0 || _blocked.Count != 0)
{
int[] array = FallbackItemIds.Where(IsAllowed).ToArray();
if (array.Length == 0)
{
if (!_emptyFilterReported)
{
MelonLogger.Msg("EnemyDropLoot: No map loot matches the host's item filters; extra drops skipped.");
}
_emptyFilterReported = true;
return false;
}
for (int i = 0; i < 32; i++)
{
int pickedItemValue = LootSources[Random.Range(0, LootSources.Count)].GetPickedItemValue();
if (pickedItemValue != 0 && IsAllowed(pickedItemValue))
{
itemMasterId = pickedItemValue;
return true;
}
}
itemMasterId = array[Random.Range(0, array.Length)];
return true;
}
int num = LootSources[Random.Range(0, LootSources.Count)].GetPickedItemValue();
if (num == 0 && FallbackItemIds.Count > 0)
{
num = FallbackItemIds[Random.Range(0, FallbackItemIds.Count)];
}
if (num == 0)
{
return false;
}
itemMasterId = num;
return true;
}
private static bool IsAllowed(int id)
{
if (_allowed.Count == 0 || _allowed.Contains(id))
{
return !_blocked.Contains(id);
}
return false;
}
private static void RefreshFilters()
{
string allowedItemIds = EnemyDropLootPreferences.AllowedItemIds;
string blockedItemIds = EnemyDropLootPreferences.BlockedItemIds;
if (!(allowedItemIds == _allowText) || !(blockedItemIds == _blockText))
{
_allowed = ParseIds(allowedItemIds);
_blocked = ParseIds(blockedItemIds);
_allowText = allowedItemIds;
_blockText = blockedItemIds;
_emptyFilterReported = false;
}
}
private static HashSet<int> ParseIds(string text)
{
if (!string.IsNullOrWhiteSpace(text))
{
return new HashSet<int>(from id in text.Split(',')
select int.Parse(id, NumberStyles.Integer, CultureInfo.InvariantCulture));
}
return new HashSet<int>();
}
}
}
namespace EnemyDropLoot.Config
{
internal static class EnemyDropLootPreferences
{
private const string CategoryId = "EnemyDropLoot";
private static MelonPreferences_Category _category;
private static MelonPreferences_Entry<bool> _enabled;
private static MelonPreferences_Entry<float> _dropChance;
private static MelonPreferences_Entry<int> _maxDropsPerKill;
private static MelonPreferences_Entry<bool> _removeFakeItemDrops;
private static HostSettings _hostSettings;
private static HostSetting<bool> _hostEnabled;
private static HostSetting<float> _hostDropChance;
private static HostSetting<int> _hostMaxDropsPerKill;
private static HostSettings _fakeDropSettings;
private static HostSetting<bool> _hostRemoveFakeItemDrops;
private static HostSetting<string> _allowedItemIds;
private static HostSetting<string> _blockedItemIds;
internal static bool Enabled => _hostEnabled?.Value ?? false;
internal static float DropChance => Mathf.Clamp01(_hostDropChance?.Value ?? 0f);
internal static int MaxDropsPerKill => Mathf.Clamp(_hostMaxDropsPerKill?.Value ?? 0, 0, 100);
internal static bool RemoveFakeItemDrops
{
get
{
if (Enabled)
{
HostSettings fakeDropSettings = _fakeDropSettings;
if (fakeDropSettings != null && fakeDropSettings.IsHost)
{
return _hostRemoveFakeItemDrops?.Value ?? false;
}
}
return false;
}
}
internal static string AllowedItemIds => _allowedItemIds?.Value ?? "";
internal static string BlockedItemIds => _blockedItemIds?.Value ?? "";
internal static void Initialize()
{
if (_category == null)
{
_category = MelonPreferences.CreateCategory("EnemyDropLoot", "Enemy Drop Loot");
_enabled = CreateEntry("Enabled", defaultValue: true, "Enabled", "Toggle the Enemy Drop Loot mod. Default: true");
_dropChance = CreateEntry("DropChance", 0.1f, "Drop chance", "Chance (0-1) per roll that a slain enemy drops loot. Default: 0.1");
_maxDropsPerKill = CreateEntry("MaxDropsPerKill", 1, "Max drops per kill", "Maximum number of loot rolls performed per enemy. Default: 1");
_removeFakeItemDrops = CreateEntry("RemoveFakeItemDrops", defaultValue: false, "Remove fake item drops", "Prevent fake inventory items from dropping when a creature dies. Real items and extra loot are kept. Host setting. Default: false");
_hostSettings = new HostSettings("EnemyDropLoot");
_hostEnabled = _hostSettings.Register(_enabled, vanillaValue: false, null, live: true);
_hostDropChance = _hostSettings.Register(_dropChance, 0f, (float value) => value >= 0f && value <= 1f, live: true, legacy: true, () => Mathf.Clamp01(_dropChance.Value));
_hostMaxDropsPerKill = _hostSettings.Register(_maxDropsPerKill, 0, (int value) => value >= 0 && value <= 100, live: true, legacy: true, () => Mathf.Clamp(_maxDropsPerKill.Value, 0, 100));
_allowedItemIds = _hostSettings.Register(CreateEntry("AllowedItemIds", "", "Allowed Item IDs", "Host setting, live for subsequent drops. Comma-separated item master IDs allowed from the current map pool. Empty allows all map items; blocked IDs take priority. Up to 128 IDs."), "", IsValidItemList, live: true, legacy: false);
_blockedItemIds = _hostSettings.Register(CreateEntry("BlockedItemIds", "", "Blocked Item IDs", "Host setting, live for subsequent drops. Comma-separated item master IDs excluded from extra loot. Empty blocks none. Up to 128 IDs."), "", IsValidItemList, live: true, legacy: false);
_fakeDropSettings = new HostSettings("EnemyDropLoot.FakeDrops");
_hostRemoveFakeItemDrops = _fakeDropSettings.Register(_removeFakeItemDrops, vanillaValue: false, null, live: true);
}
}
private static MelonPreferences_Entry<T> CreateEntry<T>(string identifier, T defaultValue, string displayName, string? description = null)
{
if (_category == null)
{
throw new InvalidOperationException("Preference category not initialized.");
}
return _category.CreateEntry<T>(identifier, defaultValue, displayName, description, false, false, (ValueValidator)null, (string)null);
}
private static bool IsValidItemList(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return true;
}
string[] array = text.Split(',');
if (array.Length > 128)
{
return false;
}
string[] array2 = array;
for (int i = 0; i < array2.Length; i++)
{
if (!int.TryParse(array2[i], NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) || result <= 0)
{
return false;
}
}
return true;
}
internal static int RollDropCount()
{
if (!Enabled)
{
return 0;
}
int maxDropsPerKill = MaxDropsPerKill;
float dropChance = DropChance;
if (maxDropsPerKill == 0 || dropChance <= 0f)
{
return 0;
}
int num = 0;
for (int i = 0; i < maxDropsPerKill; i++)
{
if (dropChance >= 1f || Random.value < dropChance)
{
num++;
}
}
return num;
}
}
}