using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Logging; using FalseGods.Integration.SulfurTogether.Diagnostics; using FalseGods.RuntimeContracts.Diagnostics; using FalseGods.RuntimeContracts.Integration; using FalseGods.RuntimeContracts.Multiplayer; using FalseGods.RuntimeContracts.Transport; using Microsoft.CodeAnalysis; using SULFURTogether.Api; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("FalseGods.Integration.SulfurTogether")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+6e5740a80f810ab422551028733810b527fb614d")] [assembly: AssemblyProduct("FalseGods.Integration.SulfurTogether")] [assembly: AssemblyTitle("FalseGods.Integration.SulfurTogether")] [assembly: AssemblyVersion("1.0.0.0")] [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 FalseGods.Integration.SulfurTogether { internal sealed class StDestructibleOwnership : IDestructibleOwnership { private readonly ILogger? _logger; private bool _unavailable; public StDestructibleOwnership(ILogger? logger = null) { _logger = logger; } public void ClaimAsOurs(object destructible) { if (_unavailable) { return; } object obj = ((destructible is GameObject) ? destructible : null); if (obj == null) { object obj2 = ((destructible is Component) ? destructible : null); obj = ((obj2 != null) ? ((Component)obj2).gameObject : null); } GameObject val = (GameObject)obj; if ((Object)(object)val == (Object)null) { return; } try { NetExternalDestructibles.Exclude(val); } catch (Exception ex) { _unavailable = true; ILogger? logger = _logger; if (logger != null) { logger.LogWarning("[st-destructibles] the session layer would not take the claim (" + ex.Message + "); it will keep mirroring our crates, which can break the wrong one."); } } } } internal sealed class StEncounterChannel : IEncounterChannel, IDisposable { public const string ChannelId = "false_gods.encounter"; private readonly StPeerDirectory _peers; private readonly ILogger _logger; private IDisposable? _registration; public event Action? Received; public StEncounterChannel(StPeerDirectory peers, ILogger logger) { _peers = peers ?? throw new ArgumentNullException("peers"); _logger = logger ?? throw new ArgumentNullException("logger"); } public bool TryRegister() { if (_registration != null) { return true; } try { _registration = (IDisposable?)NetExternalChannel.Register("false_gods.encounter", (Action)OnBridgePayload); _logger.Log(string.Format("Encounter channel '{0}' registered on the ST bridge (API v{1}).", "false_gods.encounter", 1)); return true; } catch (InvalidOperationException ex) { _logger.LogWarning("Encounter channel 'false_gods.encounter' is already registered with the ST bridge: " + ex.Message); return false; } } public void Send(EncodedPayload payload, MessageDelivery delivery, MessageTarget target) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: 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_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) byte[] array = ((EncodedPayload)(ref payload)).ToArray(); string bridgePeerId = null; if ((int)((MessageTarget)(ref target)).Kind == 2 && !_peers.TryGetBridgeId(((MessageTarget)(ref target)).Peer, out bridgePeerId)) { _logger.LogWarning($"Dropping a send to unknown {((MessageTarget)(ref target)).Peer}: no bridge id is mapped for it."); } else if (!NetExternalChannel.Send("false_gods.encounter", array, MapDelivery(delivery), MapTarget(((MessageTarget)(ref target)).Kind), bridgePeerId)) { _logger.LogWarning($"Bridge refused a {delivery} send to {target} ({array.Length} bytes)."); } } public void Dispose() { try { _registration?.Dispose(); } catch (Exception ex) { _logger.LogWarning("Encounter channel unregister failed: " + ex.Message); } _registration = null; } private void OnBridgePayload(string senderBridgeId, byte[] payload) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(senderBridgeId) || payload == null) { _logger.LogWarning("Dropping a bridge payload with no sender id or no body."); } else { this.Received?.Invoke(_peers.Map(senderBridgeId), new EncodedPayload(payload)); } } private static ExternalDelivery MapDelivery(MessageDelivery delivery) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)delivery == 1) { return (ExternalDelivery)1; } return (ExternalDelivery)0; } private static ExternalTarget MapTarget(MessageTargetKind kind) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected I4, but got Unknown //IL_001f: Unknown result type (might be due to invalid IL or missing references) return (ExternalTarget)((int)kind switch { 0 => 0, 1 => 1, 2 => 2, _ => throw new ArgumentOutOfRangeException("kind", kind, "Unknown message target kind."), }); } } internal sealed class StMultiplayerSession : IMultiplayerSession { private readonly StPeerDirectory _peers; public SessionRole Role => ReadRole(); public bool IsActive => ReadIsActive(); public SessionPeerId LocalPeer => ReadLocalPeer(); public SessionPeerId HostPeer => ReadHostPeer(); public StMultiplayerSession(StPeerDirectory peers) { _peers = peers ?? throw new ArgumentNullException("peers"); } private static SessionRole ReadRole() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)NetSessionInfo.Role == 1) { return (SessionRole)0; } return (SessionRole)1; } private static bool ReadIsActive() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 if (NetSessionInfo.IsSessionActive) { return (int)NetSessionInfo.Role > 0; } return false; } private SessionPeerId ReadLocalPeer() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) string localPeerId = NetSessionInfo.LocalPeerId; if (!string.IsNullOrEmpty(localPeerId)) { return _peers.Map(localPeerId); } return default(SessionPeerId); } private SessionPeerId ReadHostPeer() { //IL_000c: 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_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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_0046: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList peers = NetSessionInfo.Peers; for (int i = 0; i < peers.Count; i++) { ExternalPeer val = peers[i]; if (((ExternalPeer)(ref val)).IsHost) { val = peers[i]; if (!string.IsNullOrEmpty(((ExternalPeer)(ref val)).PeerId)) { StPeerDirectory peers2 = _peers; val = peers[i]; return peers2.Map(((ExternalPeer)(ref val)).PeerId); } } } return default(SessionPeerId); } } internal sealed class StParticipantPeerMap : IParticipantPeerMap { private readonly StPeerDirectory _peers; public StParticipantPeerMap(StPeerDirectory peers) { _peers = peers ?? throw new ArgumentNullException("peers"); } public bool TryGetRemotePeer(int participantIndex, out SessionPeerId peer) { //IL_000c: 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_0067: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: 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) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList peers = NetSessionInfo.Peers; for (int i = 0; i < peers.Count; i++) { ExternalPeer val = peers[i]; if (((ExternalPeer)(ref val)).IsLocal) { continue; } val = peers[i]; if (((ExternalPeer)(ref val)).PlayerIndex == participantIndex) { val = peers[i]; string peerId = ((ExternalPeer)(ref val)).PeerId; if (!string.IsNullOrEmpty(peerId)) { peer = _peers.Map(peerId); return true; } } } peer = default(SessionPeerId); return false; } } internal sealed class StPeerDirectory { private readonly Dictionary _byBridgeId = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _byPeer = new Dictionary(); private int _next = 1; public SessionPeerId Map(string bridgePeerId) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(bridgePeerId)) { throw new ArgumentException("A bridge peer id must be non-empty.", "bridgePeerId"); } if (!_byBridgeId.TryGetValue(bridgePeerId, out var value)) { ((SessionPeerId)(ref value))..ctor(_next++); _byBridgeId.Add(bridgePeerId, value); _byPeer.Add(value, bridgePeerId); } return value; } public bool TryGetBridgeId(SessionPeerId peer, out string bridgePeerId) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return _byPeer.TryGetValue(peer, out bridgePeerId); } } internal sealed class StPlayerLifeQuery : IPlayerLifeQuery { private readonly ILogger? _logger; private bool _unavailable; public StPlayerLifeQuery(ILogger? logger = null) { _logger = logger; } public bool IsOutOfTheFight(object playerUnit) { if (playerUnit == null || _unavailable) { return false; } try { return NetPlayerLife.IsOutOfTheFight(playerUnit); } catch (Exception ex) { _unavailable = true; ILogger? logger = _logger; if (logger != null) { logger.LogWarning("[st-life] the session layer cannot say who is down (" + ex.Message + "); the boss will treat every player as still fighting."); } return false; } } } internal sealed class StPlayerRoster : IPlayerRoster { private readonly StPeerDirectory _peers; public IReadOnlyList Members => ReadMembers(); public StPlayerRoster(StPeerDirectory peers) { _peers = peers ?? throw new ArgumentNullException("peers"); } private IReadOnlyList ReadMembers() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: 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) List list = new List(); IReadOnlyList peers = NetSessionInfo.Peers; for (int i = 0; i < peers.Count; i++) { ExternalPeer val = peers[i]; string peerId = ((ExternalPeer)(ref val)).PeerId; if (!string.IsNullOrEmpty(peerId)) { list.Add(_peers.Map(peerId)); } } return list; } } internal sealed class StSpawnOwnership : ISpawnOwnership { private readonly ILogger? _logger; public StSpawnOwnership(ILogger? logger = null) { _logger = logger; } public IDisposable? DeclareHostAuthoritative(object spawnOwner) { MonoBehaviour val = (MonoBehaviour)((spawnOwner is MonoBehaviour) ? spawnOwner : null); if (val == null) { ILogger? logger = _logger; if (logger != null) { logger.LogWarning("[st-spawns] a spawn owner that is not a game component cannot be declared; runtime spawns will stay host-only."); } return null; } try { IExternalSpawnOwnerRegistration result = NetExternalSpawns.RegisterHostAuthoritativeOwner(val); ILogger? logger2 = _logger; if (logger2 != null) { logger2.Log("[st-spawns] '" + ((object)val).GetType().Name + "' declared host-authoritative; its runtime spawns are mirrored to clients as host-driven puppets."); } return (IDisposable?)result; } catch (Exception ex) { ILogger? logger3 = _logger; if (logger3 != null) { logger3.LogWarning("[st-spawns] the session layer would not take the declaration (" + ex.Message + "); runtime spawns will stay host-only."); } return null; } } } internal sealed class SulfurTogetherIntegration : IFalseGodsIntegration { public IMultiplayerSession Session { get; } public IEncounterChannel Channel { get; } public IPlayerRoster Roster { get; } public IParticipantPeerMap Players { get; } public ISpawnOwnership Spawns { get; } public IPlayerLifeQuery Lives { get; } public IDestructibleOwnership Destructibles { get; } public SulfurTogetherIntegration(IMultiplayerSession session, IEncounterChannel channel, IPlayerRoster roster, IParticipantPeerMap players, ISpawnOwnership spawns, IPlayerLifeQuery lives, IDestructibleOwnership destructibles) { Session = session ?? throw new ArgumentNullException("session"); Channel = channel ?? throw new ArgumentNullException("channel"); Roster = roster ?? throw new ArgumentNullException("roster"); Players = players ?? throw new ArgumentNullException("players"); Spawns = spawns ?? throw new ArgumentNullException("spawns"); Lives = lives ?? throw new ArgumentNullException("lives"); Destructibles = destructibles ?? throw new ArgumentNullException("destructibles"); } } [BepInPlugin("ryuka.sulfur.false_gods_sulfur_together", "False Gods - SULFUR Together Adapter", "0.4.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class SulfurTogetherPlugin : BaseUnityPlugin { public const string PluginGuid = "ryuka.sulfur.false_gods_sulfur_together"; public const string PluginName = "False Gods - SULFUR Together Adapter"; public const string PluginVersion = "0.4.0"; private const string BasePluginGuid = "ryuka.sulfur.false_gods"; private const string SulfurTogetherGuid = "com.ryuka.sulfur.together"; private ILogger _log; private StEncounterChannel? _channel; private SulfurTogetherIntegration? _integration; private IIntegrationRegistration? _token; private void Awake() { _log = (ILogger)(object)new BepInExLogger(((BaseUnityPlugin)this).Logger); ((BaseUnityPlugin)this).Logger.LogMessage((object)"False Gods - SULFUR Together Adapter 0.4.0 loaded."); TryCompose("plugin load"); } private void OnDestroy() { Revoke(); _channel?.Dispose(); _channel = null; } private void TryCompose(string trigger) { if (EnsureComposed()) { IIntegrationRegistration val = FalseGodsIntegrations.Register((IFalseGodsIntegration)(object)_integration); if (val != null) { _token = val; _log.Log("Multiplayer integration registered with the base plugin (" + trigger + ")."); } else if (_token != null) { _log.Log("Broker rejected the duplicate registration (" + trigger + "); the first registration stays authoritative."); } else { _log.LogWarning("Multiplayer integration already provided by someone else (" + trigger + "); staying inert."); } } } private void Revoke() { if (_token != null) { ((IDisposable)_token).Dispose(); _token = null; _log.Log("Registration token disposed; the base plugin falls back to the single-player composition."); } } private bool EnsureComposed() { if (_integration != null) { return true; } try { string text = DescribeBridge(); StPeerDirectory peers = new StPeerDirectory(); StEncounterChannel stEncounterChannel = new StEncounterChannel(peers, _log); if (!stEncounterChannel.TryRegister()) { _log.LogWarning("Encounter channel registration refused; multiplayer unavailable."); return false; } _channel = stEncounterChannel; _integration = new SulfurTogetherIntegration((IMultiplayerSession)(object)new StMultiplayerSession(peers), (IEncounterChannel)(object)stEncounterChannel, (IPlayerRoster)(object)new StPlayerRoster(peers), (IParticipantPeerMap)(object)new StParticipantPeerMap(peers), (ISpawnOwnership)(object)new StSpawnOwnership(_log), (IPlayerLifeQuery)(object)new StPlayerLifeQuery(_log), (IDestructibleOwnership)(object)new StDestructibleOwnership(_log)); _log.Log("ST adapter composed over " + text + "."); return true; } catch (Exception ex) when (ex is TypeLoadException || ex is MissingMethodException || ex is MissingFieldException || ex is TypeInitializationException || ex is BadImageFormatException) { _log.LogWarning("The installed SULFUR Together does not carry the expected public bridge (SULFURTogether.Api). Multiplayer unavailable; single-player is unaffected. (" + ex.GetType().Name + ": " + ex.Message + ")"); return false; } } [MethodImpl(MethodImplOptions.NoInlining)] private static string DescribeBridge() { return $"ST bridge API v{1}"; } } } namespace FalseGods.Integration.SulfurTogether.Diagnostics { internal sealed class BepInExLogger : ILogger { private readonly ManualLogSource _log; public BepInExLogger(ManualLogSource log) { _log = log ?? throw new ArgumentNullException("log"); } public void Log(string message) { _log.LogMessage((object)message); } public void LogWarning(string message) { _log.LogWarning((object)message); } } }