Merge pull request #113 from TorchAPI/session-mgr-cmp
Managers for Clients
This commit is contained in:
@@ -6,10 +6,12 @@
|
|||||||
<target xsi:type="File" name="main" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" fileName="Logs\Torch-${shortdate}.log" />
|
<target xsi:type="File" name="main" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" fileName="Logs\Torch-${shortdate}.log" />
|
||||||
<target xsi:type="File" name="chat" layout="${longdate} ${message}" fileName="Logs\Chat.log" />
|
<target xsi:type="File" name="chat" layout="${longdate} ${message}" fileName="Logs\Chat.log" />
|
||||||
<target xsi:type="ColoredConsole" name="console" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" />
|
<target xsi:type="ColoredConsole" name="console" layout="${time} [${level:uppercase=true}] ${logger}: ${message}" />
|
||||||
|
<target xsi:type="File" name="patch" layout="${message}" fileName="Logs\patch.log"/>
|
||||||
</targets>
|
</targets>
|
||||||
|
|
||||||
<rules>
|
<rules>
|
||||||
<logger name="*" minlevel="Info" writeTo="main, console" />
|
<logger name="*" minlevel="Info" writeTo="main, console" />
|
||||||
<logger name="Chat" minlevel="Info" writeTo="chat" />
|
<logger name="Chat" minlevel="Info" writeTo="chat" />
|
||||||
|
<!--<logger name="Torch.Managers.PatchManager.*" minlevel="Trace" writeTo="patch"/>-->
|
||||||
</rules>
|
</rules>
|
||||||
</nlog>
|
</nlog>
|
@@ -1,31 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Torch.API
|
|
||||||
{
|
|
||||||
public interface IChatMessage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The time the message was created.
|
|
||||||
/// </summary>
|
|
||||||
DateTime Timestamp { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The SteamID of the message author.
|
|
||||||
/// </summary>
|
|
||||||
ulong SteamId { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The name of the message author.
|
|
||||||
/// </summary>
|
|
||||||
string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The content of the message.
|
|
||||||
/// </summary>
|
|
||||||
string Message { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -44,10 +44,6 @@ namespace Torch.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
ITorchConfig Config { get; }
|
ITorchConfig Config { get; }
|
||||||
|
|
||||||
/// <inheritdoc cref="IMultiplayerManager"/>
|
|
||||||
[Obsolete]
|
|
||||||
IMultiplayerManager Multiplayer { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc cref="IPluginManager"/>
|
/// <inheritdoc cref="IPluginManager"/>
|
||||||
[Obsolete]
|
[Obsolete]
|
||||||
IPluginManager Plugins { get; }
|
IPluginManager Plugins { get; }
|
||||||
@@ -55,6 +51,12 @@ namespace Torch.API
|
|||||||
/// <inheritdoc cref="IDependencyManager"/>
|
/// <inheritdoc cref="IDependencyManager"/>
|
||||||
IDependencyManager Managers { get; }
|
IDependencyManager Managers { get; }
|
||||||
|
|
||||||
|
[Obsolete("Prefer using Managers.GetManager for global managers")]
|
||||||
|
T GetManager<T>() where T : class, IManager;
|
||||||
|
|
||||||
|
[Obsolete("Prefer using Managers.AddManager for global managers")]
|
||||||
|
bool AddManager<T>(T manager) where T : class, IManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The binary version of the current instance.
|
/// The binary version of the current instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -101,6 +103,16 @@ namespace Torch.API
|
|||||||
/// Initialize the Torch instance.
|
/// Initialize the Torch instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current state of the game this instance of torch is controlling.
|
||||||
|
/// </summary>
|
||||||
|
TorchGameState GameState { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised when <see cref="GameState"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
event TorchGameStateChangedDel GameStateChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -119,6 +131,6 @@ namespace Torch.API
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ITorchClient : ITorchBase
|
public interface ITorchClient : ITorchBase
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
108
Torch.API/Managers/IChatManagerClient.cs
Normal file
108
Torch.API/Managers/IChatManagerClient.cs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Game.Multiplayer;
|
||||||
|
using VRage.Network;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a scripted or user chat message.
|
||||||
|
/// </summary>
|
||||||
|
public struct TorchChatMessage
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new torch chat message with the given author and message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="author">Author's name</param>
|
||||||
|
/// <param name="message">Message</param>
|
||||||
|
public TorchChatMessage(string author, string message)
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Now;
|
||||||
|
AuthorSteamId = null;
|
||||||
|
Author = author;
|
||||||
|
Message = message;
|
||||||
|
Font = "Blue";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new torch chat message with the given author and message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authorSteamId">Author's steam ID</param>
|
||||||
|
/// <param name="message">Message</param>
|
||||||
|
public TorchChatMessage(ulong authorSteamId, string message)
|
||||||
|
{
|
||||||
|
Timestamp = DateTime.Now;
|
||||||
|
AuthorSteamId = authorSteamId;
|
||||||
|
Author = MyMultiplayer.Static?.GetMemberName(authorSteamId) ?? "Player";
|
||||||
|
Message = message;
|
||||||
|
Font = "Blue";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This message's timestamp.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime Timestamp;
|
||||||
|
/// <summary>
|
||||||
|
/// The author's steam ID, if available. Else, null.
|
||||||
|
/// </summary>
|
||||||
|
public ulong? AuthorSteamId;
|
||||||
|
/// <summary>
|
||||||
|
/// The author's name, if available. Else, null.
|
||||||
|
/// </summary>
|
||||||
|
public string Author;
|
||||||
|
/// <summary>
|
||||||
|
/// The message contents.
|
||||||
|
/// </summary>
|
||||||
|
public string Message;
|
||||||
|
/// <summary>
|
||||||
|
/// The font, or null if default.
|
||||||
|
/// </summary>
|
||||||
|
public string Font;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback used to indicate that a messaage has been recieved.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
|
||||||
|
public delegate void MessageRecievedDel(TorchChatMessage msg, ref bool consumed);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback used to indicate the user is attempting to send a message locally.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg">Message the user is attempting to send</param>
|
||||||
|
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
|
||||||
|
public delegate void MessageSendingDel(string msg, ref bool consumed);
|
||||||
|
|
||||||
|
public interface IChatManagerClient : IManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is raised when a message addressed to us is recieved. <see cref="MessageRecievedDel"/>
|
||||||
|
/// </summary>
|
||||||
|
event MessageRecievedDel MessageRecieved;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event that is raised when we are attempting to send a message. <see cref="MessageSendingDel"/>
|
||||||
|
/// </summary>
|
||||||
|
event MessageSendingDel MessageSending;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggers the <see cref="MessageSending"/> event,
|
||||||
|
/// typically raised by the user entering text into the chat window.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message to send</param>
|
||||||
|
void SendMessageAsSelf(string message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a message on the UI given an author name and a message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="author">Author name</param>
|
||||||
|
/// <param name="message">Message content</param>
|
||||||
|
/// <param name="font">font to use</param>
|
||||||
|
void DisplayMessageOnSelf(string author, string message, string font = "Blue" );
|
||||||
|
}
|
||||||
|
}
|
45
Torch.API/Managers/IChatManagerServer.cs
Normal file
45
Torch.API/Managers/IChatManagerServer.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VRage.Network;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback used to indicate the server has recieved a message to process and forward on to others.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authorId">Steam ID of the user sending a message</param>
|
||||||
|
/// <param name="msg">Message the user is attempting to send</param>
|
||||||
|
/// <param name="consumed">If true, this event has been consumed and should be ignored</param>
|
||||||
|
public delegate void MessageProcessingDel(TorchChatMessage msg, ref bool consumed);
|
||||||
|
|
||||||
|
public interface IChatManagerServer : IChatManagerClient
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Event triggered when the server has recieved a message and should process it. <see cref="MessageProcessingDel"/>
|
||||||
|
/// </summary>
|
||||||
|
event MessageProcessingDel MessageProcessing;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a message with the given author and message to the given player, or all players by default.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="authorId">Author's steam ID</param>
|
||||||
|
/// <param name="message">The message to send</param>
|
||||||
|
/// <param name="targetSteamId">Player to send the message to, or everyone by default</param>
|
||||||
|
void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0);
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a scripted message with the given author and message to the given player, or all players by default.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="author">Author name</param>
|
||||||
|
/// <param name="message">The message to send</param>
|
||||||
|
/// <param name="font">Font to use</param>
|
||||||
|
/// <param name="targetSteamId">Player to send the message to, or everyone by default</param>
|
||||||
|
void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,61 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using VRage.Game;
|
|
||||||
using VRage.Game.ModAPI;
|
|
||||||
|
|
||||||
namespace Torch.API.Managers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Delegate for received messages.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="message">Message data.</param>
|
|
||||||
/// <param name="sendToOthers">Flag to broadcast message to other players.</param>
|
|
||||||
public delegate void MessageReceivedDel(IChatMessage message, ref bool sendToOthers);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// API for multiplayer related functions.
|
|
||||||
/// </summary>
|
|
||||||
public interface IMultiplayerManager : IManager
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when a player joins.
|
|
||||||
/// </summary>
|
|
||||||
event Action<IPlayer> PlayerJoined;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when a player disconnects.
|
|
||||||
/// </summary>
|
|
||||||
event Action<IPlayer> PlayerLeft;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when a chat message is received.
|
|
||||||
/// </summary>
|
|
||||||
event MessageReceivedDel MessageReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Send a chat message to all or one specific player.
|
|
||||||
/// </summary>
|
|
||||||
void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Blue);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Kicks the player from the game.
|
|
||||||
/// </summary>
|
|
||||||
void KickPlayer(ulong steamId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Bans or unbans a player from the game.
|
|
||||||
/// </summary>
|
|
||||||
void BanPlayer(ulong steamId, bool banned = true);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a player by their Steam64 ID or returns null if the player isn't found.
|
|
||||||
/// </summary>
|
|
||||||
IMyPlayer GetPlayerBySteamId(ulong id);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a player by their display name or returns null if the player isn't found.
|
|
||||||
/// </summary>
|
|
||||||
IMyPlayer GetPlayerByName(string name);
|
|
||||||
}
|
|
||||||
}
|
|
41
Torch.API/Managers/IMultiplayerManagerBase.cs
Normal file
41
Torch.API/Managers/IMultiplayerManagerBase.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using VRage.Game;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API for multiplayer related functions common to servers and clients.
|
||||||
|
/// </summary>
|
||||||
|
public interface IMultiplayerManagerBase : IManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a player joins.
|
||||||
|
/// </summary>
|
||||||
|
event Action<IPlayer> PlayerJoined;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fired when a player disconnects.
|
||||||
|
/// </summary>
|
||||||
|
event Action<IPlayer> PlayerLeft;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a player by their Steam64 ID or returns null if the player isn't found.
|
||||||
|
/// </summary>
|
||||||
|
IMyPlayer GetPlayerBySteamId(ulong id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a player by their display name or returns null if the player isn't found.
|
||||||
|
/// </summary>
|
||||||
|
IMyPlayer GetPlayerByName(string name);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the steam username of a member's steam ID
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId">steam ID</param>
|
||||||
|
/// <returns>steam username</returns>
|
||||||
|
string GetSteamUsername(ulong steamId);
|
||||||
|
}
|
||||||
|
}
|
12
Torch.API/Managers/IMultiplayerManagerClient.cs
Normal file
12
Torch.API/Managers/IMultiplayerManagerClient.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
public interface IMultiplayerManagerClient : IMultiplayerManagerBase
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
36
Torch.API/Managers/IMultiplayerManagerServer.cs
Normal file
36
Torch.API/Managers/IMultiplayerManagerServer.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.API.Managers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// API for multiplayer functions that exist on servers and lobbies
|
||||||
|
/// </summary>
|
||||||
|
public interface IMultiplayerManagerServer : IMultiplayerManagerBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Kicks the player from the game.
|
||||||
|
/// </summary>
|
||||||
|
void KickPlayer(ulong steamId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bans or unbans a player from the game.
|
||||||
|
/// </summary>
|
||||||
|
void BanPlayer(ulong steamId, bool banned = true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of the banned SteamID's
|
||||||
|
/// </summary>
|
||||||
|
IReadOnlyList<ulong> BannedPlayers { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the player with the given SteamID is banned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="steamId">The SteamID of the player.</param>
|
||||||
|
/// <returns>True if the player is banned; otherwise false.</returns>
|
||||||
|
bool IsBanned(ulong steamId);
|
||||||
|
}
|
||||||
|
}
|
@@ -18,6 +18,12 @@ namespace Torch.API.Managers
|
|||||||
/// Register a network handler.
|
/// Register a network handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void RegisterNetworkHandler(INetworkHandler handler);
|
void RegisterNetworkHandler(INetworkHandler handler);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unregister a network handler.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>true if the handler was unregistered, false if it wasn't registered to begin with</returns>
|
||||||
|
bool UnregisterNetworkHandler(INetworkHandler handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -33,6 +39,7 @@ namespace Torch.API.Managers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes a network message.
|
/// Processes a network message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <returns>true if the message should be discarded</returns>
|
||||||
bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
|
bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,5 +25,15 @@ namespace Torch.API.Session
|
|||||||
|
|
||||||
/// <inheritdoc cref="IDependencyManager"/>
|
/// <inheritdoc cref="IDependencyManager"/>
|
||||||
IDependencyManager Managers { get; }
|
IDependencyManager Managers { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current state of the session
|
||||||
|
/// </summary>
|
||||||
|
TorchSessionState State { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised when the <see cref="State"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
event TorchSessionStateChangedDel StateChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,6 +27,11 @@ namespace Torch.API.Session
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
ITorchSession CurrentSession { get; }
|
ITorchSession CurrentSession { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when any <see cref="ITorchSession"/> <see cref="ITorchSession.State"/> changes.
|
||||||
|
/// </summary>
|
||||||
|
event TorchSessionStateChangedDel SessionStateChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the given factory as a supplier for session based managers
|
/// Adds the given factory as a supplier for session based managers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
38
Torch.API/Session/TorchSessionState.cs
Normal file
38
Torch.API/Session/TorchSessionState.cs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.API.Session
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the state of a <see cref="ITorchSession"/>
|
||||||
|
/// </summary>
|
||||||
|
public enum TorchSessionState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The session has been created, and is now loading.
|
||||||
|
/// </summary>
|
||||||
|
Loading,
|
||||||
|
/// <summary>
|
||||||
|
/// The session has loaded, and is now running.
|
||||||
|
/// </summary>
|
||||||
|
Loaded,
|
||||||
|
/// <summary>
|
||||||
|
/// The session was running, and is now unloading.
|
||||||
|
/// </summary>
|
||||||
|
Unloading,
|
||||||
|
/// <summary>
|
||||||
|
/// The session was unloading, and is now unloaded and stopped.
|
||||||
|
/// </summary>
|
||||||
|
Unloaded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback raised when a session's state changes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="session">The session who had a state change</param>
|
||||||
|
/// <param name="newState">The session's new state</param>
|
||||||
|
public delegate void TorchSessionStateChangedDel(ITorchSession session, TorchSessionState newState);
|
||||||
|
}
|
@@ -160,15 +160,18 @@
|
|||||||
<Link>Properties\AssemblyVersion.cs</Link>
|
<Link>Properties\AssemblyVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="ConnectionState.cs" />
|
<Compile Include="ConnectionState.cs" />
|
||||||
<Compile Include="IChatMessage.cs" />
|
|
||||||
<Compile Include="ITorchConfig.cs" />
|
<Compile Include="ITorchConfig.cs" />
|
||||||
<Compile Include="Managers\DependencyManagerExtensions.cs" />
|
<Compile Include="Managers\DependencyManagerExtensions.cs" />
|
||||||
<Compile Include="Managers\DependencyProviderExtensions.cs" />
|
<Compile Include="Managers\DependencyProviderExtensions.cs" />
|
||||||
|
<Compile Include="Managers\IChatManagerClient.cs" />
|
||||||
|
<Compile Include="Managers\IChatManagerServer.cs" />
|
||||||
<Compile Include="Managers\IDependencyManager.cs" />
|
<Compile Include="Managers\IDependencyManager.cs" />
|
||||||
<Compile Include="Managers\IDependencyProvider.cs" />
|
<Compile Include="Managers\IDependencyProvider.cs" />
|
||||||
<Compile Include="Managers\IManager.cs" />
|
<Compile Include="Managers\IManager.cs" />
|
||||||
<Compile Include="Managers\IMultiplayerManager.cs" />
|
<Compile Include="Managers\IMultiplayerManagerClient.cs" />
|
||||||
|
<Compile Include="Managers\IMultiplayerManagerBase.cs" />
|
||||||
<Compile Include="IPlayer.cs" />
|
<Compile Include="IPlayer.cs" />
|
||||||
|
<Compile Include="Managers\IMultiplayerManagerServer.cs" />
|
||||||
<Compile Include="Managers\INetworkManager.cs" />
|
<Compile Include="Managers\INetworkManager.cs" />
|
||||||
<Compile Include="Managers\IPluginManager.cs" />
|
<Compile Include="Managers\IPluginManager.cs" />
|
||||||
<Compile Include="Plugins\ITorchPlugin.cs" />
|
<Compile Include="Plugins\ITorchPlugin.cs" />
|
||||||
@@ -182,6 +185,8 @@
|
|||||||
<Compile Include="ModAPI\TorchAPI.cs" />
|
<Compile Include="ModAPI\TorchAPI.cs" />
|
||||||
<Compile Include="Session\ITorchSession.cs" />
|
<Compile Include="Session\ITorchSession.cs" />
|
||||||
<Compile Include="Session\ITorchSessionManager.cs" />
|
<Compile Include="Session\ITorchSessionManager.cs" />
|
||||||
|
<Compile Include="Session\TorchSessionState.cs" />
|
||||||
|
<Compile Include="TorchGameState.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
47
Torch.API/TorchGameState.cs
Normal file
47
Torch.API/TorchGameState.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox;
|
||||||
|
|
||||||
|
namespace Torch.API
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the state of a <see cref="MySandboxGame"/>
|
||||||
|
/// </summary>
|
||||||
|
public enum TorchGameState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The game is currently being created.
|
||||||
|
/// </summary>
|
||||||
|
Creating,
|
||||||
|
/// <summary>
|
||||||
|
/// The game has been created and is ready to begin loading.
|
||||||
|
/// </summary>
|
||||||
|
Created,
|
||||||
|
/// <summary>
|
||||||
|
/// The game is currently loading.
|
||||||
|
/// </summary>
|
||||||
|
Loading,
|
||||||
|
/// <summary>
|
||||||
|
/// The game is fully loaded and ready to start sessions
|
||||||
|
/// </summary>
|
||||||
|
Loaded,
|
||||||
|
/// <summary>
|
||||||
|
/// The game is beginning the unload sequence
|
||||||
|
/// </summary>
|
||||||
|
Unloading,
|
||||||
|
/// <summary>
|
||||||
|
/// The game has been shutdown and is no longer active
|
||||||
|
/// </summary>
|
||||||
|
Unloaded
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback raised when a game's state changes
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="game">The game who had a state change</param>
|
||||||
|
/// <param name="newState">The game's new state</param>
|
||||||
|
public delegate void TorchGameStateChangedDel(MySandboxGame game, TorchGameState newState);
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Torch.Client;
|
using Torch.Client;
|
||||||
using Torch.Tests;
|
using Torch.Tests;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
@@ -29,6 +30,10 @@ namespace Torch.Client.Tests
|
|||||||
|
|
||||||
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> MemberInfo => Manager().MemberInfo;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Events => Manager().Events;
|
||||||
|
|
||||||
#region Binding
|
#region Binding
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(Getters))]
|
[MemberData(nameof(Getters))]
|
||||||
@@ -62,6 +67,28 @@ namespace Torch.Client.Tests
|
|||||||
if (field.Field.IsStatic)
|
if (field.Field.IsStatic)
|
||||||
Assert.NotNull(field.Field.GetValue(null));
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(MemberInfo))]
|
||||||
|
public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Events))]
|
||||||
|
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
Torch.Client/Manager/MultiplayerManagerClient.cs
Normal file
32
Torch.Client/Manager/MultiplayerManagerClient.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers;
|
||||||
|
|
||||||
|
namespace Torch.Client.Manager
|
||||||
|
{
|
||||||
|
public class MultiplayerManagerClient : MultiplayerManagerBase, IMultiplayerManagerClient
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MultiplayerManagerClient(ITorchBase torch) : base(torch) { }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
base.Attach();
|
||||||
|
MyMultiplayer.Static.ClientJoined += RaiseClientJoined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
MyMultiplayer.Static.ClientJoined -= RaiseClientJoined;
|
||||||
|
base.Detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
Torch.Client/Manager/MultiplayerManagerLobby.cs
Normal file
44
Torch.Client/Manager/MultiplayerManagerLobby.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers;
|
||||||
|
|
||||||
|
namespace Torch.Client.Manager
|
||||||
|
{
|
||||||
|
public class MultiplayerManagerLobby : MultiplayerManagerBase, IMultiplayerManagerServer
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<ulong> BannedPlayers => new List<ulong>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MultiplayerManagerLobby(ITorchBase torch) : base(torch) { }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void BanPlayer(ulong steamId, bool banned = true) => Torch.Invoke(() => MyMultiplayer.Static.BanClient(steamId, banned));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsBanned(ulong steamId) => false;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
base.Attach();
|
||||||
|
MyMultiplayer.Static.ClientJoined += RaiseClientJoined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
MyMultiplayer.Static.ClientJoined -= RaiseClientJoined;
|
||||||
|
base.Detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -121,6 +121,8 @@
|
|||||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||||
<Link>Properties\AssemblyVersion.cs</Link>
|
<Link>Properties\AssemblyVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Manager\MultiplayerManagerClient.cs" />
|
||||||
|
<Compile Include="Manager\MultiplayerManagerLobby.cs" />
|
||||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
<Compile Include="TorchClient.cs" />
|
<Compile Include="TorchClient.cs" />
|
||||||
<Compile Include="TorchClientConfig.cs" />
|
<Compile Include="TorchClientConfig.cs" />
|
||||||
@@ -167,6 +169,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup />
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
<Import Project="$(SolutionDir)\TransformOnBuild.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@@ -4,12 +4,17 @@ using System.IO;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using Sandbox;
|
using Sandbox;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
using Sandbox.Engine.Networking;
|
using Sandbox.Engine.Networking;
|
||||||
using Sandbox.Engine.Platform;
|
using Sandbox.Engine.Platform;
|
||||||
using Sandbox.Game;
|
using Sandbox.Game;
|
||||||
using SpaceEngineers.Game;
|
using SpaceEngineers.Game;
|
||||||
using VRage.Steam;
|
using VRage.Steam;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Session;
|
||||||
|
using Torch.Client.Manager;
|
||||||
|
using Torch.Session;
|
||||||
using VRage;
|
using VRage;
|
||||||
using VRage.FileSystem;
|
using VRage.FileSystem;
|
||||||
using VRage.GameServices;
|
using VRage.GameServices;
|
||||||
@@ -27,6 +32,13 @@ namespace Torch.Client
|
|||||||
public TorchClient()
|
public TorchClient()
|
||||||
{
|
{
|
||||||
Config = new TorchClientConfig();
|
Config = new TorchClientConfig();
|
||||||
|
var sessionManager = Managers.GetManager<ITorchSessionManager>();
|
||||||
|
sessionManager.AddFactory((x) => MyMultiplayer.Static is MyMultiplayerLobby
|
||||||
|
? new MultiplayerManagerLobby(this)
|
||||||
|
: null);
|
||||||
|
sessionManager.AddFactory((x) => MyMultiplayer.Static is MyMultiplayerClientBase
|
||||||
|
? new MultiplayerManagerClient(this)
|
||||||
|
: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Init()
|
public override void Init()
|
||||||
|
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Sandbox.Game.Gui;
|
||||||
using Sandbox.Graphics;
|
using Sandbox.Graphics;
|
||||||
using Sandbox.Graphics.GUI;
|
using Sandbox.Graphics.GUI;
|
||||||
using Sandbox.Gui;
|
using Sandbox.Gui;
|
||||||
@@ -16,6 +17,15 @@ namespace Torch.Client
|
|||||||
{
|
{
|
||||||
public class TorchMainMenuScreen : MyGuiScreenMainMenu
|
public class TorchMainMenuScreen : MyGuiScreenMainMenu
|
||||||
{
|
{
|
||||||
|
public TorchMainMenuScreen()
|
||||||
|
: this(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public TorchMainMenuScreen(bool pauseGame)
|
||||||
|
: base(pauseGame)
|
||||||
|
{
|
||||||
|
}
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void RecreateControls(bool constructor)
|
public override void RecreateControls(bool constructor)
|
||||||
{
|
{
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Torch.Tests;
|
using Torch.Tests;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
@@ -28,6 +29,10 @@ namespace Torch.Server.Tests
|
|||||||
|
|
||||||
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> MemberInfo => Manager().MemberInfo;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Events => Manager().Events;
|
||||||
|
|
||||||
#region Binding
|
#region Binding
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(Getters))]
|
[MemberData(nameof(Getters))]
|
||||||
@@ -61,6 +66,17 @@ namespace Torch.Server.Tests
|
|||||||
if (field.Field.IsStatic)
|
if (field.Field.IsStatic)
|
||||||
Assert.NotNull(field.Field.GetValue(null));
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Events))]
|
||||||
|
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
173
Torch.Server/Managers/MultiplayerManagerDedicated.cs
Normal file
173
Torch.Server/Managers/MultiplayerManagerDedicated.cs
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using NLog.Fluent;
|
||||||
|
using Sandbox;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Managers;
|
||||||
|
using Torch.Utils;
|
||||||
|
using Torch.ViewModels;
|
||||||
|
using VRage.GameServices;
|
||||||
|
using VRage.Network;
|
||||||
|
using VRage.Steam;
|
||||||
|
|
||||||
|
namespace Torch.Server.Managers
|
||||||
|
{
|
||||||
|
public class MultiplayerManagerDedicated : MultiplayerManagerBase, IMultiplayerManagerServer
|
||||||
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedGetter(Name = "m_members")]
|
||||||
|
private static Func<MyDedicatedServerBase, List<ulong>> _members;
|
||||||
|
[ReflectedGetter(Name = "m_waitingForGroup")]
|
||||||
|
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IReadOnlyList<ulong> BannedPlayers => MySandboxGame.ConfigDedicated.Banned;
|
||||||
|
|
||||||
|
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public MultiplayerManagerDedicated(ITorchBase torch) : base(torch) { }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void BanPlayer(ulong steamId, bool banned = true)
|
||||||
|
{
|
||||||
|
Torch.Invoke(() =>
|
||||||
|
{
|
||||||
|
MyMultiplayer.Static.BanClient(steamId, banned);
|
||||||
|
if (_gameOwnerIds.ContainsKey(steamId))
|
||||||
|
MyMultiplayer.Static.BanClient(_gameOwnerIds[steamId], banned);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsBanned(ulong steamId) => _isClientBanned.Invoke(MyMultiplayer.Static, steamId) || MySandboxGame.ConfigDedicated.Banned.Contains(steamId);
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
base.Attach();
|
||||||
|
_gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke();
|
||||||
|
_gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke();
|
||||||
|
_gameServerValidateAuthTicketReplacer.Replace(new Action<ulong, JoinResult, ulong>(ValidateAuthTicketResponse), MyGameService.GameServer);
|
||||||
|
_gameServerUserGroupStatusReplacer.Replace(new Action<ulong, ulong, bool, bool>(UserGroupStatusResponse), MyGameService.GameServer);
|
||||||
|
_log.Info("Inserted steam authentication intercept");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
if (_gameServerValidateAuthTicketReplacer != null && _gameServerValidateAuthTicketReplacer.Replaced)
|
||||||
|
_gameServerValidateAuthTicketReplacer.Restore(MyGameService.GameServer);
|
||||||
|
if (_gameServerUserGroupStatusReplacer != null && _gameServerUserGroupStatusReplacer.Replaced)
|
||||||
|
_gameServerUserGroupStatusReplacer.Restore(MyGameService.GameServer);
|
||||||
|
_log.Info("Removed steam authentication intercept");
|
||||||
|
base.Detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.ValidateAuthTicketResponse), typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
|
||||||
|
private static Func<ReflectedEventReplacer> _gameServerValidateAuthTicketFactory;
|
||||||
|
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.UserGroupStatusResponse), typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
|
||||||
|
private static Func<ReflectedEventReplacer> _gameServerUserGroupStatusFactory;
|
||||||
|
private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer;
|
||||||
|
private ReflectedEventReplacer _gameServerUserGroupStatusReplacer;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
#region CustomAuth
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase), Name = "ConvertSteamIDFrom64")]
|
||||||
|
private static Func<ulong, string> _convertSteamIDFrom64;
|
||||||
|
|
||||||
|
[ReflectedStaticMethod(Type = typeof(MyGameService), Name = "GetServerAccountType")]
|
||||||
|
private static Func<ulong, MyGameServiceAccountType> _getServerAccountType;
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = "UserAccepted")]
|
||||||
|
private static Action<MyDedicatedServerBase, ulong> _userAcceptedImpl;
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = "UserRejected")]
|
||||||
|
private static Action<MyDedicatedServerBase, ulong, JoinResult> _userRejected;
|
||||||
|
[ReflectedMethod(Name = "IsClientBanned")]
|
||||||
|
private static Func<MyMultiplayerBase, ulong, bool> _isClientBanned;
|
||||||
|
[ReflectedMethod(Name = "IsClientKicked")]
|
||||||
|
private static Func<MyMultiplayerBase, ulong, bool> _isClientKicked;
|
||||||
|
[ReflectedMethod(Name = "RaiseClientKicked")]
|
||||||
|
private static Action<MyMultiplayerBase, ulong> _raiseClientKicked;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
//Largely copied from SE
|
||||||
|
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner)
|
||||||
|
{
|
||||||
|
_log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
|
||||||
|
if (IsBanned(steamOwner))
|
||||||
|
{
|
||||||
|
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins);
|
||||||
|
_raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||||
|
}
|
||||||
|
else if (_isClientKicked.Invoke(MyMultiplayer.Static, steamOwner))
|
||||||
|
{
|
||||||
|
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently);
|
||||||
|
_raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
||||||
|
}
|
||||||
|
if (response != JoinResult.OK)
|
||||||
|
{
|
||||||
|
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit)
|
||||||
|
{
|
||||||
|
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
|
||||||
|
MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) ||
|
||||||
|
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(steamID)))
|
||||||
|
{
|
||||||
|
this.UserAccepted(steamID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_getServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
|
||||||
|
{
|
||||||
|
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
||||||
|
{
|
||||||
|
_waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
|
||||||
|
{
|
||||||
|
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId))
|
||||||
|
{
|
||||||
|
if (member || officer)
|
||||||
|
UserAccepted(userId);
|
||||||
|
else
|
||||||
|
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private void UserAccepted(ulong steamId)
|
||||||
|
{
|
||||||
|
_userAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
|
||||||
|
base.RaiseClientJoined(steamId);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
@@ -182,6 +182,9 @@
|
|||||||
<HintPath>..\GameBinaries\VRage.Scripting.dll</HintPath>
|
<HintPath>..\GameBinaries\VRage.Scripting.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="VRage.Steam">
|
||||||
|
<HintPath>..\GameBinaries\VRage.Steam.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
<Reference Include="WindowsBase" />
|
<Reference Include="WindowsBase" />
|
||||||
<Reference Include="PresentationCore" />
|
<Reference Include="PresentationCore" />
|
||||||
<Reference Include="PresentationFramework" />
|
<Reference Include="PresentationFramework" />
|
||||||
@@ -191,6 +194,7 @@
|
|||||||
<Link>Properties\AssemblyVersion.cs</Link>
|
<Link>Properties\AssemblyVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="ListBoxExtensions.cs" />
|
<Compile Include="ListBoxExtensions.cs" />
|
||||||
|
<Compile Include="Managers\MultiplayerManagerDedicated.cs" />
|
||||||
<Compile Include="Managers\InstanceManager.cs" />
|
<Compile Include="Managers\InstanceManager.cs" />
|
||||||
<Compile Include="NativeMethods.cs" />
|
<Compile Include="NativeMethods.cs" />
|
||||||
<Compile Include="Initializer.cs" />
|
<Compile Include="Initializer.cs" />
|
||||||
|
@@ -17,6 +17,8 @@ using Sandbox.Game.Multiplayer;
|
|||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
using SteamSDK;
|
using SteamSDK;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Session;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
using Torch.Server.Managers;
|
using Torch.Server.Managers;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
@@ -63,6 +65,9 @@ namespace Torch.Server
|
|||||||
AddManager(DedicatedInstance);
|
AddManager(DedicatedInstance);
|
||||||
Config = config ?? new TorchConfig();
|
Config = config ?? new TorchConfig();
|
||||||
MyFakes.ENABLE_INFINARIO = false;
|
MyFakes.ENABLE_INFINARIO = false;
|
||||||
|
|
||||||
|
var sessionManager = Managers.GetManager<ITorchSessionManager>();
|
||||||
|
sessionManager.AddFactory((x) => new MultiplayerManagerDedicated(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -90,7 +95,6 @@ namespace Torch.Server
|
|||||||
MyGlobalTypeMetadata.Static.Init();
|
MyGlobalTypeMetadata.Static.Init();
|
||||||
|
|
||||||
GetManager<InstanceManager>().LoadInstance(Config.InstancePath);
|
GetManager<InstanceManager>().LoadInstance(Config.InstancePath);
|
||||||
Plugins.LoadPlugins();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InvokeBeforeRun()
|
private void InvokeBeforeRun()
|
||||||
@@ -253,19 +257,20 @@ namespace Torch.Server
|
|||||||
{
|
{
|
||||||
case SaveGameStatus.Success:
|
case SaveGameStatus.Success:
|
||||||
Log.Info("Save completed.");
|
Log.Info("Save completed.");
|
||||||
Multiplayer.SendMessage("Saved game.", playerId: callerId);
|
// TODO
|
||||||
|
// Multiplayer.SendMessage("Saved game.", playerId: callerId);
|
||||||
break;
|
break;
|
||||||
case SaveGameStatus.SaveInProgress:
|
case SaveGameStatus.SaveInProgress:
|
||||||
Log.Error("Save failed, a save is already in progress.");
|
Log.Error("Save failed, a save is already in progress.");
|
||||||
Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red);
|
// Multiplayer.SendMessage("Save failed, a save is already in progress.", playerId: callerId, font: MyFontEnum.Red);
|
||||||
break;
|
break;
|
||||||
case SaveGameStatus.GameNotReady:
|
case SaveGameStatus.GameNotReady:
|
||||||
Log.Error("Save failed, game was not ready.");
|
Log.Error("Save failed, game was not ready.");
|
||||||
Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red);
|
// Multiplayer.SendMessage("Save failed, game was not ready.", playerId: callerId, font: MyFontEnum.Red);
|
||||||
break;
|
break;
|
||||||
case SaveGameStatus.TimedOut:
|
case SaveGameStatus.TimedOut:
|
||||||
Log.Error("Save failed, save timed out.");
|
Log.Error("Save failed, save timed out.");
|
||||||
Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red);
|
// Multiplayer.SendMessage("Save failed, save timed out.", playerId: callerId, font: MyFontEnum.Red);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Specialized;
|
using System.Collections.Specialized;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
@@ -20,7 +21,11 @@ using Sandbox.Engine.Multiplayer;
|
|||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
using SteamSDK;
|
using SteamSDK;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Session;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
|
using Torch.Server.Managers;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
namespace Torch.Server
|
namespace Torch.Server
|
||||||
{
|
{
|
||||||
@@ -30,7 +35,6 @@ namespace Torch.Server
|
|||||||
public partial class ChatControl : UserControl
|
public partial class ChatControl : UserControl
|
||||||
{
|
{
|
||||||
private TorchBase _server;
|
private TorchBase _server;
|
||||||
private MultiplayerManager _multiplayer;
|
|
||||||
|
|
||||||
public ChatControl()
|
public ChatControl()
|
||||||
{
|
{
|
||||||
@@ -40,33 +44,75 @@ namespace Torch.Server
|
|||||||
public void BindServer(ITorchServer server)
|
public void BindServer(ITorchServer server)
|
||||||
{
|
{
|
||||||
_server = (TorchBase)server;
|
_server = (TorchBase)server;
|
||||||
_multiplayer = (MultiplayerManager)server.Multiplayer;
|
Dispatcher.Invoke(() =>
|
||||||
DataContext = _multiplayer;
|
{
|
||||||
|
ChatItems.Inlines.Clear();
|
||||||
|
});
|
||||||
|
|
||||||
ChatItems.Inlines.Clear();
|
var sessionManager = server.Managers.GetManager<ITorchSessionManager>();
|
||||||
_multiplayer.ChatHistory.ForEach(InsertMessage);
|
if (sessionManager != null)
|
||||||
if (_multiplayer.ChatHistory is INotifyCollectionChanged ncc)
|
sessionManager.SessionStateChanged += SessionStateChanged;
|
||||||
ncc.CollectionChanged += ChatHistory_CollectionChanged;
|
|
||||||
ChatScroller.ScrollToBottom();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ChatHistory_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
private void SessionStateChanged(ITorchSession session, TorchSessionState state)
|
||||||
{
|
{
|
||||||
foreach (IChatMessage msg in e.NewItems)
|
switch (state)
|
||||||
InsertMessage(msg);
|
{
|
||||||
|
case TorchSessionState.Loading:
|
||||||
|
Dispatcher.Invoke(() => ChatItems.Inlines.Clear());
|
||||||
|
break;
|
||||||
|
case TorchSessionState.Loaded:
|
||||||
|
{
|
||||||
|
var chatMgr = session.Managers.GetManager<IChatManagerClient>();
|
||||||
|
if (chatMgr != null)
|
||||||
|
chatMgr.MessageRecieved += OnMessageRecieved;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TorchSessionState.Unloading:
|
||||||
|
{
|
||||||
|
var chatMgr = session.Managers.GetManager<IChatManagerClient>();
|
||||||
|
if (chatMgr != null)
|
||||||
|
chatMgr.MessageRecieved -= OnMessageRecieved;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TorchSessionState.Unloaded:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(state), state, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InsertMessage(IChatMessage msg)
|
private void OnMessageRecieved(TorchChatMessage msg, ref bool consumed)
|
||||||
{
|
{
|
||||||
bool atBottom = ChatScroller.VerticalOffset + 8 > ChatScroller.ScrollableHeight;
|
InsertMessage(msg);
|
||||||
var span = new Span();
|
}
|
||||||
span.Inlines.Add($"{msg.Timestamp} ");
|
|
||||||
span.Inlines.Add(new Run(msg.Name) { Foreground = msg.Name == "Server" ? Brushes.DarkBlue : Brushes.Blue });
|
private static readonly Dictionary<string, Brush> _brushes = new Dictionary<string, Brush>();
|
||||||
span.Inlines.Add($": {msg.Message}");
|
private static Brush LookupBrush(string font)
|
||||||
span.Inlines.Add(new LineBreak());
|
{
|
||||||
ChatItems.Inlines.Add(span);
|
if (_brushes.TryGetValue(font, out Brush result))
|
||||||
if (atBottom)
|
return result;
|
||||||
ChatScroller.ScrollToBottom();
|
Brush brush = typeof(Brushes).GetField(font, BindingFlags.Static)?.GetValue(null) as Brush ?? Brushes.Blue;
|
||||||
|
_brushes.Add(font, brush);
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertMessage(TorchChatMessage msg)
|
||||||
|
{
|
||||||
|
if (Dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
bool atBottom = ChatScroller.VerticalOffset + 8 > ChatScroller.ScrollableHeight;
|
||||||
|
var span = new Span();
|
||||||
|
span.Inlines.Add($"{msg.Timestamp} ");
|
||||||
|
span.Inlines.Add(new Run(msg.Author) { Foreground = LookupBrush(msg.Font) });
|
||||||
|
span.Inlines.Add($": {msg.Message}");
|
||||||
|
span.Inlines.Add(new LineBreak());
|
||||||
|
ChatItems.Inlines.Add(span);
|
||||||
|
if (atBottom)
|
||||||
|
ChatScroller.ScrollToBottom();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Dispatcher.Invoke(() => InsertMessage(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendButton_Click(object sender, RoutedEventArgs e)
|
private void SendButton_Click(object sender, RoutedEventArgs e)
|
||||||
@@ -87,27 +133,22 @@ namespace Torch.Server
|
|||||||
if (string.IsNullOrEmpty(text))
|
if (string.IsNullOrEmpty(text))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var commands = _server.Commands;
|
var commands = _server.CurrentSession?.Managers.GetManager<Torch.Commands.CommandManager>();
|
||||||
if (commands.IsCommand(text))
|
if (commands != null && commands.IsCommand(text))
|
||||||
{
|
{
|
||||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", text));
|
InsertMessage(new TorchChatMessage("Server", text) { Font = MyFontEnum.DarkBlue });
|
||||||
_server.Invoke(() =>
|
_server.Invoke(() =>
|
||||||
{
|
{
|
||||||
var response = commands.HandleCommandFromServer(text);
|
string response = commands.HandleCommandFromServer(text);
|
||||||
Dispatcher.BeginInvoke(() => OnMessageEntered_Callback(response));
|
if (!string.IsNullOrWhiteSpace(response))
|
||||||
|
InsertMessage(new TorchChatMessage("Server", response) { Font = MyFontEnum.Blue });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_server.Multiplayer.SendMessage(text);
|
_server.CurrentSession?.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf(text);
|
||||||
}
|
}
|
||||||
Message.Text = "";
|
Message.Text = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMessageEntered_Callback(string response)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(response))
|
|
||||||
_multiplayer.ChatHistory.Add(new ChatMessage(DateTime.Now, 0, "Server", response));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,7 +20,10 @@ using Sandbox.Game.World;
|
|||||||
using Sandbox.ModAPI;
|
using Sandbox.ModAPI;
|
||||||
using SteamSDK;
|
using SteamSDK;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Session;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
|
using Torch.Server.Managers;
|
||||||
using Torch.ViewModels;
|
using Torch.ViewModels;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
|
|
||||||
@@ -41,19 +44,34 @@ namespace Torch.Server
|
|||||||
public void BindServer(ITorchServer server)
|
public void BindServer(ITorchServer server)
|
||||||
{
|
{
|
||||||
_server = server;
|
_server = server;
|
||||||
DataContext = (MultiplayerManager)_server.Multiplayer;
|
|
||||||
|
var sessionManager = server.Managers.GetManager<ITorchSessionManager>();
|
||||||
|
sessionManager.SessionStateChanged += SessionStateChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SessionStateChanged(ITorchSession session, TorchSessionState newState)
|
||||||
|
{
|
||||||
|
switch (newState)
|
||||||
|
{
|
||||||
|
case TorchSessionState.Loaded:
|
||||||
|
Dispatcher.Invoke(() => DataContext = _server?.CurrentSession?.Managers.GetManager<MultiplayerManagerDedicated>());
|
||||||
|
break;
|
||||||
|
case TorchSessionState.Unloading:
|
||||||
|
Dispatcher.Invoke(() => DataContext = null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void KickButton_Click(object sender, RoutedEventArgs e)
|
private void KickButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
||||||
_server.Multiplayer.KickPlayer(player.Key);
|
_server.CurrentSession?.Managers.GetManager<IMultiplayerManagerServer>()?.KickPlayer(player.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BanButton_Click(object sender, RoutedEventArgs e)
|
private void BanButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var player = (KeyValuePair<ulong, PlayerViewModel>) PlayerList.SelectedItem;
|
var player = (KeyValuePair<ulong, PlayerViewModel>)PlayerList.SelectedItem;
|
||||||
_server.Multiplayer.BanPlayer(player.Key);
|
_server.CurrentSession?.Managers.GetManager<IMultiplayerManagerServer>()?.BanPlayer(player.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ namespace Torch.Tests
|
|||||||
{
|
{
|
||||||
TestUtils.Init();
|
TestUtils.Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ReflectionTestManager _manager = new ReflectionTestManager().Init(typeof(ReflectionTestBinding));
|
private static ReflectionTestManager _manager = new ReflectionTestManager().Init(typeof(ReflectionTestBinding));
|
||||||
public static IEnumerable<object[]> Getters => _manager.Getters;
|
public static IEnumerable<object[]> Getters => _manager.Getters;
|
||||||
|
|
||||||
@@ -19,6 +20,10 @@ namespace Torch.Tests
|
|||||||
|
|
||||||
public static IEnumerable<object[]> Invokers => _manager.Invokers;
|
public static IEnumerable<object[]> Invokers => _manager.Invokers;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> MemberInfo => _manager.MemberInfo;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Events => _manager.Events;
|
||||||
|
|
||||||
#region Binding
|
#region Binding
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(Getters))]
|
[MemberData(nameof(Getters))]
|
||||||
@@ -52,6 +57,28 @@ namespace Torch.Tests
|
|||||||
if (field.Field.IsStatic)
|
if (field.Field.IsStatic)
|
||||||
Assert.NotNull(field.Field.GetValue(null));
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(MemberInfo))]
|
||||||
|
public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Events))]
|
||||||
|
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Results
|
#region Results
|
||||||
@@ -79,10 +106,51 @@ namespace Torch.Tests
|
|||||||
{
|
{
|
||||||
return k >= 0;
|
return k >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public event Action Event1;
|
||||||
|
|
||||||
|
public ReflectionTestTarget()
|
||||||
|
{
|
||||||
|
Event1 += Callback1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Callback1Flag = false;
|
||||||
|
public void Callback1()
|
||||||
|
{
|
||||||
|
Callback1Flag = true;
|
||||||
|
}
|
||||||
|
public bool Callback2Flag = false;
|
||||||
|
public void Callback2()
|
||||||
|
{
|
||||||
|
Callback2Flag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RaiseEvent()
|
||||||
|
{
|
||||||
|
Event1?.Invoke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ReflectionTestBinding
|
private class ReflectionTestBinding
|
||||||
{
|
{
|
||||||
|
#region Instance
|
||||||
|
#region MemberInfo
|
||||||
|
[ReflectedFieldInfo(typeof(ReflectionTestTarget), "TestField")]
|
||||||
|
public static FieldInfo TestFieldInfo;
|
||||||
|
|
||||||
|
[ReflectedPropertyInfo(typeof(ReflectionTestTarget), "TestProperty")]
|
||||||
|
public static PropertyInfo TestPropertyInfo;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall")]
|
||||||
|
public static MethodInfo TestMethodInfoGeneral;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall", Parameters = new[] { typeof(int) })]
|
||||||
|
public static MethodInfo TestMethodInfoExplicitArgs;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCall", ReturnType = typeof(bool))]
|
||||||
|
public static MethodInfo TestMethodInfoExplicitReturn;
|
||||||
|
#endregion
|
||||||
|
|
||||||
[ReflectedGetter(Name = "TestField")]
|
[ReflectedGetter(Name = "TestField")]
|
||||||
public static Func<ReflectionTestTarget, int> TestFieldGetter;
|
public static Func<ReflectionTestTarget, int> TestFieldGetter;
|
||||||
[ReflectedSetter(Name = "TestField")]
|
[ReflectedSetter(Name = "TestField")]
|
||||||
@@ -96,7 +164,27 @@ namespace Torch.Tests
|
|||||||
[ReflectedMethod]
|
[ReflectedMethod]
|
||||||
public static Func<ReflectionTestTarget, int, bool> TestCall;
|
public static Func<ReflectionTestTarget, int, bool> TestCall;
|
||||||
|
|
||||||
|
[ReflectedEventReplace(typeof(ReflectionTestTarget), "Event1", typeof(ReflectionTestTarget), "Callback1")]
|
||||||
|
public static Func<ReflectedEventReplacer> TestEventReplacer;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Static
|
||||||
|
#region MemberInfo
|
||||||
|
[ReflectedFieldInfo(typeof(ReflectionTestTarget), "TestFieldStatic")]
|
||||||
|
public static FieldInfo TestStaticFieldInfo;
|
||||||
|
|
||||||
|
[ReflectedPropertyInfo(typeof(ReflectionTestTarget), "TestPropertyStatic")]
|
||||||
|
public static PropertyInfo TestStaticPropertyInfo;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic")]
|
||||||
|
public static MethodInfo TestStaticMethodInfoGeneral;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic", Parameters = new[] { typeof(int) })]
|
||||||
|
public static MethodInfo TestStaticMethodInfoExplicitArgs;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(ReflectionTestTarget), "TestCallStatic", ReturnType = typeof(bool))]
|
||||||
|
public static MethodInfo TestStaticMethodInfoExplicitReturn;
|
||||||
|
#endregion
|
||||||
[ReflectedGetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
|
[ReflectedGetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
|
||||||
public static Func<int> TestStaticFieldGetter;
|
public static Func<int> TestStaticFieldGetter;
|
||||||
[ReflectedSetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
|
[ReflectedSetter(Name = "TestFieldStatic", Type = typeof(ReflectionTestTarget))]
|
||||||
@@ -109,6 +197,7 @@ namespace Torch.Tests
|
|||||||
|
|
||||||
[ReflectedStaticMethod(Type = typeof(ReflectionTestTarget))]
|
[ReflectedStaticMethod(Type = typeof(ReflectionTestTarget))]
|
||||||
public static Func<int, bool> TestCallStatic;
|
public static Func<int, bool> TestCallStatic;
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -215,7 +304,33 @@ namespace Torch.Tests
|
|||||||
Assert.True(ReflectionTestBinding.TestCallStatic.Invoke(1));
|
Assert.True(ReflectionTestBinding.TestCallStatic.Invoke(1));
|
||||||
Assert.False(ReflectionTestBinding.TestCallStatic.Invoke(-1));
|
Assert.False(ReflectionTestBinding.TestCallStatic.Invoke(-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestInstanceEventReplace()
|
||||||
|
{
|
||||||
|
var target = new ReflectionTestTarget();
|
||||||
|
target.Callback1Flag = false;
|
||||||
|
target.RaiseEvent();
|
||||||
|
Assert.True(target.Callback1Flag, "Control test failed");
|
||||||
|
|
||||||
|
target.Callback1Flag = false;
|
||||||
|
target.Callback2Flag = false;
|
||||||
|
ReflectedEventReplacer binder = ReflectionTestBinding.TestEventReplacer.Invoke();
|
||||||
|
Assert.True(binder.Test(target), "Binder was unable to find the requested method");
|
||||||
|
|
||||||
|
binder.Replace(new Action(() => target.Callback2()), target);
|
||||||
|
target.RaiseEvent();
|
||||||
|
Assert.True(target.Callback2Flag, "Substitute callback wasn't called");
|
||||||
|
Assert.False(target.Callback1Flag, "Original callback wasn't removed");
|
||||||
|
|
||||||
|
target.Callback1Flag = false;
|
||||||
|
target.Callback2Flag = false;
|
||||||
|
binder.Restore(target);
|
||||||
|
target.RaiseEvent();
|
||||||
|
Assert.False(target.Callback2Flag, "Substitute callback wasn't removed");
|
||||||
|
Assert.True(target.Callback1Flag, "Original callback wasn't restored");
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,12 +28,16 @@ namespace Torch.Tests
|
|||||||
private readonly HashSet<object[]> _getters = new HashSet<object[]>();
|
private readonly HashSet<object[]> _getters = new HashSet<object[]>();
|
||||||
private readonly HashSet<object[]> _setters = new HashSet<object[]>();
|
private readonly HashSet<object[]> _setters = new HashSet<object[]>();
|
||||||
private readonly HashSet<object[]> _invokers = new HashSet<object[]>();
|
private readonly HashSet<object[]> _invokers = new HashSet<object[]>();
|
||||||
|
private readonly HashSet<object[]> _memberInfo = new HashSet<object[]>();
|
||||||
|
private readonly HashSet<object[]> _events = new HashSet<object[]>();
|
||||||
|
|
||||||
public ReflectionTestManager()
|
public ReflectionTestManager()
|
||||||
{
|
{
|
||||||
_getters.Add(new object[] { new FieldRef(null) });
|
_getters.Add(new object[] { new FieldRef(null) });
|
||||||
_setters.Add(new object[] { new FieldRef(null) });
|
_setters.Add(new object[] { new FieldRef(null) });
|
||||||
_invokers.Add(new object[] { new FieldRef(null) });
|
_invokers.Add(new object[] { new FieldRef(null) });
|
||||||
|
_memberInfo.Add(new object[] {new FieldRef(null)});
|
||||||
|
_events.Add(new object[] {new FieldRef(null)});
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReflectionTestManager Init(Assembly asm)
|
public ReflectionTestManager Init(Assembly asm)
|
||||||
@@ -50,12 +54,36 @@ namespace Torch.Tests
|
|||||||
BindingFlags.Public |
|
BindingFlags.Public |
|
||||||
BindingFlags.NonPublic))
|
BindingFlags.NonPublic))
|
||||||
{
|
{
|
||||||
if (field.GetCustomAttribute<ReflectedMethodAttribute>() != null)
|
var args = new object[] { new FieldRef(field) };
|
||||||
_invokers.Add(new object[] { new FieldRef(field) });
|
foreach (ReflectedMemberAttribute attr in field.GetCustomAttributes<ReflectedMemberAttribute>())
|
||||||
if (field.GetCustomAttribute<ReflectedGetterAttribute>() != null)
|
{
|
||||||
_getters.Add(new object[] { new FieldRef(field) });
|
if (!field.IsStatic)
|
||||||
if (field.GetCustomAttribute<ReflectedSetterAttribute>() != null)
|
throw new ArgumentException("Field must be static to be reflected");
|
||||||
_setters.Add(new object[] { new FieldRef(field) });
|
switch (attr)
|
||||||
|
{
|
||||||
|
case ReflectedMethodAttribute rma:
|
||||||
|
_invokers.Add(args);
|
||||||
|
break;
|
||||||
|
case ReflectedGetterAttribute rga:
|
||||||
|
_getters.Add(args);
|
||||||
|
break;
|
||||||
|
case ReflectedSetterAttribute rsa:
|
||||||
|
_setters.Add(args);
|
||||||
|
break;
|
||||||
|
case ReflectedFieldInfoAttribute rfia:
|
||||||
|
case ReflectedPropertyInfoAttribute rpia:
|
||||||
|
case ReflectedMethodInfoAttribute rmia:
|
||||||
|
_memberInfo.Add(args);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var reflectedEventReplacer = field.GetCustomAttribute<ReflectedEventReplaceAttribute>();
|
||||||
|
if (reflectedEventReplacer != null)
|
||||||
|
{
|
||||||
|
if (!field.IsStatic)
|
||||||
|
throw new ArgumentException("Field must be static to be reflected");
|
||||||
|
_events.Add(args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -66,6 +94,10 @@ namespace Torch.Tests
|
|||||||
|
|
||||||
public IEnumerable<object[]> Invokers => _invokers;
|
public IEnumerable<object[]> Invokers => _invokers;
|
||||||
|
|
||||||
|
public IEnumerable<object[]> MemberInfo => _memberInfo;
|
||||||
|
|
||||||
|
public IEnumerable<object[]> Events => _events;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -27,6 +28,10 @@ namespace Torch.Tests
|
|||||||
|
|
||||||
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
public static IEnumerable<object[]> Invokers => Manager().Invokers;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> MemberInfo => Manager().MemberInfo;
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> Events => Manager().Events;
|
||||||
|
|
||||||
#region Binding
|
#region Binding
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(Getters))]
|
[MemberData(nameof(Getters))]
|
||||||
@@ -60,6 +65,28 @@ namespace Torch.Tests
|
|||||||
if (field.Field.IsStatic)
|
if (field.Field.IsStatic)
|
||||||
Assert.NotNull(field.Field.GetValue(null));
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(MemberInfo))]
|
||||||
|
public void TestBindingMemberInfo(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
Assert.NotNull(field.Field.GetValue(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Events))]
|
||||||
|
public void TestBindingEvents(ReflectionTestManager.FieldRef field)
|
||||||
|
{
|
||||||
|
if (field.Field == null)
|
||||||
|
return;
|
||||||
|
Assert.True(ReflectedManager.Process(field.Field));
|
||||||
|
if (field.Field.IsStatic)
|
||||||
|
((Func<ReflectedEventReplacer>)field.Field.GetValue(null)).Invoke();
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,37 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Sandbox.Engine.Multiplayer;
|
|
||||||
using Sandbox.Engine.Networking;
|
|
||||||
using Torch.API;
|
|
||||||
using VRage.Network;
|
|
||||||
|
|
||||||
namespace Torch
|
|
||||||
{
|
|
||||||
public class ChatMessage : IChatMessage
|
|
||||||
{
|
|
||||||
public DateTime Timestamp { get; }
|
|
||||||
public ulong SteamId { get; }
|
|
||||||
public string Name { get; }
|
|
||||||
public string Message { get; }
|
|
||||||
|
|
||||||
public ChatMessage(DateTime timestamp, ulong steamId, string name, string message)
|
|
||||||
{
|
|
||||||
Timestamp = timestamp;
|
|
||||||
SteamId = steamId;
|
|
||||||
Name = name;
|
|
||||||
Message = message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ChatMessage FromChatMsg(ChatMsg msg, DateTime dt = default(DateTime))
|
|
||||||
{
|
|
||||||
return new ChatMessage(
|
|
||||||
dt == default(DateTime) ? DateTime.Now : dt,
|
|
||||||
msg.Author,
|
|
||||||
MyMultiplayer.Static.GetMemberName(msg.Author),
|
|
||||||
msg.Text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -117,7 +117,7 @@ namespace Torch.Commands
|
|||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
context.Respond(e.Message, "Error", MyFontEnum.Red);
|
context.Respond(e.Message, "Error", MyFontEnum.Red);
|
||||||
Log.Error($"Command '{SyntaxHelp}' from '{Plugin.Name ?? "Torch"}' threw an exception. Args: {string.Join(", ", context.Args)}");
|
Log.Error($"Command '{SyntaxHelp}' from '{Plugin?.Name ?? "Torch"}' threw an exception. Args: {string.Join(", ", context.Args)}");
|
||||||
Log.Error(e);
|
Log.Error(e);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
using Torch.API.Plugins;
|
using Torch.API.Plugins;
|
||||||
using VRage.Game;
|
using VRage.Game;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
@@ -47,7 +48,7 @@ namespace Torch.Commands
|
|||||||
Response = message;
|
Response = message;
|
||||||
|
|
||||||
if (Player != null)
|
if (Player != null)
|
||||||
Torch.Multiplayer.SendMessage(message, sender, Player.IdentityId, font);
|
Torch.CurrentSession.Managers.GetManager<IChatManagerServer>()?.SendMessageAsOther(sender, message, font, Player.SteamUserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -9,6 +9,7 @@ using Torch.API;
|
|||||||
using Torch.API.Managers;
|
using Torch.API.Managers;
|
||||||
using Torch.API.Plugins;
|
using Torch.API.Plugins;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
|
using VRage.Game;
|
||||||
using VRage.Game.ModAPI;
|
using VRage.Game.ModAPI;
|
||||||
using VRage.Network;
|
using VRage.Network;
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ namespace Torch.Commands
|
|||||||
public CommandTree Commands { get; set; } = new CommandTree();
|
public CommandTree Commands { get; set; } = new CommandTree();
|
||||||
private Logger _log = LogManager.GetLogger(nameof(CommandManager));
|
private Logger _log = LogManager.GetLogger(nameof(CommandManager));
|
||||||
[Dependency]
|
[Dependency]
|
||||||
private ChatManager _chatManager;
|
private IChatManagerServer _chatManager;
|
||||||
|
|
||||||
public CommandManager(ITorchBase torch, char prefix = '!') : base(torch)
|
public CommandManager(ITorchBase torch, char prefix = '!') : base(torch)
|
||||||
{
|
{
|
||||||
@@ -31,7 +32,7 @@ namespace Torch.Commands
|
|||||||
public override void Attach()
|
public override void Attach()
|
||||||
{
|
{
|
||||||
RegisterCommandModule(typeof(TorchCommands));
|
RegisterCommandModule(typeof(TorchCommands));
|
||||||
_chatManager.MessageRecieved += HandleCommand;
|
_chatManager.MessageProcessing += HandleCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasPermission(ulong steamId, Command command)
|
public bool HasPermission(ulong steamId, Command command)
|
||||||
@@ -93,20 +94,21 @@ namespace Torch.Commands
|
|||||||
return context.Response;
|
return context.Response;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleCommand(ChatMsg msg, ref bool sendToOthers)
|
public void HandleCommand(TorchChatMessage msg, ref bool consumed)
|
||||||
{
|
{
|
||||||
HandleCommand(msg.Text, msg.Author, ref sendToOthers);
|
if (msg.AuthorSteamId.HasValue)
|
||||||
|
HandleCommand(msg.Message, msg.AuthorSteamId.Value, ref consumed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleCommand(string message, ulong steamId, ref bool sendToOthers, bool serverConsole = false)
|
public void HandleCommand(string message, ulong steamId, ref bool consumed, bool serverConsole = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (message.Length < 1 || message[0] != Prefix)
|
if (message.Length < 1 || message[0] != Prefix)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
sendToOthers = false;
|
consumed = true;
|
||||||
|
|
||||||
var player = Torch.Multiplayer.GetPlayerBySteamId(steamId);
|
var player = Torch.CurrentSession.Managers.GetManager<IMultiplayerManagerBase>().GetPlayerBySteamId(steamId);
|
||||||
if (player == null)
|
if (player == null)
|
||||||
{
|
{
|
||||||
_log.Error($"Command {message} invoked by nonexistant player");
|
_log.Error($"Command {message} invoked by nonexistant player");
|
||||||
@@ -123,7 +125,7 @@ namespace Torch.Commands
|
|||||||
if (!HasPermission(steamId, command))
|
if (!HasPermission(steamId, command))
|
||||||
{
|
{
|
||||||
_log.Info($"{player.DisplayName} tried to use command {cmdPath} without permission");
|
_log.Info($"{player.DisplayName} tried to use command {cmdPath} without permission");
|
||||||
Torch.Multiplayer.SendMessage($"You need to be a {command.MinimumPromoteLevel} or higher to use that command.", playerId: player.IdentityId);
|
_chatManager.SendMessageAsOther("Server", $"You need to be a {command.MinimumPromoteLevel} or higher to use that command.", MyFontEnum.Red, steamId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,7 +21,12 @@ namespace Torch.Commands
|
|||||||
[Permission(MyPromoteLevel.None)]
|
[Permission(MyPromoteLevel.None)]
|
||||||
public void Help()
|
public void Help()
|
||||||
{
|
{
|
||||||
var commandManager = ((TorchBase)Context.Torch).Commands;
|
var commandManager = Context.Torch.CurrentSession?.Managers.GetManager<CommandManager>();
|
||||||
|
if (commandManager == null)
|
||||||
|
{
|
||||||
|
Context.Respond("Must have an attached session to list commands");
|
||||||
|
return;
|
||||||
|
}
|
||||||
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
|
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
|
||||||
|
|
||||||
if (node != null)
|
if (node != null)
|
||||||
@@ -51,7 +56,12 @@ namespace Torch.Commands
|
|||||||
[Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")]
|
[Command("longhelp", "Get verbose help. Will send a long message, check the Comms tab.")]
|
||||||
public void LongHelp()
|
public void LongHelp()
|
||||||
{
|
{
|
||||||
var commandManager = Context.Torch.Managers.GetManager<CommandManager>();
|
var commandManager = Context.Torch.CurrentSession?.Managers.GetManager<CommandManager>();
|
||||||
|
if (commandManager == null)
|
||||||
|
{
|
||||||
|
Context.Respond("Must have an attached session to list commands");
|
||||||
|
return;
|
||||||
|
}
|
||||||
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
|
commandManager.Commands.GetNode(Context.Args, out CommandTree.CommandNode node);
|
||||||
|
|
||||||
if (node != null)
|
if (node != null)
|
||||||
@@ -96,7 +106,7 @@ namespace Torch.Commands
|
|||||||
[Permission(MyPromoteLevel.None)]
|
[Permission(MyPromoteLevel.None)]
|
||||||
public void Plugins()
|
public void Plugins()
|
||||||
{
|
{
|
||||||
var plugins = Context.Torch.Plugins.Select(p => p.Name);
|
var plugins = Context.Torch.Managers.GetManager<PluginManager>()?.Plugins.Select(p => p.Value.Name) ?? Enumerable.Empty<string>();
|
||||||
Context.Respond($"Loaded plugins: {string.Join(", ", plugins)}");
|
Context.Respond($"Loaded plugins: {string.Join(", ", plugins)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,13 +138,13 @@ namespace Torch.Commands
|
|||||||
{
|
{
|
||||||
if (i >= 60 && i % 60 == 0)
|
if (i >= 60 && i % 60 == 0)
|
||||||
{
|
{
|
||||||
Context.Torch.Multiplayer.SendMessage($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
|
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf($"Restarting server in {i / 60} minute{Pluralize(i / 60)}.");
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
else if (i > 0)
|
else if (i > 0)
|
||||||
{
|
{
|
||||||
if (i < 11)
|
if (i < 11)
|
||||||
Context.Torch.Multiplayer.SendMessage($"Restarting server in {i} second{Pluralize(i)}.");
|
Context.Torch.CurrentSession.Managers.GetManager<IChatManagerClient>().SendMessageAsSelf($"Restarting server in {i} second{Pluralize(i)}.");
|
||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -153,7 +163,7 @@ namespace Torch.Commands
|
|||||||
{
|
{
|
||||||
return num == 1 ? "" : "s";
|
return num == 1 ? "" : "s";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a save of the game.
|
/// Initializes a save of the game.
|
||||||
/// Caller id defaults to 0 in the case of triggering the chat command from server.
|
/// Caller id defaults to 0 in the case of triggering the chat command from server.
|
||||||
|
@@ -1,104 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using NLog;
|
|
||||||
using Sandbox.Engine.Multiplayer;
|
|
||||||
using Torch.API;
|
|
||||||
using Torch.API.Managers;
|
|
||||||
using VRage;
|
|
||||||
using VRage.Library.Collections;
|
|
||||||
using VRage.Network;
|
|
||||||
using VRage.Serialization;
|
|
||||||
using VRage.Utils;
|
|
||||||
|
|
||||||
namespace Torch.Managers
|
|
||||||
{
|
|
||||||
[Manager]
|
|
||||||
public class ChatManager : Manager
|
|
||||||
{
|
|
||||||
private static Logger _log = LogManager.GetLogger(nameof(ChatManager));
|
|
||||||
|
|
||||||
public delegate void MessageRecievedDel(ChatMsg msg, ref bool sendToOthers);
|
|
||||||
|
|
||||||
public event MessageRecievedDel MessageRecieved;
|
|
||||||
|
|
||||||
internal void RaiseMessageRecieved(ChatMsg msg, ref bool sendToOthers) =>
|
|
||||||
MessageRecieved?.Invoke(msg, ref sendToOthers);
|
|
||||||
|
|
||||||
[Dependency]
|
|
||||||
private INetworkManager _networkManager;
|
|
||||||
|
|
||||||
public ChatManager(ITorchBase torchInstance) : base(torchInstance)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Attach()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_networkManager.RegisterNetworkHandler(new ChatIntercept(this));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_log.Error("Failed to initialize network intercept, command hiding will not work! Falling back to another method.");
|
|
||||||
MyMultiplayer.Static.ChatMessageReceived += Static_ChatMessageReceived;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Static_ChatMessageReceived(ulong arg1, string arg2)
|
|
||||||
{
|
|
||||||
var msg = new ChatMsg {Author = arg1, Text = arg2};
|
|
||||||
var sendToOthers = true;
|
|
||||||
|
|
||||||
RaiseMessageRecieved(msg, ref sendToOthers);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class ChatIntercept : NetworkHandlerBase, INetworkHandler
|
|
||||||
{
|
|
||||||
private ChatManager _chatManager;
|
|
||||||
private bool? _unitTestResult;
|
|
||||||
|
|
||||||
public ChatIntercept(ChatManager chatManager)
|
|
||||||
{
|
|
||||||
_chatManager = chatManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanHandle(CallSite site)
|
|
||||||
{
|
|
||||||
if (site.MethodInfo.Name != "OnChatMessageRecieved")
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (_unitTestResult.HasValue)
|
|
||||||
return _unitTestResult.Value;
|
|
||||||
|
|
||||||
var parameters = site.MethodInfo.GetParameters();
|
|
||||||
if (parameters.Length != 1)
|
|
||||||
{
|
|
||||||
_unitTestResult = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameters[0].ParameterType != typeof(ChatMsg))
|
|
||||||
_unitTestResult = false;
|
|
||||||
|
|
||||||
_unitTestResult = true;
|
|
||||||
|
|
||||||
return _unitTestResult.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet)
|
|
||||||
{
|
|
||||||
var msg = new ChatMsg();
|
|
||||||
Serialize(site.MethodInfo, stream, ref msg);
|
|
||||||
|
|
||||||
bool sendToOthers = true;
|
|
||||||
_chatManager.RaiseMessageRecieved(msg, ref sendToOthers);
|
|
||||||
|
|
||||||
return !sendToOthers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
188
Torch/Managers/ChatManager/ChatManagerClient.cs
Normal file
188
Torch/Managers/ChatManager/ChatManagerClient.cs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Sandbox.Game.Entities.Character;
|
||||||
|
using Sandbox.Game.Gui;
|
||||||
|
using Sandbox.Game.Multiplayer;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage.Game;
|
||||||
|
|
||||||
|
namespace Torch.Managers.ChatManager
|
||||||
|
{
|
||||||
|
public class ChatManagerClient : Manager, IChatManagerClient
|
||||||
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ChatManagerClient(ITorchBase torchInstance) : base(torchInstance) { }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event MessageRecievedDel MessageRecieved;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event MessageSendingDel MessageSending;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SendMessageAsSelf(string message)
|
||||||
|
{
|
||||||
|
if (MyMultiplayer.Static != null)
|
||||||
|
{
|
||||||
|
if (Sandbox.Engine.Platform.Game.IsDedicated)
|
||||||
|
{
|
||||||
|
var scripted = new ScriptedChatMsg()
|
||||||
|
{
|
||||||
|
Author = "Server",
|
||||||
|
Font = MyFontEnum.Red,
|
||||||
|
Text = message,
|
||||||
|
Target = 0
|
||||||
|
};
|
||||||
|
MyMultiplayerBase.SendScriptedChatMessage(ref scripted);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
MyMultiplayer.Static.SendChatMessage(message);
|
||||||
|
}
|
||||||
|
else if (HasHud)
|
||||||
|
MyHud.Chat.ShowMessage(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void DisplayMessageOnSelf(string author, string message, string font)
|
||||||
|
{
|
||||||
|
if (HasHud)
|
||||||
|
MyHud.Chat?.ShowMessage(author, message, font);
|
||||||
|
MySession.Static.GlobalChatHistory.GlobalChatHistory.Chat.Enqueue(new MyGlobalChatItem()
|
||||||
|
{
|
||||||
|
Author = author,
|
||||||
|
AuthorFont = font,
|
||||||
|
Text = message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
base.Attach();
|
||||||
|
MyAPIUtilities.Static.MessageEntered += OnMessageEntered;
|
||||||
|
if (MyMultiplayer.Static != null)
|
||||||
|
{
|
||||||
|
_chatMessageRecievedReplacer = _chatMessageReceivedFactory.Invoke();
|
||||||
|
_scriptedChatMessageRecievedReplacer = _scriptedChatMessageReceivedFactory.Invoke();
|
||||||
|
_chatMessageRecievedReplacer.Replace(new Action<ulong, string>(Multiplayer_ChatMessageReceived),
|
||||||
|
MyMultiplayer.Static);
|
||||||
|
_scriptedChatMessageRecievedReplacer.Replace(
|
||||||
|
new Action<string, string, string>(Multiplayer_ScriptedChatMessageReceived), MyMultiplayer.Static);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MyAPIUtilities.Static.MessageEntered += OfflineMessageReciever;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
MyAPIUtilities.Static.MessageEntered -= OnMessageEntered;
|
||||||
|
if (_chatMessageRecievedReplacer != null && _chatMessageRecievedReplacer.Replaced && HasHud)
|
||||||
|
_chatMessageRecievedReplacer.Restore(MyHud.Chat);
|
||||||
|
if (_scriptedChatMessageRecievedReplacer != null && _scriptedChatMessageRecievedReplacer.Replaced && HasHud)
|
||||||
|
_scriptedChatMessageRecievedReplacer.Restore(MyHud.Chat);
|
||||||
|
MyAPIUtilities.Static.MessageEntered -= OfflineMessageReciever;
|
||||||
|
base.Detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Callback used to process offline messages.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
/// <returns>true if the message was consumed</returns>
|
||||||
|
protected virtual bool OfflineMessageProcessor(TorchChatMessage msg)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OfflineMessageReciever(string messageText, ref bool sendToOthers)
|
||||||
|
{
|
||||||
|
if (!sendToOthers)
|
||||||
|
return;
|
||||||
|
var torchMsg = new TorchChatMessage()
|
||||||
|
{
|
||||||
|
AuthorSteamId = Sync.MyId,
|
||||||
|
Author = MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player",
|
||||||
|
Message = messageText
|
||||||
|
};
|
||||||
|
var consumed = false;
|
||||||
|
MessageRecieved?.Invoke(torchMsg, ref consumed);
|
||||||
|
if (!consumed)
|
||||||
|
consumed = OfflineMessageProcessor(torchMsg);
|
||||||
|
sendToOthers = !consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMessageEntered(string messageText, ref bool sendToOthers)
|
||||||
|
{
|
||||||
|
if (!sendToOthers)
|
||||||
|
return;
|
||||||
|
var consumed = false;
|
||||||
|
MessageSending?.Invoke(messageText, ref consumed);
|
||||||
|
sendToOthers = !consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void Multiplayer_ChatMessageReceived(ulong steamUserId, string message)
|
||||||
|
{
|
||||||
|
var torchMsg = new TorchChatMessage()
|
||||||
|
{
|
||||||
|
AuthorSteamId = steamUserId,
|
||||||
|
Author = Torch.CurrentSession.Managers.GetManager<IMultiplayerManagerBase>()
|
||||||
|
?.GetSteamUsername(steamUserId),
|
||||||
|
Font = (steamUserId == MyGameService.UserId) ? "DarkBlue" : "Blue",
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
var consumed = false;
|
||||||
|
MessageRecieved?.Invoke(torchMsg, ref consumed);
|
||||||
|
if (!consumed && HasHud)
|
||||||
|
_hudChatMessageReceived.Invoke(MyHud.Chat, steamUserId, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Multiplayer_ScriptedChatMessageReceived(string message, string author, string font)
|
||||||
|
{
|
||||||
|
var torchMsg = new TorchChatMessage()
|
||||||
|
{
|
||||||
|
AuthorSteamId = null,
|
||||||
|
Author = author,
|
||||||
|
Font = font,
|
||||||
|
Message = message
|
||||||
|
};
|
||||||
|
var consumed = false;
|
||||||
|
MessageRecieved?.Invoke(torchMsg, ref consumed);
|
||||||
|
if (!consumed && HasHud)
|
||||||
|
_hudChatScriptedMessageReceived.Invoke(MyHud.Chat, author, message, font);
|
||||||
|
}
|
||||||
|
|
||||||
|
private const string _hudChatMessageReceivedName = "Multiplayer_ChatMessageReceived";
|
||||||
|
private const string _hudChatScriptedMessageReceivedName = "multiplayer_ScriptedChatMessageReceived";
|
||||||
|
|
||||||
|
protected static bool HasHud => !Sandbox.Engine.Platform.Game.IsDedicated;
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = _hudChatMessageReceivedName)]
|
||||||
|
private static Action<MyHudChat, ulong, string> _hudChatMessageReceived;
|
||||||
|
[ReflectedMethod(Name = _hudChatScriptedMessageReceivedName)]
|
||||||
|
private static Action<MyHudChat, string, string, string> _hudChatScriptedMessageReceived;
|
||||||
|
|
||||||
|
[ReflectedEventReplace(typeof(MyMultiplayerBase), nameof(MyMultiplayerBase.ChatMessageReceived), typeof(MyHudChat), _hudChatMessageReceivedName)]
|
||||||
|
private static Func<ReflectedEventReplacer> _chatMessageReceivedFactory;
|
||||||
|
[ReflectedEventReplace(typeof(MyMultiplayerBase), nameof(MyMultiplayerBase.ScriptedChatMessageReceived), typeof(MyHudChat), _hudChatScriptedMessageReceivedName)]
|
||||||
|
private static Func<ReflectedEventReplacer> _scriptedChatMessageReceivedFactory;
|
||||||
|
|
||||||
|
private ReflectedEventReplacer _chatMessageRecievedReplacer;
|
||||||
|
private ReflectedEventReplacer _scriptedChatMessageRecievedReplacer;
|
||||||
|
}
|
||||||
|
}
|
206
Torch/Managers/ChatManager/ChatManagerServer.cs
Normal file
206
Torch/Managers/ChatManager/ChatManagerServer.cs
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Sandbox.Game.Gui;
|
||||||
|
using Sandbox.Game.Multiplayer;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage;
|
||||||
|
using VRage.Library.Collections;
|
||||||
|
using VRage.Network;
|
||||||
|
|
||||||
|
namespace Torch.Managers.ChatManager
|
||||||
|
{
|
||||||
|
public class ChatManagerServer : ChatManagerClient, IChatManagerServer
|
||||||
|
{
|
||||||
|
[Dependency(Optional = true)]
|
||||||
|
private INetworkManager _networkManager;
|
||||||
|
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
private readonly ChatIntercept _chatIntercept;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ChatManagerServer(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
_chatIntercept = new ChatIntercept(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event MessageProcessingDel MessageProcessing;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SendMessageAsOther(ulong authorId, string message, ulong targetSteamId = 0)
|
||||||
|
{
|
||||||
|
if (MyMultiplayer.Static == null)
|
||||||
|
{
|
||||||
|
if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud)
|
||||||
|
MyHud.Chat?.ShowMessage(authorId == MyGameService.UserId ?
|
||||||
|
(MySession.Static.LocalHumanPlayer?.DisplayName ?? "Player") : $"user_{authorId}", message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MyMultiplayer.Static is MyDedicatedServerBase dedicated)
|
||||||
|
{
|
||||||
|
var msg = new ChatMsg() { Author = authorId, Text = message };
|
||||||
|
_dedicatedServerBaseSendChatMessage.Invoke(ref msg);
|
||||||
|
_dedicatedServerBaseOnChatMessage.Invoke(dedicated, new object[] { msg });
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
private delegate void MultiplayerBaseSendChatMessageDel(ref ChatMsg arg);
|
||||||
|
[ReflectedStaticMethod(Name = "SendChatMessage", Type = typeof(MyMultiplayerBase))]
|
||||||
|
private static MultiplayerBaseSendChatMessageDel _dedicatedServerBaseSendChatMessage;
|
||||||
|
|
||||||
|
// [ReflectedMethod] doesn't play well with instance methods and refs.
|
||||||
|
[ReflectedMethodInfo(typeof(MyDedicatedServerBase), "OnChatMessage")]
|
||||||
|
private static MethodInfo _dedicatedServerBaseOnChatMessage;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SendMessageAsOther(string author, string message, string font, ulong targetSteamId = 0)
|
||||||
|
{
|
||||||
|
if (MyMultiplayer.Static == null)
|
||||||
|
{
|
||||||
|
if ((targetSteamId == MyGameService.UserId || targetSteamId == 0) && HasHud)
|
||||||
|
MyHud.Chat?.ShowMessage(author, message, font);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var scripted = new ScriptedChatMsg()
|
||||||
|
{
|
||||||
|
Author = author,
|
||||||
|
Text = message,
|
||||||
|
Font = font,
|
||||||
|
Target = Sync.Players.TryGetIdentityId(targetSteamId)
|
||||||
|
};
|
||||||
|
MyMultiplayerBase.SendScriptedChatMessage(ref scripted);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
base.Attach();
|
||||||
|
if (_networkManager != null)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_networkManager.RegisterNetworkHandler(_chatIntercept);
|
||||||
|
_log.Debug("Initialized network intercept for chat messages");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Discard exception and use second method
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MyMultiplayer.Static != null)
|
||||||
|
{
|
||||||
|
MyMultiplayer.Static.ChatMessageReceived += MpStaticChatMessageReceived;
|
||||||
|
_log.Warn(
|
||||||
|
"Failed to initialize network intercept, we can't discard chat messages! Falling back to another method.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_log.Debug("Using offline message processor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override bool OfflineMessageProcessor(TorchChatMessage msg)
|
||||||
|
{
|
||||||
|
if (MyMultiplayer.Static != null)
|
||||||
|
return false;
|
||||||
|
var consumed = false;
|
||||||
|
MessageProcessing?.Invoke(msg, ref consumed);
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MpStaticChatMessageReceived(ulong a, string b)
|
||||||
|
{
|
||||||
|
var tmp = false;
|
||||||
|
RaiseMessageRecieved(new ChatMsg()
|
||||||
|
{
|
||||||
|
Author = a,
|
||||||
|
Text = b
|
||||||
|
}, ref tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
if (MyMultiplayer.Static != null)
|
||||||
|
MyMultiplayer.Static.ChatMessageReceived -= MpStaticChatMessageReceived;
|
||||||
|
_networkManager?.UnregisterNetworkHandler(_chatIntercept);
|
||||||
|
base.Detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RaiseMessageRecieved(ChatMsg message, ref bool consumed)
|
||||||
|
{
|
||||||
|
var torchMsg = new TorchChatMessage()
|
||||||
|
{
|
||||||
|
AuthorSteamId = message.Author,
|
||||||
|
Author = MyMultiplayer.Static?.GetMemberName(message.Author) ?? $"user_{message.Author}",
|
||||||
|
Message = message.Text
|
||||||
|
};
|
||||||
|
MessageProcessing?.Invoke(torchMsg, ref consumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ChatIntercept : NetworkHandlerBase, INetworkHandler
|
||||||
|
{
|
||||||
|
private readonly ChatManagerServer _chatManager;
|
||||||
|
private bool? _unitTestResult;
|
||||||
|
|
||||||
|
public ChatIntercept(ChatManagerServer chatManager)
|
||||||
|
{
|
||||||
|
_chatManager = chatManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool CanHandle(CallSite site)
|
||||||
|
{
|
||||||
|
if (site.MethodInfo.Name != "OnChatMessageRecieved")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_unitTestResult.HasValue)
|
||||||
|
return _unitTestResult.Value;
|
||||||
|
|
||||||
|
ParameterInfo[] parameters = site.MethodInfo.GetParameters();
|
||||||
|
if (parameters.Length != 1)
|
||||||
|
{
|
||||||
|
_unitTestResult = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameters[0].ParameterType != typeof(ChatMsg))
|
||||||
|
_unitTestResult = false;
|
||||||
|
|
||||||
|
_unitTestResult = true;
|
||||||
|
|
||||||
|
return _unitTestResult.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override bool Handle(ulong remoteUserId, CallSite site, BitStream stream, object obj, MyPacket packet)
|
||||||
|
{
|
||||||
|
var msg = new ChatMsg();
|
||||||
|
Serialize(site.MethodInfo, stream, ref msg);
|
||||||
|
|
||||||
|
var consumed = false;
|
||||||
|
_chatManager.RaiseMessageRecieved(msg, ref consumed);
|
||||||
|
|
||||||
|
return consumed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
164
Torch/Managers/KeenLogManager.cs
Normal file
164
Torch/Managers/KeenLogManager.cs
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.Managers.PatchManager;
|
||||||
|
using Torch.Utils;
|
||||||
|
using VRage.Utils;
|
||||||
|
|
||||||
|
namespace Torch.Managers
|
||||||
|
{
|
||||||
|
public class KeenLogManager : Manager
|
||||||
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetLogger("Keen");
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[Dependency]
|
||||||
|
private PatchManager.PatchManager _patchManager;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.Log), Parameters = new[] { typeof(MyLogSeverity), typeof(StringBuilder) })]
|
||||||
|
private static MethodInfo _logStringBuilder;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.Log), Parameters = new[] { typeof(MyLogSeverity), typeof(string), typeof(object[]) })]
|
||||||
|
private static MethodInfo _logFormatted;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLine), Parameters = new[] { typeof(string) })]
|
||||||
|
private static MethodInfo _logWriteLine;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.AppendToClosedLog), Parameters = new[] { typeof(string) })]
|
||||||
|
private static MethodInfo _logAppendToClosedLog;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLine), Parameters = new[] { typeof(string), typeof(LoggingOptions) })]
|
||||||
|
private static MethodInfo _logWriteLineOptions;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLine), Parameters = new[] { typeof(Exception) })]
|
||||||
|
private static MethodInfo _logWriteLineException;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.AppendToClosedLog), Parameters = new[] { typeof(Exception) })]
|
||||||
|
private static MethodInfo _logAppendToClosedLogException;
|
||||||
|
|
||||||
|
[ReflectedMethodInfo(typeof(MyLog), nameof(MyLog.WriteLineAndConsole), Parameters = new[] { typeof(string) })]
|
||||||
|
private static MethodInfo _logWriteLineAndConsole;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
private PatchContext _context;
|
||||||
|
|
||||||
|
public KeenLogManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
_context = _patchManager.AcquireContext();
|
||||||
|
|
||||||
|
_context.GetPattern(_logStringBuilder).Prefixes.Add(Method(nameof(PrefixLogStringBuilder)));
|
||||||
|
_context.GetPattern(_logFormatted).Prefixes.Add(Method(nameof(PrefixLogFormatted)));
|
||||||
|
|
||||||
|
_context.GetPattern(_logWriteLine).Prefixes.Add(Method(nameof(PrefixWriteLine)));
|
||||||
|
_context.GetPattern(_logAppendToClosedLog).Prefixes.Add(Method(nameof(PrefixAppendToClosedLog)));
|
||||||
|
_context.GetPattern(_logWriteLineAndConsole).Prefixes.Add(Method(nameof(PrefixWriteLineConsole)));
|
||||||
|
|
||||||
|
_context.GetPattern(_logWriteLineException).Prefixes.Add(Method(nameof(PrefixWriteLineException)));
|
||||||
|
_context.GetPattern(_logAppendToClosedLogException).Prefixes.Add(Method(nameof(PrefixAppendToClosedLogException)));
|
||||||
|
|
||||||
|
_context.GetPattern(_logWriteLineOptions).Prefixes.Add(Method(nameof(PrefixWriteLineOptions)));
|
||||||
|
|
||||||
|
_patchManager.Commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
_patchManager.FreeContext(_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodInfo Method(string name)
|
||||||
|
{
|
||||||
|
return typeof(KeenLogManager).GetMethod(name, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = "GetThreadId")]
|
||||||
|
private static Func<MyLog, int> _getThreadId;
|
||||||
|
|
||||||
|
[ReflectedMethod(Name = "GetIdentByThread")]
|
||||||
|
private static Func<MyLog, int, int> _getIndentByThread;
|
||||||
|
|
||||||
|
private static readonly ThreadLocal<StringBuilder> _tmpStringBuilder = new ThreadLocal<StringBuilder>(() => new StringBuilder(32));
|
||||||
|
|
||||||
|
private static StringBuilder PrepareLog(MyLog log)
|
||||||
|
{
|
||||||
|
return _tmpStringBuilder.Value.Clear().Append(' ', _getIndentByThread(log, _getThreadId(log)) * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PrefixWriteLine(MyLog __instance, string msg)
|
||||||
|
{
|
||||||
|
_log.Trace(PrepareLog(__instance).Append(msg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PrefixWriteLineConsole(MyLog __instance, string msg)
|
||||||
|
{
|
||||||
|
_log.Info(PrepareLog(__instance).Append(msg));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PrefixAppendToClosedLog(MyLog __instance, string text)
|
||||||
|
{
|
||||||
|
_log.Info(PrepareLog(__instance).Append(text));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private static bool PrefixWriteLineOptions(MyLog __instance, string message, LoggingOptions option)
|
||||||
|
{
|
||||||
|
if (__instance.LogFlag(option))
|
||||||
|
_log.Info(PrepareLog(__instance).Append(message));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PrefixAppendToClosedLogException(Exception e)
|
||||||
|
{
|
||||||
|
_log.Info(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PrefixWriteLineException(Exception ex)
|
||||||
|
{
|
||||||
|
_log.Info(ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PrefixLogFormatted(MyLog __instance, MyLogSeverity severity, string format, object[] args)
|
||||||
|
{
|
||||||
|
_log.Log(LogLevelFor(severity), PrepareLog(__instance).AppendFormat(format, args));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PrefixLogStringBuilder(MyLog __instance, MyLogSeverity severity, StringBuilder builder)
|
||||||
|
{
|
||||||
|
_log.Log(LogLevelFor(severity), PrepareLog(__instance).Append(builder));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LogLevel LogLevelFor(MyLogSeverity severity)
|
||||||
|
{
|
||||||
|
switch (severity)
|
||||||
|
{
|
||||||
|
case MyLogSeverity.Debug:
|
||||||
|
return LogLevel.Debug;
|
||||||
|
case MyLogSeverity.Info:
|
||||||
|
return LogLevel.Info;
|
||||||
|
case MyLogSeverity.Warning:
|
||||||
|
return LogLevel.Warn;
|
||||||
|
case MyLogSeverity.Error:
|
||||||
|
return LogLevel.Error;
|
||||||
|
case MyLogSeverity.Critical:
|
||||||
|
return LogLevel.Fatal;
|
||||||
|
default:
|
||||||
|
return LogLevel.Info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,338 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Data;
|
|
||||||
using System.Windows.Threading;
|
|
||||||
using NLog;
|
|
||||||
using Torch;
|
|
||||||
using Sandbox;
|
|
||||||
using Sandbox.Engine.Multiplayer;
|
|
||||||
using Sandbox.Engine.Networking;
|
|
||||||
using Sandbox.Game.Entities.Character;
|
|
||||||
using Sandbox.Game.Multiplayer;
|
|
||||||
using Sandbox.Game.World;
|
|
||||||
using Sandbox.ModAPI;
|
|
||||||
using SteamSDK;
|
|
||||||
using Torch.API;
|
|
||||||
using Torch.API.Managers;
|
|
||||||
using Torch.Collections;
|
|
||||||
using Torch.Commands;
|
|
||||||
using Torch.Utils;
|
|
||||||
using Torch.ViewModels;
|
|
||||||
using VRage.Game;
|
|
||||||
using VRage.Game.ModAPI;
|
|
||||||
using VRage.GameServices;
|
|
||||||
using VRage.Library.Collections;
|
|
||||||
using VRage.Network;
|
|
||||||
using VRage.Steam;
|
|
||||||
using VRage.Utils;
|
|
||||||
|
|
||||||
namespace Torch.Managers
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public class MultiplayerManager : Manager, IMultiplayerManager
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event Action<IPlayer> PlayerJoined;
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event Action<IPlayer> PlayerLeft;
|
|
||||||
/// <inheritdoc />
|
|
||||||
public event MessageReceivedDel MessageReceived;
|
|
||||||
|
|
||||||
public IList<IChatMessage> ChatHistory { get; } = new ObservableList<IChatMessage>();
|
|
||||||
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
|
|
||||||
public IMyPlayer LocalPlayer => MySession.Static.LocalHumanPlayer;
|
|
||||||
private static readonly Logger Log = LogManager.GetLogger(nameof(MultiplayerManager));
|
|
||||||
private static readonly Logger ChatLog = LogManager.GetLogger("Chat");
|
|
||||||
|
|
||||||
[ReflectedGetter(Name = "m_players")]
|
|
||||||
private static Func<MyPlayerCollection, Dictionary<MyPlayer.PlayerId, MyPlayer>> _onlinePlayers;
|
|
||||||
|
|
||||||
[Dependency]
|
|
||||||
private ChatManager _chatManager;
|
|
||||||
[Dependency]
|
|
||||||
private CommandManager _commandManager;
|
|
||||||
[Dependency]
|
|
||||||
private NetworkManager _networkManager;
|
|
||||||
|
|
||||||
internal MultiplayerManager(ITorchBase torch) : base(torch)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override void Attach()
|
|
||||||
{
|
|
||||||
Torch.SessionLoaded += OnSessionLoaded;
|
|
||||||
_chatManager.MessageRecieved += Instance_MessageRecieved;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Instance_MessageRecieved(ChatMsg msg, ref bool sendToOthers)
|
|
||||||
{
|
|
||||||
var message = ChatMessage.FromChatMsg(msg);
|
|
||||||
ChatHistory.Add(message);
|
|
||||||
ChatLog.Info($"{message.Name}: {message.Message}");
|
|
||||||
MessageReceived?.Invoke(message, ref sendToOthers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void BanPlayer(ulong steamId, bool banned = true)
|
|
||||||
{
|
|
||||||
Torch.Invoke(() =>
|
|
||||||
{
|
|
||||||
MyMultiplayer.Static.BanClient(steamId, banned);
|
|
||||||
if (_gameOwnerIds.ContainsKey(steamId))
|
|
||||||
MyMultiplayer.Static.BanClient(_gameOwnerIds[steamId], banned);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IMyPlayer GetPlayerByName(string name)
|
|
||||||
{
|
|
||||||
return _onlinePlayers.Invoke(MySession.Static.Players).FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IMyPlayer GetPlayerBySteamId(ulong steamId)
|
|
||||||
{
|
|
||||||
_onlinePlayers.Invoke(MySession.Static.Players).TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p);
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ulong GetSteamId(long identityId)
|
|
||||||
{
|
|
||||||
foreach (var kv in _onlinePlayers.Invoke(MySession.Static.Players))
|
|
||||||
{
|
|
||||||
if (kv.Value.Identity.IdentityId == identityId)
|
|
||||||
return kv.Key.SteamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string GetSteamUsername(ulong steamId)
|
|
||||||
{
|
|
||||||
return MyMultiplayer.Static.GetMemberName(steamId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void SendMessage(string message, string author = "Server", long playerId = 0, string font = MyFontEnum.Red)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(message))
|
|
||||||
return;
|
|
||||||
|
|
||||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, message));
|
|
||||||
if (_commandManager.IsCommand(message))
|
|
||||||
{
|
|
||||||
var response = _commandManager.HandleCommandFromServer(message);
|
|
||||||
ChatHistory.Add(new ChatMessage(DateTime.Now, 0, author, response));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var msg = new ScriptedChatMsg { Author = author, Font = font, Target = playerId, Text = message };
|
|
||||||
MyMultiplayerBase.SendScriptedChatMessage(ref msg);
|
|
||||||
var character = MySession.Static.Players.TryGetIdentity(playerId)?.Character;
|
|
||||||
var steamId = GetSteamId(playerId);
|
|
||||||
if (character == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var addToGlobalHistoryMethod = typeof(MyCharacter).GetMethod("OnGlobalMessageSuccess", BindingFlags.Instance | BindingFlags.NonPublic);
|
|
||||||
_networkManager.RaiseEvent(addToGlobalHistoryMethod, character, steamId, steamId, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSessionLoaded()
|
|
||||||
{
|
|
||||||
Log.Info("Initializing Steam auth");
|
|
||||||
MyMultiplayer.Static.ClientKicked += OnClientKicked;
|
|
||||||
MyMultiplayer.Static.ClientLeft += OnClientLeft;
|
|
||||||
|
|
||||||
//TODO: Move these with the methods?
|
|
||||||
if (!RemoveHandlers())
|
|
||||||
{
|
|
||||||
Log.Error("Steam auth failed to initialize");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MyGameService.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse;
|
|
||||||
MyGameService.GameServer.UserGroupStatusResponse += UserGroupStatusResponse;
|
|
||||||
Log.Info("Steam auth initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnClientKicked(ulong steamId)
|
|
||||||
{
|
|
||||||
OnClientLeft(steamId, MyChatMemberStateChangeEnum.Kicked);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnClientLeft(ulong steamId, MyChatMemberStateChangeEnum stateChange)
|
|
||||||
{
|
|
||||||
Players.TryGetValue(steamId, out PlayerViewModel vm);
|
|
||||||
if (vm == null)
|
|
||||||
vm = new PlayerViewModel(steamId);
|
|
||||||
Log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}.");
|
|
||||||
PlayerLeft?.Invoke(vm);
|
|
||||||
Players.Remove(steamId);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Split the following into a new file?
|
|
||||||
//These methods override some Keen code to allow us full control over client authentication.
|
|
||||||
//This lets us have a server set to private (admins only) or friends (friends of all listed admins)
|
|
||||||
[ReflectedGetter(Name = "m_members")]
|
|
||||||
private static Func<MyDedicatedServerBase, List<ulong>> _members;
|
|
||||||
[ReflectedGetter(Name = "m_waitingForGroup")]
|
|
||||||
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
|
|
||||||
[ReflectedGetter(Name = "m_kickedClients")]
|
|
||||||
private static Func<MyMultiplayerBase, Dictionary<ulong, int>> _kickedClients;
|
|
||||||
//private HashSet<ulong> _waitingForFriends;
|
|
||||||
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
|
|
||||||
//private IMultiplayer _multiplayerImplementation;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes Keen's hooks into some Steam events so we have full control over client authentication
|
|
||||||
/// </summary>
|
|
||||||
private static bool RemoveHandlers()
|
|
||||||
{
|
|
||||||
MethodInfo methodValidateAuthTicket = typeof(MyDedicatedServerBase).GetMethod("GameServer_ValidateAuthTicketResponse",
|
|
||||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
if (methodValidateAuthTicket == null)
|
|
||||||
{
|
|
||||||
Log.Error("Unable to find the GameServer_ValidateAuthTicketResponse method to unhook");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var eventValidateAuthTicket = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.ValidateAuthTicketResponse))
|
|
||||||
.FirstOrDefault(x => x.Method == methodValidateAuthTicket) as Action<ulong, JoinResult, ulong>;
|
|
||||||
if (eventValidateAuthTicket == null)
|
|
||||||
{
|
|
||||||
Log.Error(
|
|
||||||
"Unable to unhook the GameServer_ValidateAuthTicketResponse method from GameServer.ValidateAuthTicketResponse");
|
|
||||||
Log.Debug(" Want to unhook {0}", methodValidateAuthTicket);
|
|
||||||
Log.Debug(" Registered handlers: ");
|
|
||||||
foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer,
|
|
||||||
nameof(MyGameService.GameServer.ValidateAuthTicketResponse)))
|
|
||||||
Log.Debug(" - " + method.Method);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
MethodInfo methodUserGroupStatus = typeof(MyDedicatedServerBase).GetMethod("GameServer_UserGroupStatus",
|
|
||||||
BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
if (methodUserGroupStatus == null)
|
|
||||||
{
|
|
||||||
Log.Error("Unable to find the GameServer_UserGroupStatus method to unhook");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var eventUserGroupStatus = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse))
|
|
||||||
.FirstOrDefault(x => x.Method == methodUserGroupStatus)
|
|
||||||
as Action<ulong, ulong, bool, bool>;
|
|
||||||
if (eventUserGroupStatus == null)
|
|
||||||
{
|
|
||||||
Log.Error("Unable to unhook the GameServer_UserGroupStatus method from GameServer.UserGroupStatus");
|
|
||||||
Log.Debug(" Want to unhook {0}", methodUserGroupStatus);
|
|
||||||
Log.Debug(" Registered handlers: ");
|
|
||||||
foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse)))
|
|
||||||
Log.Debug(" - " + method.Method);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
MyGameService.GameServer.ValidateAuthTicketResponse -=
|
|
||||||
eventValidateAuthTicket;
|
|
||||||
MyGameService.GameServer.UserGroupStatusResponse -=
|
|
||||||
eventUserGroupStatus;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Largely copied from SE
|
|
||||||
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner)
|
|
||||||
{
|
|
||||||
Log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
|
|
||||||
if (IsClientBanned.Invoke(MyMultiplayer.Static, steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner))
|
|
||||||
{
|
|
||||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins);
|
|
||||||
RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
|
||||||
}
|
|
||||||
else if (IsClientKicked.Invoke(MyMultiplayer.Static, steamOwner))
|
|
||||||
{
|
|
||||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently);
|
|
||||||
RaiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
|
|
||||||
}
|
|
||||||
if (response != JoinResult.OK)
|
|
||||||
{
|
|
||||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit)
|
|
||||||
{
|
|
||||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
|
|
||||||
MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) ||
|
|
||||||
MySandboxGame.ConfigDedicated.Administrators.Contains(ConvertSteamIDFrom64(steamID)))
|
|
||||||
{
|
|
||||||
this.UserAccepted(steamID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (GetServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
|
|
||||||
{
|
|
||||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
|
||||||
{
|
|
||||||
_waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
|
|
||||||
{
|
|
||||||
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId))
|
|
||||||
{
|
|
||||||
if (member || officer)
|
|
||||||
UserAccepted(userId);
|
|
||||||
else
|
|
||||||
UserRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UserAccepted(ulong steamId)
|
|
||||||
{
|
|
||||||
UserAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
|
|
||||||
|
|
||||||
var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected };
|
|
||||||
Log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
|
||||||
Players.Add(steamId, vm);
|
|
||||||
PlayerJoined?.Invoke(vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase))]
|
|
||||||
private static Func<ulong, string> ConvertSteamIDFrom64;
|
|
||||||
|
|
||||||
[ReflectedStaticMethod(Type = typeof(MyGameService))]
|
|
||||||
private static Func<ulong, MyGameServiceAccountType> GetServerAccountType;
|
|
||||||
|
|
||||||
[ReflectedMethod(Name = "UserAccepted")]
|
|
||||||
private static Action<MyDedicatedServerBase, ulong> UserAcceptedImpl;
|
|
||||||
|
|
||||||
[ReflectedMethod]
|
|
||||||
private static Action<MyDedicatedServerBase, ulong, JoinResult> UserRejected;
|
|
||||||
[ReflectedMethod]
|
|
||||||
private static Func<MyMultiplayerBase, ulong, bool> IsClientBanned;
|
|
||||||
[ReflectedMethod]
|
|
||||||
private static Func<MyMultiplayerBase, ulong, bool> IsClientKicked;
|
|
||||||
[ReflectedMethod]
|
|
||||||
private static Action<MyMultiplayerBase, ulong> RaiseClientKicked;
|
|
||||||
}
|
|
||||||
}
|
|
124
Torch/Managers/MultiplayerManagerBase.cs
Normal file
124
Torch/Managers/MultiplayerManagerBase.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using NLog;
|
||||||
|
using Torch;
|
||||||
|
using Sandbox;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
|
using Sandbox.Game.Entities.Character;
|
||||||
|
using Sandbox.Game.Multiplayer;
|
||||||
|
using Sandbox.Game.World;
|
||||||
|
using Sandbox.ModAPI;
|
||||||
|
using SteamSDK;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.Collections;
|
||||||
|
using Torch.Commands;
|
||||||
|
using Torch.Utils;
|
||||||
|
using Torch.ViewModels;
|
||||||
|
using VRage.Game;
|
||||||
|
using VRage.Game.ModAPI;
|
||||||
|
using VRage.GameServices;
|
||||||
|
using VRage.Library.Collections;
|
||||||
|
using VRage.Network;
|
||||||
|
using VRage.Steam;
|
||||||
|
using VRage.Utils;
|
||||||
|
|
||||||
|
namespace Torch.Managers
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public abstract class MultiplayerManagerBase : Manager, IMultiplayerManagerBase
|
||||||
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event Action<IPlayer> PlayerJoined;
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event Action<IPlayer> PlayerLeft;
|
||||||
|
|
||||||
|
public ObservableDictionary<ulong, PlayerViewModel> Players { get; } = new ObservableDictionary<ulong, PlayerViewModel>();
|
||||||
|
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedGetter(Name = "m_players")]
|
||||||
|
private static Func<MyPlayerCollection, Dictionary<MyPlayer.PlayerId, MyPlayer>> _onlinePlayers;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
protected MultiplayerManagerBase(ITorchBase torch) : base(torch)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
MyMultiplayer.Static.ClientLeft += OnClientLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
if (MyMultiplayer.Static != null)
|
||||||
|
MyMultiplayer.Static.ClientLeft -= OnClientLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IMyPlayer GetPlayerByName(string name)
|
||||||
|
{
|
||||||
|
return _onlinePlayers.Invoke(MySession.Static.Players).FirstOrDefault(x => x.Value.DisplayName == name).Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IMyPlayer GetPlayerBySteamId(ulong steamId)
|
||||||
|
{
|
||||||
|
_onlinePlayers.Invoke(MySession.Static.Players).TryGetValue(new MyPlayer.PlayerId(steamId), out MyPlayer p);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong GetSteamId(long identityId)
|
||||||
|
{
|
||||||
|
foreach (KeyValuePair<MyPlayer.PlayerId, MyPlayer> kv in _onlinePlayers.Invoke(MySession.Static.Players))
|
||||||
|
{
|
||||||
|
if (kv.Value.Identity.IdentityId == identityId)
|
||||||
|
return kv.Key.SteamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string GetSteamUsername(ulong steamId)
|
||||||
|
{
|
||||||
|
return MyMultiplayer.Static.GetMemberName(steamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClientLeft(ulong steamId, MyChatMemberStateChangeEnum stateChange)
|
||||||
|
{
|
||||||
|
Players.TryGetValue(steamId, out PlayerViewModel vm);
|
||||||
|
if (vm == null)
|
||||||
|
vm = new PlayerViewModel(steamId);
|
||||||
|
_log.Info($"{vm.Name} ({vm.SteamId}) {(ConnectionState)stateChange}.");
|
||||||
|
PlayerLeft?.Invoke(vm);
|
||||||
|
Players.Remove(steamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RaiseClientJoined(ulong steamId)
|
||||||
|
{
|
||||||
|
var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected };
|
||||||
|
_log.Info($"Player {vm.Name} joined ({vm.SteamId}");
|
||||||
|
Players.Add(steamId, vm);
|
||||||
|
PlayerJoined?.Invoke(vm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -20,9 +20,9 @@ namespace Torch.Managers
|
|||||||
{
|
{
|
||||||
private static Logger _log = LogManager.GetLogger(nameof(NetworkManager));
|
private static Logger _log = LogManager.GetLogger(nameof(NetworkManager));
|
||||||
|
|
||||||
private const string MyTransportLayerField = "TransportLayer";
|
private const string _myTransportLayerField = "TransportLayer";
|
||||||
private const string TransportHandlersField = "m_handlers";
|
private const string _transportHandlersField = "m_handlers";
|
||||||
private HashSet<INetworkHandler> _networkHandlers = new HashSet<INetworkHandler>();
|
private readonly HashSet<INetworkHandler> _networkHandlers = new HashSet<INetworkHandler>();
|
||||||
private bool _init;
|
private bool _init;
|
||||||
|
|
||||||
[ReflectedGetter(Name = "m_typeTable")]
|
[ReflectedGetter(Name = "m_typeTable")]
|
||||||
@@ -40,14 +40,14 @@ namespace Torch.Managers
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var syncLayerType = typeof(MySyncLayer);
|
var syncLayerType = typeof(MySyncLayer);
|
||||||
var transportLayerField = syncLayerType.GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance);
|
var transportLayerField = syncLayerType.GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
|
||||||
if (transportLayerField == null)
|
if (transportLayerField == null)
|
||||||
throw new TypeLoadException("Could not find internal type for TransportLayer");
|
throw new TypeLoadException("Could not find internal type for TransportLayer");
|
||||||
|
|
||||||
var transportLayerType = transportLayerField.FieldType;
|
var transportLayerType = transportLayerField.FieldType;
|
||||||
|
|
||||||
if (!Reflection.HasField(transportLayerType, TransportHandlersField))
|
if (!Reflection.HasField(transportLayerType, _transportHandlersField))
|
||||||
throw new TypeLoadException("Could not find Handlers field");
|
throw new TypeLoadException("Could not find Handlers field");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -60,15 +60,9 @@ namespace Torch.Managers
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// Loads the network intercept system
|
/// <inheritdoc/>
|
||||||
/// </summary>
|
|
||||||
public override void Attach()
|
public override void Attach()
|
||||||
{
|
|
||||||
Torch.SessionLoaded += OnSessionLoaded;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSessionLoaded()
|
|
||||||
{
|
{
|
||||||
if (_init)
|
if (_init)
|
||||||
return;
|
return;
|
||||||
@@ -79,9 +73,9 @@ namespace Torch.Managers
|
|||||||
throw new InvalidOperationException("Reflection unit test failed.");
|
throw new InvalidOperationException("Reflection unit test failed.");
|
||||||
|
|
||||||
//don't bother with nullchecks here, it was all handled in ReflectionUnitTest
|
//don't bother with nullchecks here, it was all handled in ReflectionUnitTest
|
||||||
var transportType = typeof(MySyncLayer).GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance).FieldType;
|
var transportType = typeof(MySyncLayer).GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance).FieldType;
|
||||||
var transportInstance = typeof(MySyncLayer).GetField(MyTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.Static.SyncLayer);
|
var transportInstance = typeof(MySyncLayer).GetField(_myTransportLayerField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(MyMultiplayer.Static.SyncLayer);
|
||||||
var handlers = (IDictionary)transportType.GetField(TransportHandlersField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(transportInstance);
|
var handlers = (IDictionary)transportType.GetField(_transportHandlersField, BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(transportInstance);
|
||||||
var handlerTypeField = handlers.GetType().GenericTypeArguments[0].GetField("messageId"); //Should be MyTransportLayer.HandlerId
|
var handlerTypeField = handlers.GetType().GenericTypeArguments[0].GetField("messageId"); //Should be MyTransportLayer.HandlerId
|
||||||
object id = null;
|
object id = null;
|
||||||
foreach (var key in handlers.Keys)
|
foreach (var key in handlers.Keys)
|
||||||
@@ -105,6 +99,12 @@ namespace Torch.Managers
|
|||||||
_log.Debug("Initialized network intercept");
|
_log.Debug("Initialized network intercept");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
// TODO reverse what was done in Attach
|
||||||
|
}
|
||||||
|
|
||||||
#region Network Intercept
|
#region Network Intercept
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -205,6 +205,8 @@ namespace Torch.Managers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void RegisterNetworkHandler(INetworkHandler handler)
|
public void RegisterNetworkHandler(INetworkHandler handler)
|
||||||
{
|
{
|
||||||
var handlerType = handler.GetType().FullName;
|
var handlerType = handler.GetType().FullName;
|
||||||
@@ -225,6 +227,12 @@ namespace Torch.Managers
|
|||||||
_networkHandlers.Add(handler);
|
_networkHandlers.Add(handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool UnregisterNetworkHandler(INetworkHandler handler)
|
||||||
|
{
|
||||||
|
return _networkHandlers.Remove(handler);
|
||||||
|
}
|
||||||
|
|
||||||
public void RegisterNetworkHandlers(params INetworkHandler[] handlers)
|
public void RegisterNetworkHandlers(params INetworkHandler[] handlers)
|
||||||
{
|
{
|
||||||
foreach (var handler in handlers)
|
foreach (var handler in handlers)
|
||||||
|
@@ -7,6 +7,7 @@ using System.Reflection.Emit;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using Torch.Managers.PatchManager.MSIL;
|
||||||
using Torch.Managers.PatchManager.Transpile;
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
|
|
||||||
@@ -34,18 +35,21 @@ namespace Torch.Managers.PatchManager
|
|||||||
|
|
||||||
if (Prefixes.Count == 0 && Suffixes.Count == 0 && Transpilers.Count == 0)
|
if (Prefixes.Count == 0 && Suffixes.Count == 0 && Transpilers.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
_log.Debug($"Begin patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})");
|
||||||
var patch = ComposePatchedMethod();
|
var patch = ComposePatchedMethod();
|
||||||
|
|
||||||
_revertAddress = AssemblyMemory.GetMethodBodyStart(_method);
|
_revertAddress = AssemblyMemory.GetMethodBodyStart(_method);
|
||||||
var newAddress = AssemblyMemory.GetMethodBodyStart(patch);
|
var newAddress = AssemblyMemory.GetMethodBodyStart(patch);
|
||||||
_revertData = AssemblyMemory.WriteJump(_revertAddress, newAddress);
|
_revertData = AssemblyMemory.WriteJump(_revertAddress, newAddress);
|
||||||
_pinnedPatch = GCHandle.Alloc(patch);
|
_pinnedPatch = GCHandle.Alloc(patch);
|
||||||
|
_log.Debug($"Done patching {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Revert()
|
internal void Revert()
|
||||||
{
|
{
|
||||||
if (_pinnedPatch.HasValue)
|
if (_pinnedPatch.HasValue)
|
||||||
{
|
{
|
||||||
|
_log.Debug($"Revert {_method.DeclaringType?.FullName}#{_method.Name}({string.Join(", ", _method.GetParameters().Select(x => x.ParameterType.Name))})");
|
||||||
AssemblyMemory.WriteMemory(_revertAddress, _revertData);
|
AssemblyMemory.WriteMemory(_revertAddress, _revertData);
|
||||||
_revertData = null;
|
_revertData = null;
|
||||||
_pinnedPatch.Value.Free();
|
_pinnedPatch.Value.Free();
|
||||||
@@ -113,29 +117,30 @@ namespace Torch.Managers.PatchManager
|
|||||||
|
|
||||||
var specialVariables = new Dictionary<string, LocalBuilder>();
|
var specialVariables = new Dictionary<string, LocalBuilder>();
|
||||||
|
|
||||||
Label? labelAfterOriginalContent = Suffixes.Count > 0 ? target.DefineLabel() : (Label?)null;
|
Label labelAfterOriginalContent = target.DefineLabel();
|
||||||
Label? labelAfterOriginalReturn = Prefixes.Any(x => x.ReturnType == typeof(bool)) ? target.DefineLabel() : (Label?)null;
|
Label labelAfterOriginalReturn = target.DefineLabel();
|
||||||
|
|
||||||
|
|
||||||
var returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
|
Type returnType = _method is MethodInfo meth ? meth.ReturnType : typeof(void);
|
||||||
var resultVariable = returnType != typeof(void) && (labelAfterOriginalReturn.HasValue || // If we jump past main content we need local to store return val
|
LocalBuilder resultVariable = null;
|
||||||
Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER))
|
if (returnType != typeof(void))
|
||||||
? target.DeclareLocal(returnType)
|
{
|
||||||
: null;
|
if (Prefixes.Concat(Suffixes).SelectMany(x => x.GetParameters()).Any(x => x.Name == RESULT_PARAMETER))
|
||||||
|
resultVariable = target.DeclareLocal(returnType);
|
||||||
|
else if (Prefixes.Any(x => x.ReturnType == typeof(bool)))
|
||||||
|
resultVariable = target.DeclareLocal(returnType);
|
||||||
|
}
|
||||||
resultVariable?.SetToDefault(target);
|
resultVariable?.SetToDefault(target);
|
||||||
|
|
||||||
if (resultVariable != null)
|
if (resultVariable != null)
|
||||||
specialVariables.Add(RESULT_PARAMETER, resultVariable);
|
specialVariables.Add(RESULT_PARAMETER, resultVariable);
|
||||||
|
|
||||||
target.EmitComment("Prefixes Begin");
|
target.EmitComment("Prefixes Begin");
|
||||||
foreach (var prefix in Prefixes)
|
foreach (MethodInfo prefix in Prefixes)
|
||||||
{
|
{
|
||||||
EmitMonkeyCall(target, prefix, specialVariables);
|
EmitMonkeyCall(target, prefix, specialVariables);
|
||||||
if (prefix.ReturnType == typeof(bool))
|
if (prefix.ReturnType == typeof(bool))
|
||||||
{
|
target.Emit(OpCodes.Brfalse, labelAfterOriginalReturn);
|
||||||
Debug.Assert(labelAfterOriginalReturn.HasValue);
|
|
||||||
target.Emit(OpCodes.Brfalse, labelAfterOriginalReturn.Value);
|
|
||||||
}
|
|
||||||
else if (prefix.ReturnType != typeof(void))
|
else if (prefix.ReturnType != typeof(void))
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
$"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}");
|
$"Prefixes must return void or bool. {prefix.DeclaringType?.FullName}.{prefix.Name} returns {prefix.ReturnType}");
|
||||||
@@ -143,32 +148,25 @@ namespace Torch.Managers.PatchManager
|
|||||||
target.EmitComment("Prefixes End");
|
target.EmitComment("Prefixes End");
|
||||||
|
|
||||||
target.EmitComment("Original Begin");
|
target.EmitComment("Original Begin");
|
||||||
MethodTranspiler.Transpile(_method, Transpilers, target, labelAfterOriginalContent);
|
MethodTranspiler.Transpile(_method, (type) => new MsilLocal(target.DeclareLocal(type)), Transpilers, target, labelAfterOriginalContent);
|
||||||
target.EmitComment("Original End");
|
target.EmitComment("Original End");
|
||||||
if (labelAfterOriginalContent.HasValue)
|
|
||||||
{
|
target.MarkLabel(labelAfterOriginalContent);
|
||||||
target.MarkLabel(labelAfterOriginalContent.Value);
|
if (resultVariable != null)
|
||||||
if (resultVariable != null)
|
target.Emit(OpCodes.Stloc, resultVariable);
|
||||||
target.Emit(OpCodes.Stloc, resultVariable);
|
target.MarkLabel(labelAfterOriginalReturn);
|
||||||
}
|
|
||||||
if (labelAfterOriginalReturn.HasValue)
|
|
||||||
target.MarkLabel(labelAfterOriginalReturn.Value);
|
|
||||||
|
|
||||||
target.EmitComment("Suffixes Begin");
|
target.EmitComment("Suffixes Begin");
|
||||||
foreach (var suffix in Suffixes)
|
foreach (MethodInfo suffix in Suffixes)
|
||||||
{
|
{
|
||||||
EmitMonkeyCall(target, suffix, specialVariables);
|
EmitMonkeyCall(target, suffix, specialVariables);
|
||||||
if (suffix.ReturnType != typeof(void))
|
if (suffix.ReturnType != typeof(void))
|
||||||
throw new Exception($"Suffixes must return void. {suffix.DeclaringType?.FullName}.{suffix.Name} returns {suffix.ReturnType}");
|
throw new Exception($"Suffixes must return void. {suffix.DeclaringType?.FullName}.{suffix.Name} returns {suffix.ReturnType}");
|
||||||
}
|
}
|
||||||
target.EmitComment("Suffixes End");
|
target.EmitComment("Suffixes End");
|
||||||
|
if (resultVariable != null)
|
||||||
if (labelAfterOriginalContent.HasValue || labelAfterOriginalReturn.HasValue)
|
target.Emit(OpCodes.Ldloc, resultVariable);
|
||||||
{
|
target.Emit(OpCodes.Ret);
|
||||||
if (resultVariable != null)
|
|
||||||
target.Emit(OpCodes.Ldloc, resultVariable);
|
|
||||||
target.Emit(OpCodes.Ret);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EmitMonkeyCall(LoggingIlGenerator target, MethodInfo patch,
|
private void EmitMonkeyCall(LoggingIlGenerator target, MethodInfo patch,
|
||||||
|
@@ -24,7 +24,7 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
{
|
{
|
||||||
_module = method.Module;
|
_module = method.Module;
|
||||||
_genericTypeArgs = method.DeclaringType?.GenericTypeArguments ?? new Type[0];
|
_genericTypeArgs = method.DeclaringType?.GenericTypeArguments ?? new Type[0];
|
||||||
_genericMethArgs = method.GetGenericArguments();
|
_genericMethArgs = (method is MethodInfo ? method.GetGenericArguments() : new Type[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MemberInfo ResolveMember(int token)
|
public MemberInfo ResolveMember(int token)
|
||||||
|
55
Torch/Managers/PatchManager/MSIL/MsilArgument.cs
Normal file
55
Torch/Managers/PatchManager/MSIL/MsilArgument.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Managers.PatchManager.MSIL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents metadata about a method's parameter
|
||||||
|
/// </summary>
|
||||||
|
public class MsilArgument
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The positon of this argument. Note, if the method is static, index 0 is the instance.
|
||||||
|
/// </summary>
|
||||||
|
public int Position { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of this parameter, or null if unknown.
|
||||||
|
/// </summary>
|
||||||
|
public Type Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of this parameter, or null if unknown.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
internal MsilArgument(ParameterInfo local)
|
||||||
|
{
|
||||||
|
Position = (((MethodBase)local.Member).IsStatic ? 0 : 1) + local.Position;
|
||||||
|
Type = local.ParameterType;
|
||||||
|
Name = local.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an empty argument reference with the given position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="position">The argument's position</param>
|
||||||
|
public MsilArgument(int position)
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
Type = null;
|
||||||
|
Name = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"arg{Position:X4}({Type?.Name ?? "unknown"})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,9 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Torch.Managers.PatchManager.Transpile;
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
|
using Torch.Utils;
|
||||||
|
using Label = System.Windows.Controls.Label;
|
||||||
|
|
||||||
namespace Torch.Managers.PatchManager.MSIL
|
namespace Torch.Managers.PatchManager.MSIL
|
||||||
{
|
{
|
||||||
@@ -12,8 +16,6 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class MsilInstruction
|
public class MsilInstruction
|
||||||
{
|
{
|
||||||
private MsilOperand _operandBacking;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instruction with the given opcode.
|
/// Creates a new instruction with the given opcode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -65,11 +67,11 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
if (OpCode.Name.IndexOf("loc", StringComparison.OrdinalIgnoreCase) != -1)
|
if (OpCode.Name.IndexOf("loc", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
Operand = new MsilOperandInline.MsilOperandLocal(this);
|
Operand = new MsilOperandInline.MsilOperandLocal(this);
|
||||||
else
|
else
|
||||||
Operand = new MsilOperandInline.MsilOperandParameter(this);
|
Operand = new MsilOperandInline.MsilOperandArgument(this);
|
||||||
break;
|
break;
|
||||||
case OperandType.ShortInlineI:
|
case OperandType.ShortInlineI:
|
||||||
Operand = OpCode == OpCodes.Ldc_I4_S
|
Operand = OpCode == OpCodes.Ldc_I4_S
|
||||||
? (MsilOperand) new MsilOperandInline.MsilOperandInt8(this)
|
? (MsilOperand)new MsilOperandInline.MsilOperandInt8(this)
|
||||||
: new MsilOperandInline.MsilOperandUInt8(this);
|
: new MsilOperandInline.MsilOperandUInt8(this);
|
||||||
break;
|
break;
|
||||||
case OperandType.ShortInlineR:
|
case OperandType.ShortInlineR:
|
||||||
@@ -96,22 +98,16 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The operand for this instruction, or null.
|
/// The operand for this instruction, or null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MsilOperand Operand
|
public MsilOperand Operand { get; }
|
||||||
{
|
|
||||||
get => _operandBacking;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (_operandBacking != null && value.GetType() != _operandBacking.GetType())
|
|
||||||
throw new ArgumentException($"Operand for {OpCode.Name} must be {_operandBacking.GetType().Name}");
|
|
||||||
_operandBacking = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Labels pointing to this instruction.
|
/// Labels pointing to this instruction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public HashSet<MsilLabel> Labels { get; } = new HashSet<MsilLabel>();
|
public HashSet<MsilLabel> Labels { get; } = new HashSet<MsilLabel>();
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly ConcurrentDictionary<Type, PropertyInfo> _setterInfoForInlines = new ConcurrentDictionary<Type, PropertyInfo>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the inline value for this instruction.
|
/// Sets the inline value for this instruction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -120,10 +116,41 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// <returns>This instruction</returns>
|
/// <returns>This instruction</returns>
|
||||||
public MsilInstruction InlineValue<T>(T o)
|
public MsilInstruction InlineValue<T>(T o)
|
||||||
{
|
{
|
||||||
((MsilOperandInline<T>) Operand).Value = o;
|
Type type = typeof(T);
|
||||||
|
while (type != null)
|
||||||
|
{
|
||||||
|
if (!_setterInfoForInlines.TryGetValue(type, out PropertyInfo target))
|
||||||
|
{
|
||||||
|
Type genType = typeof(MsilOperandInline<>).MakeGenericType(type);
|
||||||
|
target = genType.GetProperty(nameof(MsilOperandInline<int>.Value));
|
||||||
|
_setterInfoForInlines[type] = target;
|
||||||
|
}
|
||||||
|
Debug.Assert(target?.DeclaringType != null);
|
||||||
|
if (target.DeclaringType.IsInstanceOfType(Operand))
|
||||||
|
{
|
||||||
|
target.SetValue(Operand, o);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
type = type.BaseType;
|
||||||
|
}
|
||||||
|
((MsilOperandInline<T>)Operand).Value = o;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes a copy of the instruction with a new opcode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newOpcode">The new opcode</param>
|
||||||
|
/// <returns>The copy</returns>
|
||||||
|
public MsilInstruction CopyWith(OpCode newOpcode)
|
||||||
|
{
|
||||||
|
var result = new MsilInstruction(newOpcode);
|
||||||
|
Operand?.CopyTo(result.Operand);
|
||||||
|
foreach (MsilLabel x in Labels)
|
||||||
|
result.Labels.Add(x);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the inline branch target for this instruction.
|
/// Sets the inline branch target for this instruction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -131,7 +158,7 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// <returns>This instruction</returns>
|
/// <returns>This instruction</returns>
|
||||||
public MsilInstruction InlineTarget(MsilLabel label)
|
public MsilInstruction InlineTarget(MsilLabel label)
|
||||||
{
|
{
|
||||||
((MsilOperandBrTarget) Operand).Target = label;
|
((MsilOperandBrTarget)Operand).Target = label;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,5 +185,32 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
sb.Append(OpCode.Name).Append("\t").Append(Operand);
|
sb.Append(OpCode.Name).Append("\t").Append(Operand);
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#pragma warning disable 169
|
||||||
|
[ReflectedMethod(Name = "StackChange")]
|
||||||
|
private static Func<OpCode, int> _stackChange;
|
||||||
|
#pragma warning restore 169
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estimates the stack delta for this instruction.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Stack delta</returns>
|
||||||
|
public int StackChange()
|
||||||
|
{
|
||||||
|
int num = _stackChange.Invoke(OpCode);
|
||||||
|
if ((OpCode == OpCodes.Call || OpCode == OpCodes.Callvirt || OpCode == OpCodes.Newobj) &&
|
||||||
|
Operand is MsilOperandInline<MethodBase> inline)
|
||||||
|
{
|
||||||
|
MethodBase op = inline.Value;
|
||||||
|
if (op is MethodInfo mi && mi.ReturnType != typeof(void))
|
||||||
|
num++;
|
||||||
|
num -= op.GetParameters().Length;
|
||||||
|
if (!op.IsStatic && OpCode != OpCodes.Newobj)
|
||||||
|
num--;
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
202
Torch/Managers/PatchManager/MSIL/MsilInstructionExtensions.cs
Normal file
202
Torch/Managers/PatchManager/MSIL/MsilInstructionExtensions.cs
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Managers.PatchManager.MSIL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Various methods to make composing MSIL easier
|
||||||
|
/// </summary>
|
||||||
|
public static class MsilInstructionExtensions
|
||||||
|
{
|
||||||
|
#region Local Utils
|
||||||
|
/// <summary>
|
||||||
|
/// Is this instruction a local load-by-value instruction.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsLocalLoad(this MsilInstruction me)
|
||||||
|
{
|
||||||
|
return me.OpCode == OpCodes.Ldloc || me.OpCode == OpCodes.Ldloc_S || me.OpCode == OpCodes.Ldloc_0 ||
|
||||||
|
me.OpCode == OpCodes.Ldloc_1 || me.OpCode == OpCodes.Ldloc_2 || me.OpCode == OpCodes.Ldloc_3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this instruction a local load-by-reference instruction.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsLocalLoadByRef(this MsilInstruction me)
|
||||||
|
{
|
||||||
|
return me.OpCode == OpCodes.Ldloca || me.OpCode == OpCodes.Ldloca_S;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this instruction a local store instruction.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsLocalStore(this MsilInstruction me)
|
||||||
|
{
|
||||||
|
return me.OpCode == OpCodes.Stloc || me.OpCode == OpCodes.Stloc_S || me.OpCode == OpCodes.Stloc_0 ||
|
||||||
|
me.OpCode == OpCodes.Stloc_1 || me.OpCode == OpCodes.Stloc_2 || me.OpCode == OpCodes.Stloc_3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For a local referencing opcode, get the local it is referencing.
|
||||||
|
/// </summary>
|
||||||
|
public static MsilLocal GetReferencedLocal(this MsilInstruction me)
|
||||||
|
{
|
||||||
|
if (me.Operand is MsilOperandInline.MsilOperandLocal mol)
|
||||||
|
return mol.Value;
|
||||||
|
if (me.OpCode == OpCodes.Stloc_0 || me.OpCode == OpCodes.Ldloc_0)
|
||||||
|
return new MsilLocal(0);
|
||||||
|
if (me.OpCode == OpCodes.Stloc_1 || me.OpCode == OpCodes.Ldloc_1)
|
||||||
|
return new MsilLocal(1);
|
||||||
|
if (me.OpCode == OpCodes.Stloc_2 || me.OpCode == OpCodes.Ldloc_2)
|
||||||
|
return new MsilLocal(2);
|
||||||
|
if (me.OpCode == OpCodes.Stloc_3 || me.OpCode == OpCodes.Ldloc_3)
|
||||||
|
return new MsilLocal(3);
|
||||||
|
throw new ArgumentException($"Can't get referenced local in instruction {me}");
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instruction representing a load-by-value from the given local.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="local">Local to load</param>
|
||||||
|
/// <returns>Loading instruction</returns>
|
||||||
|
public static MsilInstruction AsValueLoad(this MsilLocal local)
|
||||||
|
{
|
||||||
|
switch (local.Index)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return new MsilInstruction(OpCodes.Ldloc_0);
|
||||||
|
case 1:
|
||||||
|
return new MsilInstruction(OpCodes.Ldloc_1);
|
||||||
|
case 2:
|
||||||
|
return new MsilInstruction(OpCodes.Ldloc_2);
|
||||||
|
case 3:
|
||||||
|
return new MsilInstruction(OpCodes.Ldloc_3);
|
||||||
|
default:
|
||||||
|
return new MsilInstruction(local.Index < 0xFF ? OpCodes.Ldloc_S : OpCodes.Ldloc).InlineValue(local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instruction representing a store-by-value to the given local.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="local">Local to write to</param>
|
||||||
|
/// <returns>Loading instruction</returns>
|
||||||
|
public static MsilInstruction AsValueStore(this MsilLocal local)
|
||||||
|
{
|
||||||
|
switch (local.Index)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return new MsilInstruction(OpCodes.Stloc_0);
|
||||||
|
case 1:
|
||||||
|
return new MsilInstruction(OpCodes.Stloc_1);
|
||||||
|
case 2:
|
||||||
|
return new MsilInstruction(OpCodes.Stloc_2);
|
||||||
|
case 3:
|
||||||
|
return new MsilInstruction(OpCodes.Stloc_3);
|
||||||
|
default:
|
||||||
|
return new MsilInstruction(local.Index < 0xFF ? OpCodes.Stloc_S : OpCodes.Stloc).InlineValue(local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instruction representing a load-by-reference from the given local.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="local">Local to load</param>
|
||||||
|
/// <returns>Loading instruction</returns>
|
||||||
|
public static MsilInstruction AsReferenceLoad(this MsilLocal local)
|
||||||
|
{
|
||||||
|
return new MsilInstruction(local.Index < 0xFF ? OpCodes.Ldloca_S : OpCodes.Ldloca).InlineValue(local);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Argument Utils
|
||||||
|
/// <summary>
|
||||||
|
/// Is this instruction an argument load-by-value instruction.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsArgumentLoad(this MsilInstruction me)
|
||||||
|
{
|
||||||
|
return me.OpCode == OpCodes.Ldarg || me.OpCode == OpCodes.Ldarg_S || me.OpCode == OpCodes.Ldarg_0 ||
|
||||||
|
me.OpCode == OpCodes.Ldarg_1 || me.OpCode == OpCodes.Ldarg_2 || me.OpCode == OpCodes.Ldarg_3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this instruction an argument load-by-reference instruction.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsArgumentLoadByRef(this MsilInstruction me)
|
||||||
|
{
|
||||||
|
return me.OpCode == OpCodes.Ldarga || me.OpCode == OpCodes.Ldarga_S;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is this instruction an argument store instruction.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsArgumentStore(this MsilInstruction me)
|
||||||
|
{
|
||||||
|
return me.OpCode == OpCodes.Starg || me.OpCode == OpCodes.Starg_S;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// For an argument referencing opcode, get the index of the local it is referencing.
|
||||||
|
/// </summary>
|
||||||
|
public static MsilArgument GetReferencedArgument(this MsilInstruction me)
|
||||||
|
{
|
||||||
|
if (me.Operand is MsilOperandInline.MsilOperandArgument mol)
|
||||||
|
return mol.Value;
|
||||||
|
if (me.OpCode == OpCodes.Ldarg_0)
|
||||||
|
return new MsilArgument(0);
|
||||||
|
if (me.OpCode == OpCodes.Ldarg_1)
|
||||||
|
return new MsilArgument(1);
|
||||||
|
if (me.OpCode == OpCodes.Ldarg_2)
|
||||||
|
return new MsilArgument(2);
|
||||||
|
if (me.OpCode == OpCodes.Ldarg_3)
|
||||||
|
return new MsilArgument(3);
|
||||||
|
throw new ArgumentException($"Can't get referenced argument in instruction {me}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instruction representing a load-by-value from the given argument.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">argument to load</param>
|
||||||
|
/// <returns>Load instruction</returns>
|
||||||
|
public static MsilInstruction AsValueLoad(this MsilArgument argument)
|
||||||
|
{
|
||||||
|
switch (argument.Position)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return new MsilInstruction(OpCodes.Ldarg_0);
|
||||||
|
case 1:
|
||||||
|
return new MsilInstruction(OpCodes.Ldarg_1);
|
||||||
|
case 2:
|
||||||
|
return new MsilInstruction(OpCodes.Ldarg_2);
|
||||||
|
case 3:
|
||||||
|
return new MsilInstruction(OpCodes.Ldarg_3);
|
||||||
|
default:
|
||||||
|
return new MsilInstruction(argument.Position < 0xFF ? OpCodes.Ldarg_S : OpCodes.Ldarg).InlineValue(argument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instruction representing a store-by-value to the given argument.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">argument to write to</param>
|
||||||
|
/// <returns>Store instruction</returns>
|
||||||
|
public static MsilInstruction AsValueStore(this MsilArgument argument)
|
||||||
|
{
|
||||||
|
return new MsilInstruction(argument.Position < 0xFF ? OpCodes.Starg_S : OpCodes.Starg).InlineValue(argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an instruction representing a load-by-reference from the given argument.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="argument">argument to load</param>
|
||||||
|
/// <returns>Reference load instruction</returns>
|
||||||
|
public static MsilInstruction AsReferenceLoad(this MsilArgument argument)
|
||||||
|
{
|
||||||
|
return new MsilInstruction(argument.Position < 0xFF ? OpCodes.Ldarga_S : OpCodes.Ldarga).InlineValue(argument);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
62
Torch/Managers/PatchManager/MSIL/MsilLocal.cs
Normal file
62
Torch/Managers/PatchManager/MSIL/MsilLocal.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Torch.Managers.PatchManager.MSIL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents metadata about a method's local
|
||||||
|
/// </summary>
|
||||||
|
public class MsilLocal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The index of this local.
|
||||||
|
/// </summary>
|
||||||
|
public int Index { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of this local, or null if unknown.
|
||||||
|
/// </summary>
|
||||||
|
public Type Type { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of this local, or null if unknown.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
internal MsilLocal(LocalBuilder local)
|
||||||
|
{
|
||||||
|
Index = local.LocalIndex;
|
||||||
|
Type = local.LocalType;
|
||||||
|
Name = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal MsilLocal(LocalVariableInfo local)
|
||||||
|
{
|
||||||
|
Index = local.LocalIndex;
|
||||||
|
Type = local.LocalType;
|
||||||
|
Name = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an empty local reference with the given index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">The local's index</param>
|
||||||
|
public MsilLocal(int index)
|
||||||
|
{
|
||||||
|
Index = index;
|
||||||
|
Type = null;
|
||||||
|
Name = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"lcl{Index:X4}({Type?.Name ?? "unknown"})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -18,6 +18,8 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MsilInstruction Instruction { get; }
|
public MsilInstruction Instruction { get; }
|
||||||
|
|
||||||
|
internal abstract void CopyTo(MsilOperand operand);
|
||||||
|
|
||||||
internal abstract void Read(MethodContext context, BinaryReader reader);
|
internal abstract void Read(MethodContext context, BinaryReader reader);
|
||||||
|
|
||||||
internal abstract void Emit(LoggingIlGenerator generator);
|
internal abstract void Emit(LoggingIlGenerator generator);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
using Torch.Managers.PatchManager.Transpile;
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
|
|
||||||
@@ -22,8 +23,8 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
{
|
{
|
||||||
int val = Instruction.OpCode.OperandType == OperandType.InlineBrTarget
|
int val = Instruction.OpCode.OperandType == OperandType.InlineBrTarget
|
||||||
? reader.ReadInt32()
|
? reader.ReadInt32()
|
||||||
: reader.ReadByte();
|
: reader.ReadSByte();
|
||||||
Target = context.LabelAt((int) reader.BaseStream.Position + val);
|
Target = context.LabelAt((int)reader.BaseStream.Position + val);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
@@ -31,6 +32,14 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
generator.Emit(Instruction.OpCode, Target.LabelFor(generator));
|
generator.Emit(Instruction.OpCode, Target.LabelFor(generator));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override void CopyTo(MsilOperand operand)
|
||||||
|
{
|
||||||
|
var lt = operand as MsilOperandBrTarget;
|
||||||
|
if (lt == null)
|
||||||
|
throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand));
|
||||||
|
lt.Target = Target;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
using Torch.Managers.PatchManager.Transpile;
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
@@ -22,6 +23,15 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public T Value { get; set; }
|
public T Value { get; set; }
|
||||||
|
|
||||||
|
internal override void CopyTo(MsilOperand operand)
|
||||||
|
{
|
||||||
|
var lt = operand as MsilOperandInline<T>;
|
||||||
|
if (lt == null)
|
||||||
|
throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand));
|
||||||
|
lt.Value = Value;
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
@@ -66,7 +76,7 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
Value =
|
Value =
|
||||||
(sbyte) reader.ReadByte();
|
(sbyte)reader.ReadByte();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
@@ -199,21 +209,23 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inline parameter reference
|
/// Inline argument reference
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MsilOperandParameter : MsilOperandInline<ParameterInfo>
|
public class MsilOperandArgument : MsilOperandInline<MsilArgument>
|
||||||
{
|
{
|
||||||
internal MsilOperandParameter(MsilInstruction instruction) : base(instruction)
|
internal MsilOperandArgument(MsilInstruction instruction) : base(instruction)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
Value =
|
int paramID =
|
||||||
context.Method.GetParameters()[
|
Instruction.OpCode.OperandType == OperandType.ShortInlineVar
|
||||||
Instruction.OpCode.OperandType == OperandType.ShortInlineVar
|
? reader.ReadByte()
|
||||||
? reader.ReadByte()
|
: reader.ReadUInt16();
|
||||||
: reader.ReadUInt16()];
|
if (paramID == 0 && !context.Method.IsStatic)
|
||||||
|
throw new ArgumentException("Haven't figured out how to ldarg with the \"this\" argument");
|
||||||
|
Value = new MsilArgument(context.Method.GetParameters()[paramID - (context.Method.IsStatic ? 0 : 1)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
@@ -225,7 +237,7 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inline local variable reference
|
/// Inline local variable reference
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MsilOperandLocal : MsilOperandInline<LocalVariableInfo>
|
public class MsilOperandLocal : MsilOperandInline<MsilLocal>
|
||||||
{
|
{
|
||||||
internal MsilOperandLocal(MsilInstruction instruction) : base(instruction)
|
internal MsilOperandLocal(MsilInstruction instruction) : base(instruction)
|
||||||
{
|
{
|
||||||
@@ -234,15 +246,15 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
Value =
|
Value =
|
||||||
context.Method.GetMethodBody().LocalVariables[
|
new MsilLocal(context.Method.GetMethodBody().LocalVariables[
|
||||||
Instruction.OpCode.OperandType == OperandType.ShortInlineVar
|
Instruction.OpCode.OperandType == OperandType.ShortInlineVar
|
||||||
? reader.ReadByte()
|
? reader.ReadByte()
|
||||||
: reader.ReadUInt16()];
|
: reader.ReadUInt16()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override void Emit(LoggingIlGenerator generator)
|
internal override void Emit(LoggingIlGenerator generator)
|
||||||
{
|
{
|
||||||
generator.Emit(Instruction.OpCode, Value.LocalIndex);
|
generator.Emit(Instruction.OpCode, Value.Index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
using Torch.Managers.PatchManager.Transpile;
|
using Torch.Managers.PatchManager.Transpile;
|
||||||
@@ -19,6 +20,20 @@ namespace Torch.Managers.PatchManager.MSIL
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public MsilLabel[] Labels { get; set; }
|
public MsilLabel[] Labels { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
internal override void CopyTo(MsilOperand operand)
|
||||||
|
{
|
||||||
|
var lt = operand as MsilOperandSwitch;
|
||||||
|
if (lt == null)
|
||||||
|
throw new ArgumentException($"Target {operand?.GetType().Name} must be of same type {GetType().Name}", nameof(operand));
|
||||||
|
if (Labels == null)
|
||||||
|
lt.Labels = null;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lt.Labels = new MsilLabel[Labels.Length];
|
||||||
|
Array.Copy(Labels, lt.Labels, Labels.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
internal override void Read(MethodContext context, BinaryReader reader)
|
internal override void Read(MethodContext context, BinaryReader reader)
|
||||||
{
|
{
|
||||||
int length = reader.ReadInt32();
|
int length = reader.ReadInt32();
|
||||||
|
@@ -52,7 +52,7 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
/// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/>
|
/// <inheritdoc cref="ILGenerator.Emit(OpCode, LocalBuilder)"/>
|
||||||
public void Emit(OpCode op, LocalBuilder arg)
|
public void Emit(OpCode op, LocalBuilder arg)
|
||||||
{
|
{
|
||||||
_log?.Trace($"Emit\t{op,_opcodePadding} L:{arg.LocalIndex} {arg.LocalType}");
|
_log?.Trace($"Emit\t{op,_opcodePadding} Local:{arg.LocalIndex}/{arg.LocalType}");
|
||||||
Backing.Emit(op, arg);
|
Backing.Emit(op, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ using System.Reflection;
|
|||||||
using System.Reflection.Emit;
|
using System.Reflection.Emit;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Torch.Managers.PatchManager.MSIL;
|
using Torch.Managers.PatchManager.MSIL;
|
||||||
|
using Torch.Utils;
|
||||||
|
|
||||||
namespace Torch.Managers.PatchManager.Transpile
|
namespace Torch.Managers.PatchManager.Transpile
|
||||||
{
|
{
|
||||||
@@ -34,8 +35,8 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
public MethodContext(MethodBase method)
|
public MethodContext(MethodBase method)
|
||||||
{
|
{
|
||||||
Method = method;
|
Method = method;
|
||||||
_msilBytes = Method.GetMethodBody().GetILAsByteArray();
|
_msilBytes = Method.GetMethodBody().GetILAsByteArray();
|
||||||
TokenResolver = new NormalTokenResolver(method);
|
TokenResolver = new NormalTokenResolver(method);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Read()
|
public void Read()
|
||||||
@@ -52,20 +53,19 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
using (var reader = new BinaryReader(memory))
|
using (var reader = new BinaryReader(memory))
|
||||||
while (memory.Length > memory.Position)
|
while (memory.Length > memory.Position)
|
||||||
{
|
{
|
||||||
var count = 1;
|
var opcodeOffset = (int) memory.Position;
|
||||||
var instructionValue = (short)memory.ReadByte();
|
var instructionValue = (short)memory.ReadByte();
|
||||||
if (Prefixes.Contains(instructionValue))
|
if (Prefixes.Contains(instructionValue))
|
||||||
{
|
{
|
||||||
instructionValue = (short) ((instructionValue << 8) | memory.ReadByte());
|
instructionValue = (short)((instructionValue << 8) | memory.ReadByte());
|
||||||
count++;
|
|
||||||
}
|
}
|
||||||
if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode))
|
if (!OpCodeLookup.TryGetValue(instructionValue, out OpCode opcode))
|
||||||
throw new Exception($"Unknown opcode {instructionValue:X}");
|
throw new Exception($"Unknown opcode {instructionValue:X}");
|
||||||
if (opcode.Size != count)
|
if (opcode.Size != memory.Position - opcodeOffset)
|
||||||
throw new Exception($"Opcode said it was {opcode.Size} but we read {count}");
|
throw new Exception($"Opcode said it was {opcode.Size} but we read {memory.Position - opcodeOffset}");
|
||||||
var instruction = new MsilInstruction(opcode)
|
var instruction = new MsilInstruction(opcode)
|
||||||
{
|
{
|
||||||
Offset = (int) memory.Position
|
Offset = opcodeOffset
|
||||||
};
|
};
|
||||||
_instructions.Add(instruction);
|
_instructions.Add(instruction);
|
||||||
instruction.Operand?.Read(this, reader);
|
instruction.Operand?.Read(this, reader);
|
||||||
@@ -76,22 +76,74 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
{
|
{
|
||||||
foreach (var label in Labels)
|
foreach (var label in Labels)
|
||||||
{
|
{
|
||||||
int min = 0, max = _instructions.Count - 1;
|
int min = 0, max = _instructions.Count;
|
||||||
while (min <= max)
|
while (min != max)
|
||||||
{
|
{
|
||||||
var mid = min + ((max - min) / 2);
|
int mid = (min + max) / 2;
|
||||||
if (label.Key < _instructions[mid].Offset)
|
if (_instructions[mid].Offset < label.Key)
|
||||||
max = mid - 1;
|
|
||||||
else
|
|
||||||
min = mid + 1;
|
min = mid + 1;
|
||||||
|
else
|
||||||
|
max = mid;
|
||||||
}
|
}
|
||||||
|
#if DEBUG
|
||||||
|
if (min >= _instructions.Count || min < 0)
|
||||||
|
{
|
||||||
|
_log.Trace(
|
||||||
|
$"Want offset {label.Key} for {label.Value}, instruction offsets at\n {string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4} {x}"))}");
|
||||||
|
}
|
||||||
|
MsilInstruction prevInsn = min > 0 ? _instructions[min - 1] : null;
|
||||||
|
if ((prevInsn == null || prevInsn.Offset >= label.Key) ||
|
||||||
|
_instructions[min].Offset < label.Key)
|
||||||
|
_log.Error($"Label {label.Value} wanted {label.Key} but instruction is at {_instructions[min].Offset}. Previous instruction is at {prevInsn?.Offset ?? -1}");
|
||||||
|
#endif
|
||||||
_instructions[min]?.Labels?.Add(label.Value);
|
_instructions[min]?.Labels?.Add(label.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
public void CheckIntegrity()
|
||||||
|
{
|
||||||
|
var entryStackCount = new Dictionary<MsilLabel, Dictionary<MsilInstruction, int>>();
|
||||||
|
var currentStackSize = 0;
|
||||||
|
foreach (MsilInstruction insn in _instructions)
|
||||||
|
{
|
||||||
|
// I don't want to deal with this, so I won't
|
||||||
|
if (insn.OpCode == OpCodes.Br || insn.OpCode == OpCodes.Br_S || insn.OpCode == OpCodes.Jmp ||
|
||||||
|
insn.OpCode == OpCodes.Leave || insn.OpCode == OpCodes.Leave_S)
|
||||||
|
break;
|
||||||
|
foreach (MsilLabel label in insn.Labels)
|
||||||
|
if (entryStackCount.TryGetValue(label, out Dictionary<MsilInstruction, int> dict))
|
||||||
|
dict.Add(insn, currentStackSize);
|
||||||
|
else
|
||||||
|
(entryStackCount[label] = new Dictionary<MsilInstruction, int>()).Add(insn, currentStackSize);
|
||||||
|
|
||||||
|
currentStackSize += insn.StackChange();
|
||||||
|
|
||||||
|
if (insn.Operand is MsilOperandBrTarget br)
|
||||||
|
if (entryStackCount.TryGetValue(br.Target, out Dictionary<MsilInstruction, int> dict))
|
||||||
|
dict.Add(insn, currentStackSize);
|
||||||
|
else
|
||||||
|
(entryStackCount[br.Target] = new Dictionary<MsilInstruction, int>()).Add(insn, currentStackSize);
|
||||||
|
}
|
||||||
|
foreach (KeyValuePair<MsilLabel, Dictionary<MsilInstruction, int>> label in entryStackCount)
|
||||||
|
{
|
||||||
|
if (label.Value.Values.Aggregate(new HashSet<int>(), (a, b) =>
|
||||||
|
{
|
||||||
|
a.Add(b);
|
||||||
|
return a;
|
||||||
|
}).Count > 1)
|
||||||
|
{
|
||||||
|
_log.Warn($"Label {label.Key} has multiple entry stack counts");
|
||||||
|
foreach (KeyValuePair<MsilInstruction, int> kv in label.Value)
|
||||||
|
_log.Warn($"{kv.Key.Offset:X4} {kv.Key} => {kv.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string ToHumanMsil()
|
public string ToHumanMsil()
|
||||||
{
|
{
|
||||||
return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x}"));
|
return string.Join("\n", _instructions.Select(x => $"IL_{x.Offset:X4}: {x.StackChange():+0;-#} {x}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Dictionary<short, OpCode> OpCodeLookup;
|
private static readonly Dictionary<short, OpCode> OpCodeLookup;
|
||||||
@@ -106,9 +158,9 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
var opcode = (OpCode)field.GetValue(null);
|
var opcode = (OpCode)field.GetValue(null);
|
||||||
if (opcode.OpCodeType != OpCodeType.Nternal)
|
if (opcode.OpCodeType != OpCodeType.Nternal)
|
||||||
OpCodeLookup.Add(opcode.Value, opcode);
|
OpCodeLookup.Add(opcode.Value, opcode);
|
||||||
if ((ushort) opcode.Value > 0xFF)
|
if ((ushort)opcode.Value > 0xFF)
|
||||||
{
|
{
|
||||||
Prefixes.Add((short) ((ushort) opcode.Value >> 8));
|
Prefixes.Add((short)((ushort)opcode.Value >> 8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,18 +9,34 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
{
|
{
|
||||||
internal class MethodTranspiler
|
internal class MethodTranspiler
|
||||||
{
|
{
|
||||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
public static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
internal static void Transpile(MethodBase baseMethod, IEnumerable<MethodInfo> transpilers, LoggingIlGenerator output, Label? retLabel)
|
internal static void Transpile(MethodBase baseMethod, Func<Type, MsilLocal> localCreator, IEnumerable<MethodInfo> transpilers, LoggingIlGenerator output, Label? retLabel)
|
||||||
{
|
{
|
||||||
var context = new MethodContext(baseMethod);
|
var context = new MethodContext(baseMethod);
|
||||||
context.Read();
|
context.Read();
|
||||||
_log.Trace("Input Method:");
|
context.CheckIntegrity();
|
||||||
_log.Trace(context.ToHumanMsil);
|
// _log.Trace("Input Method:");
|
||||||
|
// _log.Trace(context.ToHumanMsil);
|
||||||
|
|
||||||
var methodContent = (IEnumerable<MsilInstruction>) context.Instructions;
|
var methodContent = (IEnumerable<MsilInstruction>)context.Instructions;
|
||||||
foreach (var transpiler in transpilers)
|
foreach (MethodInfo transpiler in transpilers)
|
||||||
methodContent = (IEnumerable<MsilInstruction>)transpiler.Invoke(null, new object[] { methodContent });
|
{
|
||||||
|
var paramList = new List<object>();
|
||||||
|
foreach (var parameter in transpiler.GetParameters())
|
||||||
|
{
|
||||||
|
if (parameter.Name.Equals("__methodBody"))
|
||||||
|
paramList.Add(baseMethod.GetMethodBody());
|
||||||
|
else if (parameter.Name.Equals("__localCreator"))
|
||||||
|
paramList.Add(localCreator);
|
||||||
|
else if (parameter.ParameterType == typeof(IEnumerable<MsilInstruction>))
|
||||||
|
paramList.Add(methodContent);
|
||||||
|
else
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Bad transpiler parameter type {parameter.ParameterType.FullName} {parameter.Name}");
|
||||||
|
}
|
||||||
|
methodContent = (IEnumerable<MsilInstruction>)transpiler.Invoke(null, paramList.ToArray());
|
||||||
|
}
|
||||||
methodContent = FixBranchAndReturn(methodContent, retLabel);
|
methodContent = FixBranchAndReturn(methodContent, retLabel);
|
||||||
foreach (var k in methodContent)
|
foreach (var k in methodContent)
|
||||||
k.Emit(output);
|
k.Emit(output);
|
||||||
@@ -28,21 +44,24 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
|
|
||||||
private static IEnumerable<MsilInstruction> FixBranchAndReturn(IEnumerable<MsilInstruction> insn, Label? retTarget)
|
private static IEnumerable<MsilInstruction> FixBranchAndReturn(IEnumerable<MsilInstruction> insn, Label? retTarget)
|
||||||
{
|
{
|
||||||
foreach (var i in insn)
|
foreach (MsilInstruction i in insn)
|
||||||
{
|
{
|
||||||
if (retTarget.HasValue && i.OpCode == OpCodes.Ret)
|
if (retTarget.HasValue && i.OpCode == OpCodes.Ret)
|
||||||
{
|
{
|
||||||
var j = new MsilInstruction(OpCodes.Br);
|
MsilInstruction j = new MsilInstruction(OpCodes.Br).InlineTarget(new MsilLabel(retTarget.Value));
|
||||||
((MsilOperandBrTarget)j.Operand).Target = new MsilLabel(retTarget.Value);
|
foreach (MsilLabel l in i.Labels)
|
||||||
|
j.Labels.Add(l);
|
||||||
|
_log.Trace($"Replacing {i} with {j}");
|
||||||
yield return j;
|
yield return j;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode))
|
else if (_opcodeReplaceRule.TryGetValue(i.OpCode, out OpCode replaceOpcode))
|
||||||
{
|
{
|
||||||
yield return new MsilInstruction(replaceOpcode) { Operand = i.Operand };
|
var result = i.CopyWith(replaceOpcode);
|
||||||
continue;
|
_log.Trace($"Replacing {i} with {result}");
|
||||||
|
yield return result;
|
||||||
}
|
}
|
||||||
yield return i;
|
else
|
||||||
|
yield return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +75,13 @@ namespace Torch.Managers.PatchManager.Transpile
|
|||||||
if (opcode.OperandType == OperandType.ShortInlineBrTarget &&
|
if (opcode.OperandType == OperandType.ShortInlineBrTarget &&
|
||||||
opcode.Name.EndsWith(".s", StringComparison.OrdinalIgnoreCase))
|
opcode.Name.EndsWith(".s", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var other = (OpCode?) typeof(OpCodes).GetField(field.Name.Substring(0, field.Name.Length - 2),
|
var other = (OpCode?)typeof(OpCodes).GetField(field.Name.Substring(0, field.Name.Length - 2),
|
||||||
BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
|
BindingFlags.Static | BindingFlags.Public)?.GetValue(null);
|
||||||
if (other.HasValue && other.Value.OperandType == OperandType.InlineBrTarget)
|
if (other.HasValue && other.Value.OperandType == OperandType.InlineBrTarget)
|
||||||
_opcodeReplaceRule.Add(opcode, other.Value);
|
_opcodeReplaceRule.Add(opcode, other.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_opcodeReplaceRule[OpCodes.Leave_S] = OpCodes.Leave;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
196
Torch/Managers/PluginManager.cs
Normal file
196
Torch/Managers/PluginManager.cs
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Torch.API;
|
||||||
|
using Torch.API.Managers;
|
||||||
|
using Torch.API.Plugins;
|
||||||
|
using Torch.API.Session;
|
||||||
|
using Torch.Commands;
|
||||||
|
using VRage.Collections;
|
||||||
|
|
||||||
|
namespace Torch.Managers
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public class PluginManager : Manager, IPluginManager
|
||||||
|
{
|
||||||
|
private static Logger _log = LogManager.GetLogger(nameof(PluginManager));
|
||||||
|
public readonly string PluginDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
|
||||||
|
[Dependency]
|
||||||
|
private UpdateManager _updateManager;
|
||||||
|
[Dependency(Optional = true)]
|
||||||
|
private ITorchSessionManager _sessionManager;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IList<ITorchPlugin> Plugins { get; } = new ObservableList<ITorchPlugin>();
|
||||||
|
|
||||||
|
public event Action<ITorchPlugin> PluginLoaded;
|
||||||
|
public event Action<IList<ITorchPlugin>> PluginsLoaded;
|
||||||
|
|
||||||
|
public PluginManager(ITorchBase torchInstance) : base(torchInstance)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(PluginDir))
|
||||||
|
Directory.CreateDirectory(PluginDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates loaded plugins in parallel.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdatePlugins()
|
||||||
|
{
|
||||||
|
foreach (var plugin in Plugins)
|
||||||
|
plugin.Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Action<ITorchPlugin> _attachCommandsHandler = null;
|
||||||
|
|
||||||
|
private void SessionStateChanged(ITorchSession session, TorchSessionState newState)
|
||||||
|
{
|
||||||
|
var cmdManager = session.Managers.GetManager<CommandManager>();
|
||||||
|
if (cmdManager == null)
|
||||||
|
return;
|
||||||
|
switch (newState)
|
||||||
|
{
|
||||||
|
case TorchSessionState.Loaded:
|
||||||
|
if (_attachCommandsHandler != null)
|
||||||
|
PluginLoaded -= _attachCommandsHandler;
|
||||||
|
_attachCommandsHandler = (x) => cmdManager.RegisterPluginCommands(x);
|
||||||
|
PluginLoaded += _attachCommandsHandler;
|
||||||
|
foreach (ITorchPlugin plugin in Plugins)
|
||||||
|
cmdManager.RegisterPluginCommands(plugin);
|
||||||
|
break;
|
||||||
|
case TorchSessionState.Unloading:
|
||||||
|
if (_attachCommandsHandler != null)
|
||||||
|
{
|
||||||
|
PluginLoaded -= _attachCommandsHandler;
|
||||||
|
_attachCommandsHandler = null;
|
||||||
|
}
|
||||||
|
foreach (ITorchPlugin plugin in Plugins)
|
||||||
|
{
|
||||||
|
// cmdMgr?.UnregisterPluginCommands(plugin);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TorchSessionState.Loading:
|
||||||
|
case TorchSessionState.Unloaded:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(newState), newState, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prepares the plugin manager for loading.
|
||||||
|
/// </summary>
|
||||||
|
public override void Attach()
|
||||||
|
{
|
||||||
|
if (_sessionManager != null)
|
||||||
|
_sessionManager.SessionStateChanged += SessionStateChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unloads all plugins.
|
||||||
|
/// </summary>
|
||||||
|
public override void Detach()
|
||||||
|
{
|
||||||
|
if (_sessionManager != null)
|
||||||
|
_sessionManager.SessionStateChanged -= SessionStateChanged;
|
||||||
|
foreach (var plugin in Plugins)
|
||||||
|
plugin.Dispose();
|
||||||
|
|
||||||
|
Plugins.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DownloadPlugins()
|
||||||
|
{
|
||||||
|
var folders = Directory.GetDirectories(PluginDir);
|
||||||
|
var taskList = new List<Task>();
|
||||||
|
|
||||||
|
//Copy list because we don't want to modify the config.
|
||||||
|
var toDownload = Torch.Config.Plugins.ToList();
|
||||||
|
|
||||||
|
foreach (var folder in folders)
|
||||||
|
{
|
||||||
|
var manifestPath = Path.Combine(folder, "manifest.xml");
|
||||||
|
if (!File.Exists(manifestPath))
|
||||||
|
{
|
||||||
|
_log.Debug($"No manifest in {folder}, skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var manifest = PluginManifest.Load(manifestPath);
|
||||||
|
toDownload.RemoveAll(x => string.Compare(manifest.Repository, x, StringComparison.InvariantCultureIgnoreCase) == 0);
|
||||||
|
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var repository in toDownload)
|
||||||
|
{
|
||||||
|
var manifest = new PluginManifest { Repository = repository, Version = "0.0" };
|
||||||
|
taskList.Add(_updateManager.CheckAndUpdatePlugin(manifest));
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.WaitAll(taskList.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void LoadPlugins()
|
||||||
|
{
|
||||||
|
if (Torch.Config.ShouldUpdatePlugins)
|
||||||
|
DownloadPlugins();
|
||||||
|
else
|
||||||
|
_log.Warn("Automatic plugin updates are disabled.");
|
||||||
|
|
||||||
|
_log.Info("Loading plugins");
|
||||||
|
var dlls = Directory.GetFiles(PluginDir, "*.dll", SearchOption.AllDirectories);
|
||||||
|
foreach (var dllPath in dlls)
|
||||||
|
{
|
||||||
|
_log.Info($"Loading plugin {dllPath}");
|
||||||
|
var asm = Assembly.UnsafeLoadFrom(dllPath);
|
||||||
|
|
||||||
|
foreach (var type in asm.GetExportedTypes())
|
||||||
|
{
|
||||||
|
if (type.GetInterfaces().Contains(typeof(ITorchPlugin)))
|
||||||
|
{
|
||||||
|
if (type.GetCustomAttribute<PluginAttribute>() == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var plugin = (TorchPluginBase)Activator.CreateInstance(type);
|
||||||
|
if (plugin.Id == default(Guid))
|
||||||
|
throw new TypeLoadException($"Plugin '{type.FullName}' is missing a {nameof(PluginAttribute)}");
|
||||||
|
|
||||||
|
_log.Info($"Loading plugin {plugin.Name} ({plugin.Version})");
|
||||||
|
plugin.StoragePath = Torch.Config.InstancePath;
|
||||||
|
Plugins.Add(plugin);
|
||||||
|
PluginLoaded?.Invoke(plugin);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Error($"Error loading plugin '{type.FullName}'");
|
||||||
|
_log.Error(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugins.ForEach(p => p.Init(Torch));
|
||||||
|
PluginsLoaded?.Invoke(Plugins.ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<ITorchPlugin> GetEnumerator()
|
||||||
|
{
|
||||||
|
return Plugins.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator()
|
||||||
|
{
|
||||||
|
return GetEnumerator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -45,5 +45,20 @@ namespace Torch.Session
|
|||||||
{
|
{
|
||||||
Managers.Detach();
|
Managers.Detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TorchSessionState _state = TorchSessionState.Loading;
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public TorchSessionState State
|
||||||
|
{
|
||||||
|
get => _state;
|
||||||
|
internal set
|
||||||
|
{
|
||||||
|
_state = value;
|
||||||
|
StateChanged?.Invoke(this, _state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event TorchSessionStateChangedDel StateChanged;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -21,6 +21,9 @@ namespace Torch.Session
|
|||||||
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
private TorchSession _currentSession;
|
private TorchSession _currentSession;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public event TorchSessionStateChangedDel SessionStateChanged;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ITorchSession CurrentSession => _currentSession;
|
public ITorchSession CurrentSession => _currentSession;
|
||||||
|
|
||||||
@@ -46,48 +49,130 @@ namespace Torch.Session
|
|||||||
return _factories.Remove(factory);
|
return _factories.Remove(factory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Session events
|
||||||
|
|
||||||
|
private void SetState(TorchSessionState state)
|
||||||
|
{
|
||||||
|
if (_currentSession == null)
|
||||||
|
return;
|
||||||
|
_currentSession.State = state;
|
||||||
|
SessionStateChanged?.Invoke(_currentSession, _currentSession.State);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SessionLoading()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_currentSession != null)
|
||||||
|
{
|
||||||
|
_log.Warn($"Override old torch session {_currentSession.KeenSession.Name}");
|
||||||
|
_currentSession.Detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
_log.Info($"Starting new torch session for {MySession.Static.Name}");
|
||||||
|
|
||||||
|
_currentSession = new TorchSession(Torch, MySession.Static);
|
||||||
|
SetState(TorchSessionState.Loading);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Error(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SessionLoaded()
|
private void SessionLoaded()
|
||||||
{
|
{
|
||||||
if (_currentSession != null)
|
try
|
||||||
{
|
{
|
||||||
_log.Warn($"Override old torch session {_currentSession.KeenSession.Name}");
|
if (_currentSession == null)
|
||||||
_currentSession.Detach();
|
{
|
||||||
|
_log.Warn("Session loaded event occurred when we don't have a session.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (SessionManagerFactoryDel factory in _factories)
|
||||||
|
{
|
||||||
|
IManager manager = factory(CurrentSession);
|
||||||
|
if (manager != null)
|
||||||
|
CurrentSession.Managers.AddManager(manager);
|
||||||
|
}
|
||||||
|
(CurrentSession as TorchSession)?.Attach();
|
||||||
|
SetState(TorchSessionState.Loaded);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Error(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_log.Info($"Starting new torch session for {MySession.Static.Name}");
|
private void SessionUnloading()
|
||||||
_currentSession = new TorchSession(Torch, MySession.Static);
|
{
|
||||||
foreach (SessionManagerFactoryDel factory in _factories)
|
try
|
||||||
{
|
{
|
||||||
IManager manager = factory(CurrentSession);
|
if (_currentSession == null)
|
||||||
if (manager != null)
|
{
|
||||||
CurrentSession.Managers.AddManager(manager);
|
_log.Warn("Session unloading event occurred when we don't have a session.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SetState(TorchSessionState.Unloading);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Error(e);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
(CurrentSession as TorchSession)?.Attach();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SessionUnloaded()
|
private void SessionUnloaded()
|
||||||
{
|
{
|
||||||
if (_currentSession == null)
|
try
|
||||||
return;
|
{
|
||||||
_log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}");
|
if (_currentSession == null)
|
||||||
_currentSession.Detach();
|
{
|
||||||
_currentSession = null;
|
_log.Warn("Session unloading event occurred when we don't have a session.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_log.Info($"Unloading torch session for {_currentSession.KeenSession.Name}");
|
||||||
|
SetState(TorchSessionState.Unloaded);
|
||||||
|
_currentSession.Detach();
|
||||||
|
_currentSession = null;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log.Error(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Attach()
|
public override void Attach()
|
||||||
{
|
{
|
||||||
|
MySession.OnLoading += SessionLoading;
|
||||||
MySession.AfterLoading += SessionLoaded;
|
MySession.AfterLoading += SessionLoaded;
|
||||||
|
MySession.OnUnloading += SessionUnloading;
|
||||||
MySession.OnUnloaded += SessionUnloaded;
|
MySession.OnUnloaded += SessionUnloaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void Detach()
|
public override void Detach()
|
||||||
{
|
{
|
||||||
_currentSession?.Detach();
|
MySession.OnLoading -= SessionLoading;
|
||||||
_currentSession = null;
|
|
||||||
MySession.AfterLoading -= SessionLoaded;
|
MySession.AfterLoading -= SessionLoaded;
|
||||||
|
MySession.OnUnloading -= SessionUnloading;
|
||||||
MySession.OnUnloaded -= SessionUnloaded;
|
MySession.OnUnloaded -= SessionUnloaded;
|
||||||
|
|
||||||
|
if (_currentSession != null)
|
||||||
|
{
|
||||||
|
if (_currentSession.State == TorchSessionState.Loaded)
|
||||||
|
SetState(TorchSessionState.Unloading);
|
||||||
|
if (_currentSession.State == TorchSessionState.Unloading)
|
||||||
|
SetState(TorchSessionState.Unloaded);
|
||||||
|
_currentSession.Detach();
|
||||||
|
_currentSession = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -154,17 +154,22 @@
|
|||||||
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
<Compile Include="..\Versioning\AssemblyVersion.cs">
|
||||||
<Link>Properties\AssemblyVersion.cs</Link>
|
<Link>Properties\AssemblyVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="ChatMessage.cs" />
|
|
||||||
<Compile Include="Collections\ObservableList.cs" />
|
<Compile Include="Collections\ObservableList.cs" />
|
||||||
|
<Compile Include="Managers\ChatManager\ChatManagerClient.cs" />
|
||||||
|
<Compile Include="Managers\ChatManager\ChatManagerServer.cs" />
|
||||||
<Compile Include="Extensions\DispatcherExtensions.cs" />
|
<Compile Include="Extensions\DispatcherExtensions.cs" />
|
||||||
<Compile Include="Extensions\ICollectionExtensions.cs" />
|
<Compile Include="Extensions\ICollectionExtensions.cs" />
|
||||||
<Compile Include="Managers\DependencyManager.cs" />
|
<Compile Include="Managers\DependencyManager.cs" />
|
||||||
|
<Compile Include="Managers\KeenLogManager.cs" />
|
||||||
<Compile Include="Managers\PatchManager\AssemblyMemory.cs" />
|
<Compile Include="Managers\PatchManager\AssemblyMemory.cs" />
|
||||||
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
|
<Compile Include="Managers\PatchManager\DecoratedMethod.cs" />
|
||||||
<Compile Include="Managers\PatchManager\EmitExtensions.cs" />
|
<Compile Include="Managers\PatchManager\EmitExtensions.cs" />
|
||||||
<Compile Include="Managers\PatchManager\MSIL\ITokenResolver.cs" />
|
<Compile Include="Managers\PatchManager\MSIL\ITokenResolver.cs" />
|
||||||
<Compile Include="Managers\PatchManager\MSIL\MsilInstruction.cs" />
|
<Compile Include="Managers\PatchManager\MSIL\MsilInstruction.cs" />
|
||||||
|
<Compile Include="Managers\PatchManager\MSIL\MsilInstructionExtensions.cs" />
|
||||||
<Compile Include="Managers\PatchManager\MSIL\MsilLabel.cs" />
|
<Compile Include="Managers\PatchManager\MSIL\MsilLabel.cs" />
|
||||||
|
<Compile Include="Managers\PatchManager\MSIL\MsilArgument.cs" />
|
||||||
|
<Compile Include="Managers\PatchManager\MSIL\MsilLocal.cs" />
|
||||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperand.cs" />
|
<Compile Include="Managers\PatchManager\MSIL\MsilOperand.cs" />
|
||||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperandBrTarget.cs" />
|
<Compile Include="Managers\PatchManager\MSIL\MsilOperandBrTarget.cs" />
|
||||||
<Compile Include="Managers\PatchManager\MSIL\MsilOperandInline.cs" />
|
<Compile Include="Managers\PatchManager\MSIL\MsilOperandInline.cs" />
|
||||||
@@ -192,13 +197,12 @@
|
|||||||
<Compile Include="Commands\Permissions\PermissionAttribute.cs" />
|
<Compile Include="Commands\Permissions\PermissionAttribute.cs" />
|
||||||
<Compile Include="Commands\Permissions\PermissonsSystem.cs" />
|
<Compile Include="Commands\Permissions\PermissonsSystem.cs" />
|
||||||
<Compile Include="Commands\TorchCommands.cs" />
|
<Compile Include="Commands\TorchCommands.cs" />
|
||||||
<Compile Include="Managers\ChatManager.cs" />
|
|
||||||
<Compile Include="Managers\EntityManager.cs" />
|
<Compile Include="Managers\EntityManager.cs" />
|
||||||
<Compile Include="Managers\FilesystemManager.cs" />
|
<Compile Include="Managers\FilesystemManager.cs" />
|
||||||
<Compile Include="Managers\Manager.cs" />
|
<Compile Include="Managers\Manager.cs" />
|
||||||
<Compile Include="Managers\NetworkManager\NetworkHandlerBase.cs" />
|
<Compile Include="Managers\NetworkManager\NetworkHandlerBase.cs" />
|
||||||
<Compile Include="Managers\NetworkManager\NetworkManager.cs" />
|
<Compile Include="Managers\NetworkManager\NetworkManager.cs" />
|
||||||
<Compile Include="Managers\MultiplayerManager.cs" />
|
<Compile Include="Managers\MultiplayerManagerBase.cs" />
|
||||||
<Compile Include="Managers\UpdateManager.cs" />
|
<Compile Include="Managers\UpdateManager.cs" />
|
||||||
<Compile Include="Persistent.cs" />
|
<Compile Include="Persistent.cs" />
|
||||||
<Compile Include="Plugins\PluginManifest.cs" />
|
<Compile Include="Plugins\PluginManifest.cs" />
|
||||||
|
@@ -10,6 +10,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
using Sandbox;
|
using Sandbox;
|
||||||
|
using Sandbox.Engine.Multiplayer;
|
||||||
using Sandbox.Game;
|
using Sandbox.Game;
|
||||||
using Sandbox.Game.Multiplayer;
|
using Sandbox.Game.Multiplayer;
|
||||||
using Sandbox.Game.Screens.Helpers;
|
using Sandbox.Game.Screens.Helpers;
|
||||||
@@ -22,10 +23,13 @@ using Torch.API.ModAPI;
|
|||||||
using Torch.API.Session;
|
using Torch.API.Session;
|
||||||
using Torch.Commands;
|
using Torch.Commands;
|
||||||
using Torch.Managers;
|
using Torch.Managers;
|
||||||
|
using Torch.Managers.ChatManager;
|
||||||
|
using Torch.Managers.PatchManager;
|
||||||
using Torch.Utils;
|
using Torch.Utils;
|
||||||
using Torch.Session;
|
using Torch.Session;
|
||||||
using VRage.Collections;
|
using VRage.Collections;
|
||||||
using VRage.FileSystem;
|
using VRage.FileSystem;
|
||||||
|
using VRage.Game;
|
||||||
using VRage.Game.ObjectBuilder;
|
using VRage.Game.ObjectBuilder;
|
||||||
using VRage.ObjectBuilders;
|
using VRage.ObjectBuilders;
|
||||||
using VRage.Plugins;
|
using VRage.Plugins;
|
||||||
@@ -67,18 +71,6 @@ namespace Torch
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
||||||
public IPluginManager Plugins { get; protected set; }
|
public IPluginManager Plugins { get; protected set; }
|
||||||
/// <inheritdoc />
|
|
||||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
|
||||||
public IMultiplayerManager Multiplayer { get; protected set; }
|
|
||||||
/// <inheritdoc />
|
|
||||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
|
||||||
public EntityManager Entities { get; protected set; }
|
|
||||||
/// <inheritdoc />
|
|
||||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
|
||||||
public INetworkManager Network { get; protected set; }
|
|
||||||
/// <inheritdoc />
|
|
||||||
[Obsolete("Use GetManager<T>() or the [Dependency] attribute.")]
|
|
||||||
public CommandManager Commands { get; protected set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ITorchSession CurrentSession => Managers?.GetManager<ITorchSessionManager>()?.CurrentSession;
|
public ITorchSession CurrentSession => Managers?.GetManager<ITorchSessionManager>()?.CurrentSession;
|
||||||
@@ -120,31 +112,38 @@ namespace Torch
|
|||||||
Managers = new DependencyManager();
|
Managers = new DependencyManager();
|
||||||
|
|
||||||
Plugins = new PluginManager(this);
|
Plugins = new PluginManager(this);
|
||||||
Multiplayer = new MultiplayerManager(this);
|
|
||||||
Entities = new EntityManager(this);
|
|
||||||
Network = new NetworkManager(this);
|
|
||||||
Commands = new CommandManager(this);
|
|
||||||
|
|
||||||
Managers.AddManager(new TorchSessionManager(this));
|
var sessionManager = new TorchSessionManager(this);
|
||||||
|
sessionManager.AddFactory((x) => MyMultiplayer.Static?.SyncLayer != null ? new NetworkManager(this) : null);
|
||||||
|
sessionManager.AddFactory((x) => Sync.IsServer ? new ChatManagerServer(this) : new ChatManagerClient(this));
|
||||||
|
sessionManager.AddFactory((x) => Sync.IsServer ? new CommandManager(this) : null);
|
||||||
|
sessionManager.AddFactory((x) => new EntityManager(this));
|
||||||
|
|
||||||
|
Managers.AddManager(sessionManager);
|
||||||
|
var patcher = new PatchManager(this);
|
||||||
|
GameStateInjector.Inject(patcher.AcquireContext());
|
||||||
|
Managers.AddManager(patcher);
|
||||||
|
// Managers.AddManager(new KeenLogManager(this));
|
||||||
Managers.AddManager(new FilesystemManager(this));
|
Managers.AddManager(new FilesystemManager(this));
|
||||||
Managers.AddManager(new UpdateManager(this));
|
Managers.AddManager(new UpdateManager(this));
|
||||||
Managers.AddManager(Network);
|
|
||||||
Managers.AddManager(Commands);
|
|
||||||
Managers.AddManager(Plugins);
|
Managers.AddManager(Plugins);
|
||||||
Managers.AddManager(Multiplayer);
|
GameStateChanged += (game, state) =>
|
||||||
Managers.AddManager(Entities);
|
{
|
||||||
Managers.AddManager(new ChatManager(this));
|
if (state != TorchGameState.Created)
|
||||||
|
return;
|
||||||
|
// At this point flush the patches; it's safe.
|
||||||
|
patcher.Commit();
|
||||||
|
};
|
||||||
TorchAPI.Instance = this;
|
TorchAPI.Instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
[Obsolete("Prefer using Managers.GetManager for global managers")]
|
||||||
public T GetManager<T>() where T : class, IManager
|
public T GetManager<T>() where T : class, IManager
|
||||||
{
|
{
|
||||||
return Managers.GetManager<T>();
|
return Managers.GetManager<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
[Obsolete("Prefer using Managers.AddManager for global managers")]
|
||||||
public bool AddManager<T>(T manager) where T : class, IManager
|
public bool AddManager<T>(T manager) where T : class, IManager
|
||||||
{
|
{
|
||||||
return Managers.AddManager(manager);
|
return Managers.AddManager(manager);
|
||||||
@@ -254,10 +253,13 @@ namespace Torch
|
|||||||
|
|
||||||
Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
|
Debug.Assert(MyPerGameSettings.BasicGameInfo.GameVersion != null, "MyPerGameSettings.BasicGameInfo.GameVersion != null");
|
||||||
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));
|
GameVersion = new Version(new MyVersion(MyPerGameSettings.BasicGameInfo.GameVersion.Value).FormattedText.ToString().Replace("_", "."));
|
||||||
try { Console.Title = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}"; }
|
try
|
||||||
|
{
|
||||||
|
Console.Title = $"{Config.InstanceName} - Torch {TorchVersion}, SE {GameVersion}";
|
||||||
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
//Running as service
|
// Running as service
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@@ -275,6 +277,7 @@ namespace Torch
|
|||||||
MySession.OnUnloading += OnSessionUnloading;
|
MySession.OnUnloading += OnSessionUnloading;
|
||||||
MySession.OnUnloaded += OnSessionUnloaded;
|
MySession.OnUnloaded += OnSessionUnloaded;
|
||||||
RegisterVRagePlugin();
|
RegisterVRagePlugin();
|
||||||
|
Managers.GetManager<PluginManager>().LoadPlugins();
|
||||||
Managers.Attach();
|
Managers.Attach();
|
||||||
_init = true;
|
_init = true;
|
||||||
}
|
}
|
||||||
@@ -282,25 +285,57 @@ namespace Torch
|
|||||||
private void OnSessionLoading()
|
private void OnSessionLoading()
|
||||||
{
|
{
|
||||||
Log.Debug("Session loading");
|
Log.Debug("Session loading");
|
||||||
SessionLoading?.Invoke();
|
try
|
||||||
|
{
|
||||||
|
SessionLoading?.Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSessionLoaded()
|
private void OnSessionLoaded()
|
||||||
{
|
{
|
||||||
Log.Debug("Session loaded");
|
Log.Debug("Session loaded");
|
||||||
SessionLoaded?.Invoke();
|
try
|
||||||
|
{
|
||||||
|
SessionLoaded?.Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSessionUnloading()
|
private void OnSessionUnloading()
|
||||||
{
|
{
|
||||||
Log.Debug("Session unloading");
|
Log.Debug("Session unloading");
|
||||||
SessionUnloading?.Invoke();
|
try
|
||||||
|
{
|
||||||
|
SessionUnloading?.Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSessionUnloaded()
|
private void OnSessionUnloaded()
|
||||||
{
|
{
|
||||||
Log.Debug("Session unloaded");
|
Log.Debug("Session unloaded");
|
||||||
SessionUnloaded?.Invoke();
|
try
|
||||||
|
{
|
||||||
|
SessionUnloaded?.Invoke();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Error(e);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -357,5 +392,89 @@ namespace Torch
|
|||||||
{
|
{
|
||||||
GetManager<IPluginManager>().UpdatePlugins();
|
GetManager<IPluginManager>().UpdatePlugins();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private TorchGameState _gameState = TorchGameState.Unloaded;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public TorchGameState GameState
|
||||||
|
{
|
||||||
|
get => _gameState;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
_gameState = value;
|
||||||
|
GameStateChanged?.Invoke(MySandboxGame.Static, _gameState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event TorchGameStateChangedDel GameStateChanged;
|
||||||
|
|
||||||
|
#region GameStateInjecting
|
||||||
|
private static class GameStateInjector
|
||||||
|
{
|
||||||
|
#pragma warning disable 649
|
||||||
|
[ReflectedMethodInfo(typeof(MySandboxGame), nameof(MySandboxGame.Dispose))]
|
||||||
|
private static MethodInfo _sandboxGameDispose;
|
||||||
|
[ReflectedMethodInfo(typeof(MySandboxGame), "Initialize")]
|
||||||
|
private static MethodInfo _sandboxGameInit;
|
||||||
|
#pragma warning restore 649
|
||||||
|
|
||||||
|
internal static void Inject(PatchContext target)
|
||||||
|
{
|
||||||
|
ConstructorInfo ctor = typeof(MySandboxGame).GetConstructor(new[] {typeof(string[])});
|
||||||
|
if (ctor == null)
|
||||||
|
throw new ArgumentException("Can't find constructor MySandboxGame(string[])");
|
||||||
|
target.GetPattern(ctor).Prefixes.Add(MethodRef(nameof(PrefixConstructor)));
|
||||||
|
target.GetPattern(ctor).Suffixes.Add(MethodRef(nameof(SuffixConstructor)));
|
||||||
|
target.GetPattern(_sandboxGameInit).Prefixes.Add(MethodRef(nameof(PrefixInit)));
|
||||||
|
target.GetPattern(_sandboxGameInit).Suffixes.Add(MethodRef(nameof(SuffixInit)));
|
||||||
|
target.GetPattern(_sandboxGameDispose).Prefixes.Add(MethodRef(nameof(PrefixDispose)));
|
||||||
|
target.GetPattern(_sandboxGameDispose).Suffixes.Add(MethodRef(nameof(SuffixDispose)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodInfo MethodRef(string name)
|
||||||
|
{
|
||||||
|
return typeof(GameStateInjector).GetMethod(name,
|
||||||
|
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrefixConstructor()
|
||||||
|
{
|
||||||
|
if (Instance is TorchBase tb)
|
||||||
|
tb.GameState = TorchGameState.Creating;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SuffixConstructor()
|
||||||
|
{
|
||||||
|
if (Instance is TorchBase tb)
|
||||||
|
tb.GameState = TorchGameState.Created;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrefixInit()
|
||||||
|
{
|
||||||
|
if (Instance is TorchBase tb)
|
||||||
|
tb.GameState = TorchGameState.Loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SuffixInit()
|
||||||
|
{
|
||||||
|
if (Instance is TorchBase tb)
|
||||||
|
tb.GameState = TorchGameState.Loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrefixDispose()
|
||||||
|
{
|
||||||
|
if (Instance is TorchBase tb)
|
||||||
|
tb.GameState = TorchGameState.Unloading;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SuffixDispose()
|
||||||
|
{
|
||||||
|
if (Instance is TorchBase tb)
|
||||||
|
tb.GameState = TorchGameState.Unloaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ using System.Linq.Expressions;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
using Sandbox.Engine.Multiplayer;
|
using Sandbox.Engine.Multiplayer;
|
||||||
using Torch.API;
|
using Torch.API;
|
||||||
|
|
||||||
@@ -34,6 +35,81 @@ namespace Torch.Utils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region MemberInfoAttributes
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that this field should contain the <see cref="System.Reflection.FieldInfo"/> instance for the given field.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedFieldInfoAttribute : ReflectedMemberAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a reflected field info attribute using the given type and name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type that contains the member</param>
|
||||||
|
/// <param name="name">Name of the member</param>
|
||||||
|
public ReflectedFieldInfoAttribute(Type type, string name)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that this field should contain the <see cref="System.Reflection.MethodInfo"/> instance for the given method.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedMethodInfoAttribute : ReflectedMemberAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a reflected method info attribute using the given type and name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type that contains the member</param>
|
||||||
|
/// <param name="name">Name of the member</param>
|
||||||
|
public ReflectedMethodInfoAttribute(Type type, string name)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Expected parameters of this method, or null if any parameters are accepted.
|
||||||
|
/// </summary>
|
||||||
|
public Type[] Parameters { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assembly qualified names of <see cref="Parameters"/>
|
||||||
|
/// </summary>
|
||||||
|
public string[] ParameterNames
|
||||||
|
{
|
||||||
|
get => Parameters.Select(x => x.AssemblyQualifiedName).ToArray();
|
||||||
|
set => Parameters = value?.Select(x => x == null ? null : Type.GetType(x)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Expected return type of this method, or null if any return type is accepted.
|
||||||
|
/// </summary>
|
||||||
|
public Type ReturnType { get; set; } = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that this field should contain the <see cref="System.Reflection.PropertyInfo"/> instance for the given property.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedPropertyInfoAttribute : ReflectedMemberAttribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a reflected property info attribute using the given type and name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">Type that contains the member</param>
|
||||||
|
/// <param name="name">Name of the member</param>
|
||||||
|
public ReflectedPropertyInfoAttribute(Type type, string name)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region FieldPropGetSet
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that this field should contain a delegate capable of retrieving the value of a field.
|
/// Indicates that this field should contain a delegate capable of retrieving the value of a field.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -81,7 +157,9 @@ namespace Torch.Utils
|
|||||||
public class ReflectedSetterAttribute : ReflectedMemberAttribute
|
public class ReflectedSetterAttribute : ReflectedMemberAttribute
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Invoker
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates that this field should contain a delegate capable of invoking an instance method.
|
/// Indicates that this field should contain a delegate capable of invoking an instance method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -138,14 +216,193 @@ namespace Torch.Utils
|
|||||||
public class ReflectedStaticMethodAttribute : ReflectedMethodAttribute
|
public class ReflectedStaticMethodAttribute : ReflectedMethodAttribute
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region EventReplacer
|
||||||
|
/// <summary>
|
||||||
|
/// Instance of statefully replacing and restoring the callbacks of an event.
|
||||||
|
/// </summary>
|
||||||
|
public class ReflectedEventReplacer
|
||||||
|
{
|
||||||
|
private const BindingFlags BindFlagAll = BindingFlags.Static |
|
||||||
|
BindingFlags.Instance |
|
||||||
|
BindingFlags.Public |
|
||||||
|
BindingFlags.NonPublic;
|
||||||
|
|
||||||
|
private object _instance;
|
||||||
|
private Func<IEnumerable<Delegate>> _backingStoreReader;
|
||||||
|
private Action<Delegate> _callbackAdder;
|
||||||
|
private Action<Delegate> _callbackRemover;
|
||||||
|
private readonly ReflectedEventReplaceAttribute _attributes;
|
||||||
|
private readonly HashSet<Delegate> _registeredCallbacks = new HashSet<Delegate>();
|
||||||
|
private readonly MethodInfo _targetMethodInfo;
|
||||||
|
|
||||||
|
internal ReflectedEventReplacer(ReflectedEventReplaceAttribute attr)
|
||||||
|
{
|
||||||
|
_attributes = attr;
|
||||||
|
FieldInfo backingStore = GetEventBackingField(attr.EventName, attr.EventDeclaringType);
|
||||||
|
if (backingStore == null)
|
||||||
|
throw new ArgumentException($"Unable to find backing field for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
|
||||||
|
EventInfo evtInfo = ReflectedManager.GetFieldPropRecursive(attr.EventDeclaringType, attr.EventName, BindFlagAll, (a, b, c) => a.GetEvent(b, c));
|
||||||
|
if (evtInfo == null)
|
||||||
|
throw new ArgumentException($"Unable to find event info for event {attr.EventDeclaringType.FullName}#{attr.EventName}");
|
||||||
|
_backingStoreReader = () => GetEventsInternal(_instance, backingStore);
|
||||||
|
_callbackAdder = (x) => evtInfo.AddEventHandler(_instance, x);
|
||||||
|
_callbackRemover = (x) => evtInfo.RemoveEventHandler(_instance, x);
|
||||||
|
if (attr.TargetParameters == null)
|
||||||
|
{
|
||||||
|
_targetMethodInfo = attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll);
|
||||||
|
if (_targetMethodInfo == null)
|
||||||
|
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName} to replace");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_targetMethodInfo =
|
||||||
|
attr.TargetDeclaringType.GetMethod(attr.TargetName, BindFlagAll, null, attr.TargetParameters, null);
|
||||||
|
if (_targetMethodInfo == null)
|
||||||
|
throw new ArgumentException($"Unable to find method {attr.TargetDeclaringType.FullName}#{attr.TargetName}){string.Join(", ", attr.TargetParameters.Select(x => x.FullName))}) to replace");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that this replacement can be performed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">The instance to operate on, or null if static</param>
|
||||||
|
/// <returns>true if possible, false if unsuccessful</returns>
|
||||||
|
public bool Test(object instance)
|
||||||
|
{
|
||||||
|
_instance = instance;
|
||||||
|
_registeredCallbacks.Clear();
|
||||||
|
foreach (Delegate callback in _backingStoreReader.Invoke())
|
||||||
|
if (callback.Method == _targetMethodInfo)
|
||||||
|
_registeredCallbacks.Add(callback);
|
||||||
|
|
||||||
|
return _registeredCallbacks.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Delegate _newCallback;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the target callback defined in the attribute and replaces it with the provided callback.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newCallback">The new event callback</param>
|
||||||
|
/// <param name="instance">The instance to operate on, or null if static</param>
|
||||||
|
public void Replace(Delegate newCallback, object instance)
|
||||||
|
{
|
||||||
|
_instance = instance;
|
||||||
|
if (_newCallback != null)
|
||||||
|
throw new Exception("Reflected event replacer is in invalid state: Replace when already replaced");
|
||||||
|
_newCallback = newCallback;
|
||||||
|
Test(instance);
|
||||||
|
if (_registeredCallbacks.Count == 0)
|
||||||
|
throw new Exception("Reflected event replacer is in invalid state: Nothing to replace");
|
||||||
|
foreach (Delegate callback in _registeredCallbacks)
|
||||||
|
_callbackRemover.Invoke(callback);
|
||||||
|
_callbackAdder.Invoke(_newCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the callback is currently replaced
|
||||||
|
/// </summary>
|
||||||
|
public bool Replaced => _newCallback != null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the callback added by <see cref="Replace"/> and puts the original callback back.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">The instance to operate on, or null if static</param>
|
||||||
|
public void Restore(object instance)
|
||||||
|
{
|
||||||
|
_instance = instance;
|
||||||
|
if (_newCallback == null)
|
||||||
|
throw new Exception("Reflected event replacer is in invalid state: Restore when not replaced");
|
||||||
|
_callbackRemover.Invoke(_newCallback);
|
||||||
|
foreach (Delegate callback in _registeredCallbacks)
|
||||||
|
_callbackAdder.Invoke(callback);
|
||||||
|
_newCallback = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly string[] _backingFieldForEvent = { "{0}", "<backing_store>{0}" };
|
||||||
|
|
||||||
|
private static FieldInfo GetEventBackingField(string eventName, Type baseType)
|
||||||
|
{
|
||||||
|
FieldInfo eventField = null;
|
||||||
|
Type type = baseType;
|
||||||
|
while (type != null && eventField == null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _backingFieldForEvent.Length && eventField == null; i++)
|
||||||
|
eventField = type.GetField(string.Format(_backingFieldForEvent[i], eventName), BindFlagAll);
|
||||||
|
type = type.BaseType;
|
||||||
|
}
|
||||||
|
return eventField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Delegate> GetEventsInternal(object instance, FieldInfo eventField)
|
||||||
|
{
|
||||||
|
if (eventField.GetValue(instance) is MulticastDelegate eventDel)
|
||||||
|
{
|
||||||
|
foreach (Delegate handle in eventDel.GetInvocationList())
|
||||||
|
yield return handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attribute used to indicate that the the given field, of type <![CDATA[Func<ReflectedEventReplacer>]]>, should be filled with
|
||||||
|
/// a function used to create a new event replacer.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
|
public class ReflectedEventReplaceAttribute : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Type that the event is declared in
|
||||||
|
/// </summary>
|
||||||
|
public Type EventDeclaringType { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the event
|
||||||
|
/// </summary>
|
||||||
|
public string EventName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Type that the method to replace is declared in
|
||||||
|
/// </summary>
|
||||||
|
public Type TargetDeclaringType { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the method to replace
|
||||||
|
/// </summary>
|
||||||
|
public string TargetName { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Optional parameters of the method to replace. Null to ignore.
|
||||||
|
/// </summary>
|
||||||
|
public Type[] TargetParameters { get; set; } = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a reflected event replacer attribute to, for the event defined as eventName in eventDeclaringType,
|
||||||
|
/// replace the method defined as targetName in targetDeclaringType with a custom callback.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventDeclaringType">Type the event is declared in</param>
|
||||||
|
/// <param name="eventName">Name of the event</param>
|
||||||
|
/// <param name="targetDeclaringType">Type the method to remove is declared in</param>
|
||||||
|
/// <param name="targetName">Name of the method to remove</param>
|
||||||
|
public ReflectedEventReplaceAttribute(Type eventDeclaringType, string eventName, Type targetDeclaringType,
|
||||||
|
string targetName)
|
||||||
|
{
|
||||||
|
EventDeclaringType = eventDeclaringType;
|
||||||
|
EventName = eventName;
|
||||||
|
TargetDeclaringType = targetDeclaringType;
|
||||||
|
TargetName = targetName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Automatically calls <see cref="ReflectedManager.Process(Assembly)"/> for every assembly already loaded, and every assembly that is loaded in the future.
|
/// Automatically calls <see cref="ReflectedManager.Process(Assembly)"/> for every assembly already loaded, and every assembly that is loaded in the future.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ReflectedManager
|
public class ReflectedManager
|
||||||
{
|
{
|
||||||
|
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
|
||||||
private static readonly string[] _namespaceBlacklist = new[] {
|
private static readonly string[] _namespaceBlacklist = new[] {
|
||||||
"System", "VRage", "Sandbox", "SpaceEngineers"
|
"System", "VRage", "Sandbox", "SpaceEngineers", "Microsoft"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -181,11 +438,28 @@ namespace Torch.Utils
|
|||||||
{
|
{
|
||||||
if (_processedTypes.Add(t))
|
if (_processedTypes.Add(t))
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(t.Namespace))
|
||||||
|
return;
|
||||||
foreach (string ns in _namespaceBlacklist)
|
foreach (string ns in _namespaceBlacklist)
|
||||||
if (t.FullName.StartsWith(ns))
|
if (t.FullName.StartsWith(ns))
|
||||||
return;
|
return;
|
||||||
foreach (FieldInfo field in t.GetFields(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
foreach (FieldInfo field in t.GetFields(BindingFlags.Static | BindingFlags.Instance |
|
||||||
Process(field);
|
BindingFlags.Public | BindingFlags.NonPublic))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (Process(field))
|
||||||
|
_log?.Trace($"Field {field.DeclaringType?.FullName}#{field.Name} = {field.GetValue(null) ?? "null"}");
|
||||||
|
#else
|
||||||
|
Process(field);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_log?.Error(e.InnerException ?? e, $"Unable to fill {field.DeclaringType?.FullName}#{field.Name}. {(e.InnerException ?? e).Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,37 +481,95 @@ namespace Torch.Utils
|
|||||||
/// <exception cref="ArgumentException">If the field failed to process</exception>
|
/// <exception cref="ArgumentException">If the field failed to process</exception>
|
||||||
public static bool Process(FieldInfo field)
|
public static bool Process(FieldInfo field)
|
||||||
{
|
{
|
||||||
var attr = field.GetCustomAttribute<ReflectedMethodAttribute>();
|
foreach (ReflectedMemberAttribute attr in field.GetCustomAttributes<ReflectedMemberAttribute>())
|
||||||
if (attr != null)
|
|
||||||
{
|
{
|
||||||
if (!field.IsStatic)
|
if (!field.IsStatic)
|
||||||
throw new ArgumentException("Field must be static to be reflected");
|
throw new ArgumentException("Field must be static to be reflected");
|
||||||
ProcessReflectedMethod(field, attr);
|
switch (attr)
|
||||||
return true;
|
|
||||||
}
|
|
||||||
var attr2 = field.GetCustomAttribute<ReflectedGetterAttribute>();
|
|
||||||
if (attr2 != null)
|
|
||||||
{
|
|
||||||
if (!field.IsStatic)
|
|
||||||
throw new ArgumentException("Field must be static to be reflected");
|
|
||||||
ProcessReflectedField(field, attr2);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
var attr3 = field.GetCustomAttribute<ReflectedSetterAttribute>();
|
|
||||||
if (attr3 != null)
|
|
||||||
{
|
|
||||||
if (!field.IsStatic)
|
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Field must be static to be reflected");
|
case ReflectedMethodAttribute rma:
|
||||||
|
ProcessReflectedMethod(field, rma);
|
||||||
|
return true;
|
||||||
|
case ReflectedGetterAttribute rga:
|
||||||
|
ProcessReflectedField(field, rga);
|
||||||
|
return true;
|
||||||
|
case ReflectedSetterAttribute rsa:
|
||||||
|
ProcessReflectedField(field, rsa);
|
||||||
|
return true;
|
||||||
|
case ReflectedFieldInfoAttribute rfia:
|
||||||
|
ProcessReflectedMemberInfo(field, rfia);
|
||||||
|
return true;
|
||||||
|
case ReflectedPropertyInfoAttribute rpia:
|
||||||
|
ProcessReflectedMemberInfo(field, rpia);
|
||||||
|
return true;
|
||||||
|
case ReflectedMethodInfoAttribute rmia:
|
||||||
|
ProcessReflectedMemberInfo(field, rmia);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProcessReflectedField(field, attr3);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var reflectedEventReplacer = field.GetCustomAttribute<ReflectedEventReplaceAttribute>();
|
||||||
|
if (reflectedEventReplacer != null)
|
||||||
|
{
|
||||||
|
if (!field.IsStatic)
|
||||||
|
throw new ArgumentException("Field must be static to be reflected");
|
||||||
|
field.SetValue(null,
|
||||||
|
new Func<ReflectedEventReplacer>(() => new ReflectedEventReplacer(reflectedEventReplacer)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ProcessReflectedMemberInfo(FieldInfo field, ReflectedMemberAttribute attr)
|
||||||
|
{
|
||||||
|
MemberInfo info = null;
|
||||||
|
if (attr.Type == null)
|
||||||
|
throw new ArgumentException("Reflected member info attributes require Type to be defined");
|
||||||
|
if (attr.Name == null)
|
||||||
|
throw new ArgumentException("Reflected member info attributes require Name to be defined");
|
||||||
|
switch (attr)
|
||||||
|
{
|
||||||
|
case ReflectedFieldInfoAttribute rfia:
|
||||||
|
info = GetFieldPropRecursive(rfia.Type, rfia.Name,
|
||||||
|
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
|
||||||
|
(type, name, bindingFlags) => type.GetField(name, bindingFlags));
|
||||||
|
if (info == null)
|
||||||
|
throw new ArgumentException($"Unable to find field {rfia.Type.FullName}#{rfia.Name}");
|
||||||
|
break;
|
||||||
|
case ReflectedPropertyInfoAttribute rpia:
|
||||||
|
info = GetFieldPropRecursive(rpia.Type, rpia.Name,
|
||||||
|
BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public,
|
||||||
|
(type, name, bindingFlags) => type.GetProperty(name, bindingFlags));
|
||||||
|
if (info == null)
|
||||||
|
throw new ArgumentException($"Unable to find property {rpia.Type.FullName}#{rpia.Name}");
|
||||||
|
break;
|
||||||
|
case ReflectedMethodInfoAttribute rmia:
|
||||||
|
if (rmia.Parameters != null)
|
||||||
|
{
|
||||||
|
info = rmia.Type.GetMethod(rmia.Name,
|
||||||
|
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
|
||||||
|
null, CallingConventions.Any, rmia.Parameters, null);
|
||||||
|
if (info == null)
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Unable to find method {rmia.Type.FullName}#{rmia.Name}({string.Join(", ", rmia.Parameters.Select(x => x.FullName))})");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info = rmia.Type.GetMethod(rmia.Name,
|
||||||
|
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
if (info == null)
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Unable to find method {rmia.Type.FullName}#{rmia.Name}");
|
||||||
|
}
|
||||||
|
if (rmia.ReturnType != null && !rmia.ReturnType.IsAssignableFrom(((MethodInfo)info).ReturnType))
|
||||||
|
throw new ArgumentException($"Method {rmia.Type.FullName}#{rmia.Name} has return type {((MethodInfo)info).ReturnType.FullName}, expected {rmia.ReturnType.FullName}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (info == null)
|
||||||
|
throw new ArgumentException($"Unable to find member info for {attr.GetType().Name}[{attr.Type.FullName}#{attr.Name}");
|
||||||
|
field.SetValue(null, info);
|
||||||
|
}
|
||||||
|
|
||||||
private static void ProcessReflectedMethod(FieldInfo field, ReflectedMethodAttribute attr)
|
private static void ProcessReflectedMethod(FieldInfo field, ReflectedMethodAttribute attr)
|
||||||
{
|
{
|
||||||
MethodInfo delegateMethod = field.FieldType.GetMethod("Invoke");
|
MethodInfo delegateMethod = field.FieldType.GetMethod("Invoke");
|
||||||
@@ -307,7 +639,7 @@ namespace Torch.Utils
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static T GetFieldPropRecursive<T>(Type baseType, string name, BindingFlags flags, Func<Type, string, BindingFlags, T> getter) where T : class
|
internal static T GetFieldPropRecursive<T>(Type baseType, string name, BindingFlags flags, Func<Type, string, BindingFlags, T> getter) where T : class
|
||||||
{
|
{
|
||||||
while (baseType != null)
|
while (baseType != null)
|
||||||
{
|
{
|
||||||
@@ -372,7 +704,7 @@ namespace Torch.Utils
|
|||||||
Expression instanceExpr = null;
|
Expression instanceExpr = null;
|
||||||
if (!isStatic)
|
if (!isStatic)
|
||||||
{
|
{
|
||||||
instanceExpr = trueType == paramExp[0].Type ? (Expression) paramExp[0] : Expression.Convert(paramExp[0], trueType);
|
instanceExpr = trueType == paramExp[0].Type ? (Expression)paramExp[0] : Expression.Convert(paramExp[0], trueType);
|
||||||
}
|
}
|
||||||
|
|
||||||
MemberExpression fieldExp = sourceField != null
|
MemberExpression fieldExp = sourceField != null
|
||||||
|
@@ -67,7 +67,7 @@ namespace Torch.Utils
|
|||||||
string assemblyPath = Path.Combine(path, assemblyName + ".dll");
|
string assemblyPath = Path.Combine(path, assemblyName + ".dll");
|
||||||
if (!File.Exists(assemblyPath))
|
if (!File.Exists(assemblyPath))
|
||||||
continue;
|
continue;
|
||||||
_log.Debug("Loading {0} from {1}", assemblyName, SimplifyPath(assemblyPath));
|
_log.Trace("Loading {0} from {1}", assemblyName, SimplifyPath(assemblyPath));
|
||||||
LogManager.Flush();
|
LogManager.Flush();
|
||||||
Assembly asm = Assembly.LoadFrom(assemblyPath);
|
Assembly asm = Assembly.LoadFrom(assemblyPath);
|
||||||
_assemblies.Add(assemblyName, asm);
|
_assemblies.Add(assemblyName, asm);
|
||||||
|
Reference in New Issue
Block a user