@@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.29609.76 | |||
MinimumVisualStudioVersion = 10.0.40219.1 | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLre", "CLre\CLre.csproj", "{E0EEA15D-AB3C-4C73-A000-C49B5AE9EA66}" | |||
EndProject | |||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CLre_server", "CLre_server\CLre_server.csproj", "{89B354CF-C654-4E48-8166-5E20BC6E4836}" | |||
EndProject | |||
Global | |||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | |||
Debug|Any CPU = Debug|Any CPU | |||
@@ -15,6 +17,10 @@ Global | |||
{E0EEA15D-AB3C-4C73-A000-C49B5AE9EA66}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{E0EEA15D-AB3C-4C73-A000-C49B5AE9EA66}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{E0EEA15D-AB3C-4C73-A000-C49B5AE9EA66}.Release|Any CPU.Build.0 = Release|Any CPU | |||
{89B354CF-C654-4E48-8166-5E20BC6E4836}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | |||
{89B354CF-C654-4E48-8166-5E20BC6E4836}.Debug|Any CPU.Build.0 = Debug|Any CPU | |||
{89B354CF-C654-4E48-8166-5E20BC6E4836}.Release|Any CPU.ActiveCfg = Release|Any CPU | |||
{89B354CF-C654-4E48-8166-5E20BC6E4836}.Release|Any CPU.Build.0 = Release|Any CPU | |||
EndGlobalSection | |||
GlobalSection(SolutionProperties) = preSolution | |||
HideSolutionNode = FALSE | |||
@@ -0,0 +1,10 @@ | |||
using Game.DataLoader; | |||
using Svelto.ECS; | |||
namespace CLre_server.API.Engines | |||
{ | |||
public interface ICLreEngine : IQueryingEntitiesEngine, IEngine, IDataAccess | |||
{ | |||
IEntityFactory entityFactory { get; set; } | |||
} | |||
} |
@@ -0,0 +1,61 @@ | |||
using Game.DataLoader; | |||
using HarmonyLib; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
namespace CLre_server.API.Engines | |||
{ | |||
public abstract class ServerEnginePreBuild : ICLreEngine | |||
{ | |||
public ServerEnginePreBuild() | |||
{ | |||
MainGameServer_BuildDeprecatedEngines_Patch.beforeBuildEngines.Add(this); | |||
} | |||
public abstract void Ready(); | |||
public abstract IEntitiesDB entitiesDB { get; set; } | |||
public abstract IEntityFactory entityFactory { get; set; } | |||
public IDataDB dataDB { get; set; } | |||
} | |||
public abstract class ServerEnginePostBuild : ICLreEngine | |||
{ | |||
public ServerEnginePostBuild() | |||
{ | |||
MainGameServer_BuildDeprecatedEngines_Patch.afterBuildEngines.Add(this); | |||
} | |||
public abstract void Ready(); | |||
public abstract IEntitiesDB entitiesDB { get; set; } | |||
public abstract IEntityFactory entityFactory { get; set; } | |||
public IDataDB dataDB { get; set; } | |||
} | |||
[HarmonyPatch(typeof(GameServer.GameFramework.MainGameServer), "BuildDeprecatedEngines")] | |||
class MainGameServer_BuildDeprecatedEngines_Patch | |||
{ | |||
internal static FasterList<ServerEnginePreBuild> beforeBuildEngines = new FasterList<ServerEnginePreBuild>(); | |||
internal static FasterList<ServerEnginePostBuild> afterBuildEngines = new FasterList<ServerEnginePostBuild>(); | |||
[HarmonyPrefix] | |||
public static void BeforeMethodCall(GameServer.GameFramework.MainGameServer __instance, IEntityFactory ____entityFactory) | |||
{ | |||
foreach (ICLreEngine e in beforeBuildEngines) | |||
{ | |||
e.entityFactory = ____entityFactory; | |||
__instance.AddEngine(e); | |||
} | |||
} | |||
[HarmonyPostfix] | |||
public static void AfterMethodCall(GameServer.GameFramework.MainGameServer __instance, IEntityFactory ____entityFactory) | |||
{ | |||
foreach (ICLreEngine e in afterBuildEngines) | |||
{ | |||
e.entityFactory = ____entityFactory; | |||
__instance.AddEngine(e); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,147 @@ | |||
using System; | |||
using System.Reflection; | |||
using GameServer; | |||
using HarmonyLib; | |||
using Svelto.Context; | |||
namespace CLre_server.API.MainServer | |||
{ | |||
public class Server | |||
{ | |||
// static | |||
private static Server _instance = null; | |||
public static Server Instance | |||
{ | |||
get | |||
{ | |||
if (_instance == null) _instance = new Server(); | |||
return _instance; | |||
} | |||
} | |||
// instance events | |||
public event EventHandler<StartingEventArgs> InitStart | |||
{ | |||
add => MainGameServer_Constructor_Patch.preConstructor += value; | |||
remove => MainGameServer_Constructor_Patch.preConstructor -= value; | |||
} | |||
public event EventHandler<StartedEventArgs> InitComplete | |||
{ | |||
add => ServerReadyEngine.serverEngineReady += value; | |||
remove => ServerReadyEngine.serverEngineReady -= value; | |||
} | |||
public event EventHandler<StartedEventArgs> FrameworkReady | |||
{ | |||
add => ServerReadyEngine.serverFrameworkReady += value; | |||
remove => ServerReadyEngine.serverFrameworkReady -= value; | |||
} | |||
public event EventHandler<StopEventArgs> FrameworkExit | |||
{ | |||
add => ServerReadyEngine.serverFrameworkDestroyed += value; | |||
remove => ServerReadyEngine.serverFrameworkDestroyed -= value; | |||
} | |||
// properties | |||
public GameServerSettings GameServerSettings | |||
{ | |||
get => MainGameServer_SetupMods_Patch._gameServerSettings; | |||
set | |||
{ | |||
MainGameServer_SetupMods_Patch._gameServerSettings = value; | |||
Traverse.Create(MainGameServer_Constructor_Patch.mgs).Field<GameServerSettings>("_gameServerSettings").Value = value; | |||
} | |||
} | |||
private Server() | |||
{ | |||
new ServerReadyEngine(); | |||
} | |||
} | |||
[HarmonyPatch] | |||
class MainGameServer_SetupMods_Patch | |||
{ | |||
internal static GameServerSettings _gameServerSettings; | |||
[HarmonyPostfix] | |||
public static void AfterMethodCall(GameServerSettings ____gameServerSettings) | |||
{ | |||
_gameServerSettings = ____gameServerSettings; | |||
} | |||
[HarmonyTargetMethod] | |||
public static MethodBase Target() | |||
{ | |||
return AccessTools.Method("GameServer.GameFramework.MainGameServer:SetupMods"); | |||
} | |||
} | |||
[HarmonyPatch] | |||
class MainGameServer_Constructor_Patch | |||
{ | |||
internal static ICompositionRoot mgs = null; | |||
internal static event EventHandler<StartingEventArgs> preConstructor; | |||
internal static event EventHandler<StartingEventArgs> postConstructor; | |||
[HarmonyPrefix] | |||
public static void BeforeMethodCall() | |||
{ | |||
if (preConstructor != null) preConstructor(null, default(StartingEventArgs)); | |||
} | |||
[HarmonyPostfix] | |||
public static void AfterMethodCall(ICompositionRoot __instance) | |||
{ | |||
mgs = __instance; | |||
if (postConstructor != null) postConstructor(__instance, default(StartingEventArgs)); | |||
} | |||
[HarmonyTargetMethod] | |||
public static MethodBase Target() | |||
{ | |||
return AccessTools.Constructor(AccessTools.TypeByName("GameServer.GameFramework.MainGameServer")); | |||
} | |||
} | |||
[HarmonyPatch(typeof(PhotonNetwork), "ConnectUsingSettings")] | |||
class PhotonNetwork_ConnectUsingSettings_Patch | |||
{ | |||
internal static event EventHandler<StartedEventArgs> preConnect; | |||
internal static event EventHandler<StartedEventArgs> postConnect; | |||
[HarmonyPostfix] | |||
public static void AfterMethodCall(string gameVersion) | |||
{ | |||
if (postConnect != null) postConnect(null, new StartedEventArgs | |||
{ | |||
photonVersion = gameVersion, | |||
photonRegion = PhotonNetwork.CloudRegion, | |||
worldName = MainGameServer_SetupMods_Patch._gameServerSettings.GetWorldName(), | |||
gameGuid = MainGameServer_SetupMods_Patch._gameServerSettings.GetGameGuid(), | |||
}); | |||
} | |||
[HarmonyPrefix] | |||
public static void BeforeMethodCall(string gameVersion) | |||
{ | |||
if (preConnect != null) preConnect(null, new StartedEventArgs | |||
{ | |||
photonVersion = gameVersion, | |||
photonRegion = PhotonNetwork.CloudRegion, | |||
worldName = MainGameServer_SetupMods_Patch._gameServerSettings.GetWorldName(), | |||
gameGuid = MainGameServer_SetupMods_Patch._gameServerSettings.GetGameGuid(), | |||
}); | |||
} | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
using System; | |||
using CLre_server.API.Engines; | |||
using Game.DataLoader; | |||
using GameServer; | |||
using Svelto.Context; | |||
using Svelto.ECS; | |||
namespace CLre_server.API.MainServer | |||
{ | |||
class ServerReadyEngine : ServerEnginePostBuild, IWaitForFrameworkInitialization, IWaitForFrameworkDestruction | |||
{ | |||
internal static event EventHandler<StartedEventArgs> serverEngineReady; | |||
internal static event EventHandler<StartedEventArgs> serverFrameworkReady; | |||
internal static event EventHandler<StopEventArgs> serverFrameworkDestroyed; | |||
public override void Ready() | |||
{ | |||
GameServerSettings gss = Server.Instance.GameServerSettings; | |||
if (serverEngineReady != null) serverEngineReady(this, new StartedEventArgs | |||
{ | |||
photonVersion = PhotonNetwork.gameVersion, | |||
photonRegion = PhotonNetwork.CloudRegion, | |||
gameGuid = gss.GetGameGuid(), | |||
worldName = gss.GetWorldName(), | |||
}); | |||
} | |||
public override IEntitiesDB entitiesDB { get; set; } | |||
public override IEntityFactory entityFactory { get; set; } | |||
public void OnFrameworkInitialized() | |||
{ | |||
GameServerSettings gss = Server.Instance.GameServerSettings; | |||
if (serverFrameworkReady != null) serverFrameworkReady(this, new StartedEventArgs | |||
{ | |||
photonVersion = PhotonNetwork.gameVersion, | |||
photonRegion = PhotonNetwork.CloudRegion, | |||
gameGuid = gss.GetGameGuid(), | |||
worldName = gss.GetWorldName(), | |||
}); | |||
} | |||
public void OnFrameworkDestroyed() | |||
{ | |||
if (serverFrameworkDestroyed != null) serverFrameworkDestroyed(this, new StopEventArgs{}); | |||
} | |||
} | |||
} |
@@ -0,0 +1,14 @@ | |||
namespace CLre_server.API.MainServer | |||
{ | |||
public struct StartingEventArgs{} | |||
public struct StartedEventArgs | |||
{ | |||
public string photonVersion; | |||
public CloudRegionCode photonRegion; | |||
public string worldName; | |||
public string gameGuid; | |||
} | |||
public struct StopEventArgs{} | |||
} |
@@ -0,0 +1,55 @@ | |||
using System; | |||
using System.Diagnostics; | |||
using System.Reflection; | |||
using HarmonyLib; | |||
#if DEBUG | |||
namespace CLre_server.API.Tools | |||
{ | |||
public static class AccessToolsWarnings | |||
{ | |||
internal static bool isEnabled = false; | |||
public static void Enable() | |||
{ | |||
isEnabled = true; | |||
} | |||
public static void Disable() | |||
{ | |||
isEnabled = false; | |||
} | |||
} | |||
[HarmonyPatch(typeof(AccessTools), "TypeByName")] | |||
class AccessTools_TypeByName_Patch | |||
{ | |||
[HarmonyPostfix] | |||
public static void AfterMethodCall(Type __result, string name) | |||
{ | |||
if (!AccessToolsWarnings.isEnabled) return; | |||
if (__result == null) | |||
{ | |||
var method = (new StackTrace()).GetFrame(2).GetMethod(); | |||
Utility.Logging.LogWarning($"[{method.DeclaringType.FullName}.{method.Name}] AccessTools.TypeByName(\"{name}\") returned null result"); | |||
} | |||
} | |||
} | |||
[HarmonyPatch(typeof(AccessTools), "Method", | |||
new Type[] {typeof(string), typeof(Type[]), typeof(Type[])})] | |||
class AccessTools_Method_Patch | |||
{ | |||
[HarmonyPostfix] | |||
public static void AfterMethodCall(MethodInfo __result, string typeColonMethodname) | |||
{ | |||
if (!AccessToolsWarnings.isEnabled) return; | |||
if (__result == null) | |||
{ | |||
var method = (new StackTrace()).GetFrame(2).GetMethod(); | |||
Utility.Logging.LogWarning($"[{method.DeclaringType.FullName}.{method.Name}] AccessTools.Method(\"{typeColonMethodname}\") returned null result"); | |||
} | |||
} | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,101 @@ | |||
using System.Reflection; | |||
using System.Text; | |||
using GameNetworkLayer.Shared; | |||
using HarmonyLib; | |||
using Svelto.DataStructures; | |||
using Svelto.DataStructures.Experimental; | |||
namespace CLre_server.API.Tools | |||
{ | |||
public class NetServerListener | |||
{ | |||
internal static bool isEnabled = false; | |||
private static FasterDictionary<short, FasterList<NetReceiveMessageCallback>> callbacks = new FasterDictionary<short,FasterList<NetReceiveMessageCallback>>(); | |||
public delegate void NetReceiveMessageCallback(NetworkDispatcherCode code, byte[] data, int playerId); | |||
public static void Enable() | |||
{ | |||
isEnabled = true; | |||
} | |||
public static void Disable() | |||
{ | |||
isEnabled = false; | |||
} | |||
public static void DebugReceiveMessage(NetworkDispatcherCode code, NetReceiveMessageCallback callback) | |||
{ | |||
short key = (short)code; | |||
if (callbacks.TryGetValue(key, out FasterList<NetReceiveMessageCallback> handlers)) | |||
{ | |||
handlers.Add(callback); | |||
} | |||
else | |||
{ | |||
FasterList<NetReceiveMessageCallback> newHandlers = new FasterList<NetReceiveMessageCallback>(new [] {callback}); | |||
callbacks.Add(key, newHandlers); | |||
} | |||
} | |||
internal static bool RunDebugCallbacks(NetworkDispatcherCode code, byte[] data, int playerId) | |||
{ | |||
short key = (short)code; | |||
if (callbacks.TryGetValue(key, out FasterList<NetReceiveMessageCallback> handlers)) | |||
{ | |||
foreach (NetReceiveMessageCallback callback in handlers) | |||
{ | |||
callback(code, data, playerId); | |||
} | |||
return true; | |||
} | |||
else | |||
{ | |||
return false; | |||
} | |||
} | |||
public static void Log(NetworkDispatcherCode code, byte[] data, int playerId) | |||
{ | |||
StringBuilder sb = new StringBuilder("Received "); | |||
sb.Append(code.ToString()); | |||
sb.Append(" for player #"); | |||
sb.Append(playerId); | |||
sb.Append(": 0x"); | |||
foreach (byte b in data) | |||
{ | |||
sb.Append(b.ToString("X")); | |||
} | |||
Utility.Logging.Log(sb.ToString()); | |||
} | |||
} | |||
[HarmonyPatch] | |||
class NetMessageClientListener_HandleAllMessages_Patch | |||
{ | |||
[HarmonyPrefix] | |||
public static void BeforeMethodCall(object ____deserializer, int playerId, object value) | |||
{ | |||
if (!NetServerListener.isEnabled) return; | |||
// TODO optimize this to not use Traverse | |||
Traverse result = Traverse.Create(____deserializer).Method("Deserialize", value); | |||
NetworkDispatcherCode code = result.Field<NetworkDispatcherCode>("dispatcherCode").Value; | |||
byte[] data = result.Field<byte[]>("bytes").Value; | |||
if (data == null) | |||
{ | |||
Utility.Logging.LogWarning("Network message data was deserialized as null"); | |||
return; | |||
} | |||
bool isHandled = NetServerListener.RunDebugCallbacks(code, data, playerId); | |||
if (!isHandled) Utility.Logging.Log($"Received network message for player {playerId} (code: {code.ToString()}, len: {data.Length})"); | |||
} | |||
[HarmonyTargetMethod] | |||
public static MethodBase Target() | |||
{ | |||
return AccessTools.Method("GameNetworkLayer.Server.NetMessageServerListener:HandleAllMessages"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,114 @@ | |||
using System; | |||
using System.Reflection; | |||
using System.Text; | |||
using GameNetworkLayer.Shared; | |||
using HarmonyLib; | |||
using NetworkFramework.Shared; | |||
namespace CLre_server.API.Tools | |||
{ | |||
public class NetServerSender | |||
{ | |||
private struct DummyNetDataStruct : ISerializedNetData | |||
{ | |||
public byte[] Serialize() | |||
{ | |||
return new byte[0]; | |||
} | |||
public void Deserialize(byte[] data) | |||
{ | |||
} | |||
} | |||
private static readonly MethodInfo _genericSendMessage = AccessTools.Method("GameNetworkLayer.Server.NetMessageServerSender:SendMessage"); | |||
private static readonly MethodInfo _genericGetSendMessageMethod = | |||
AccessTools.Method(typeof(NetServerSender), "GetSendMessageMethod",parameters: new Type[0]);/* | |||
/*((Func<MethodInfo>) GetSendMessageMethod<DummyNetDataStruct>).Method | |||
.GetBaseDefinition() | |||
.GetGenericMethodDefinition();*/ | |||
private static readonly MethodInfo _genericLog = | |||
AccessTools.Method(typeof(NetServerSender), "Log");/* | |||
((Action<NetworkDispatcherCode, DummyNetDataStruct>) Log<DummyNetDataStruct>).Method | |||
.GetBaseDefinition() | |||
.GetGenericMethodDefinition();*/ | |||
private static readonly MethodInfo _genericGetLogMethod = | |||
AccessTools.Method(typeof(NetServerSender), "GetLogMethod", new Type[0]);/* | |||
((Func<MethodInfo>) GetLogMethod<DummyNetDataStruct>).Method | |||
.GetBaseDefinition() | |||
.GetGenericMethodDefinition();*/ | |||
public static MethodInfo GetSendMessageMethod(Type t) | |||
{ | |||
return (MethodInfo) _genericGetSendMessageMethod.MakeGenericMethod(t) | |||
.Invoke(null, new object[0]); | |||
} | |||
public static MethodInfo GetSendMessageMethod<T>() where T : struct, ISerializedNetData | |||
{ | |||
return _genericSendMessage.MakeGenericMethod(typeof(T)); | |||
} | |||
public static MethodInfo DebugSendMessage<T>(Harmony instance = null, MethodInfo before = null, MethodInfo after = null, MethodInfo transpiler = null, MethodInfo finalizer = null) where T : struct, ISerializedNetData | |||
{ | |||
return DebugSendMessage(typeof(T), instance, before, after, transpiler, finalizer); | |||
} | |||
public static MethodInfo DebugSendMessage(Type generic, Harmony instance = null, MethodInfo before = null, MethodInfo after = null, MethodInfo transpiler = null, MethodInfo finalizer = null) | |||
{ | |||
return DebugSendMessage( | |||
generic, instance, | |||
before == null ? null : new HarmonyMethod(before), | |||
after == null ? null : new HarmonyMethod(after), | |||
transpiler == null ? null : new HarmonyMethod(transpiler), | |||
finalizer == null ? null : new HarmonyMethod(finalizer)); | |||
} | |||
public static MethodInfo DebugSendMessage<T>(Harmony instance = null, HarmonyMethod before = null, HarmonyMethod after = null, HarmonyMethod transpiler = null, HarmonyMethod finalizer = null) where T : struct, ISerializedNetData | |||
{ | |||
return DebugSendMessage(typeof(T), instance, before, after, transpiler, finalizer); | |||
} | |||
public static MethodInfo DebugSendMessage(Type generic, Harmony instance = null, HarmonyMethod before = null, HarmonyMethod after = null, HarmonyMethod transpiler = null, HarmonyMethod finalizer = null) | |||
{ | |||
if (instance == null) instance = CLre.harmonyInstance; | |||
MethodInfo target = GetSendMessageMethod(generic); | |||
return instance.Patch(target, | |||
before, | |||
after, | |||
transpiler, | |||
finalizer); | |||
} | |||
public static MethodInfo GetLogMethod(Type t) | |||
{ | |||
return (MethodInfo) _genericGetLogMethod.MakeGenericMethod(t) | |||
.Invoke(null, new object[0]); | |||
} | |||
public static MethodInfo GetLogMethod<T>() where T : struct, ISerializedNetData | |||
{ | |||
return _genericLog.MakeGenericMethod(typeof(T)); | |||
} | |||
private static void Log<T>(NetworkDispatcherCode code, ref T data) where T : struct, ISerializedNetData | |||
{ | |||
//Utility.Logging.Log($"Sending ISerializedNetData {data.GetType().FullName} (code: {code.ToString()})"); | |||
Traverse d = Traverse.Create(data); | |||
StringBuilder sb = new StringBuilder($"Sending ISerializedNetData {data.GetType().FullName} (code: {code.ToString()})"); | |||
foreach (string fieldName in d.Fields()) | |||
{ | |||
Traverse field = d.Field(fieldName); | |||
sb.Append("\n"); | |||
sb.Append("\""); | |||
sb.Append(fieldName.Substring(fieldName.IndexOf('<')+1, fieldName.LastIndexOf('>')-1)); | |||
sb.Append("\": "); | |||
sb.Append(field.GetValue()); | |||
} | |||
Utility.Logging.Log(sb.ToString()); | |||
} | |||
} | |||
} |
@@ -0,0 +1,88 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
using System.Diagnostics; | |||
namespace CLre_server.API.Utility | |||
{ | |||
/// <summary> | |||
/// Utility class to access Cardlife's built-in logging capabilities. | |||
/// The log is saved to outputLog#.Log | |||
/// </summary> | |||
public static class Logging | |||
{ | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Log(string msg) | |||
{ | |||
Svelto.Console.Log(msg); | |||
} | |||
/// <summary> | |||
/// Write a regular message to Cardlife's log | |||
/// </summary> | |||
/// <param name="obj">The object to log</param> | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Log(object obj) | |||
{ | |||
Svelto.Console.Log(obj.ToString()); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void LogError(string msg, Dictionary<string, string> extraData = null) | |||
{ | |||
Svelto.Console.LogError(msg, extraData); | |||
} | |||
/// <summary> | |||
/// Write an error message to Cardlife's log | |||
/// </summary> | |||
/// <param name="obj">The object to log</param> | |||
/// <param name="extraData">The extra data to pass to the ILogger</param> | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void LogError(object obj, Dictionary<string, string> extraData = null) | |||
{ | |||
Svelto.Console.LogError(obj.ToString(), extraData); | |||
} | |||
/// <summary> | |||
/// Write an exception to Cardlife's log and to the screen and exit game | |||
/// </summary> | |||
/// <param name="e">The exception to log</param> | |||
/// <param name="extraData">The extra data to pass to the ILogger. | |||
/// This is automatically populated with "OuterException#" and "OuterStacktrace#" entries</param> | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void LogException(Exception e, string msg = null, Dictionary<string, string> extraData = null) | |||
{ | |||
Svelto.Console.LogException(msg, e, extraData); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void LogWarning(string msg) | |||
{ | |||
Svelto.Console.LogWarning(msg); | |||
} | |||
/// <summary> | |||
/// Write a warning message to Cardlife's log | |||
/// </summary> | |||
/// <param name="obj">The object to log</param> | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void LogWarning(object obj) | |||
{ | |||
Svelto.Console.LogWarning(obj.ToString()); | |||
} | |||
// descriptive logging | |||
/// <summary> | |||
/// Write a descriptive message to Cardlife's log including the calling method's name | |||
/// </summary> | |||
/// <param name="obj">The object to log</param> | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void MetaLog(object obj) | |||
{ | |||
var method = (new StackTrace()).GetFrame(1).GetMethod(); | |||
Log($"[{method.DeclaringType.FullName}.{method.Name}] {obj.ToString()}"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,119 @@ | |||
using System; | |||
using System.Reflection; | |||
using GameNetworkLayer.Shared; | |||
using HarmonyLib; | |||
using NetworkFramework.Shared; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
namespace CLre_server.API.Utility | |||
{ | |||
public static class Reflection | |||
{ | |||
// useful function & method prototypes | |||
public delegate T Getter<T>(); | |||
public delegate bool ExistsV1(EGID egid); | |||
public delegate bool ExistsV2(int id, int groupid); | |||
public delegate T QueryEntityViewV1<T>(EGID egid); | |||
public delegate T QueryEntityViewV2<T>(int id, ExclusiveGroup.ExclusiveGroupStruct groupid); | |||
public delegate ReadOnlyCollectionStruct<T> QueryEntityViews<T>(int group) where T : class, IEntityViewStruct; | |||
public delegate T[] QueryEntitiesV2<T>(int group, out int count) where T : IEntityStruct; | |||
public delegate T[] QueryEntitiesV1<T>(ExclusiveGroup.ExclusiveGroupStruct group, out int count) where T : IEntityStruct; | |||
public delegate object[] SendMessage<T>(NetworkDispatcherCode dispatcherCode, ref T value) where T : struct, ISerializedNetData; | |||
// useful reflection functions | |||
public static TFuncProto BuildDelegate<TFuncProto>(MethodInfo method) where TFuncProto : Delegate | |||
{ | |||
return (TFuncProto) Delegate.CreateDelegate(typeof(TFuncProto), method); | |||
} | |||
public static Delegate BuildDelegateRaw(MethodInfo method, Type TFuncProto) | |||
{ | |||
return Delegate.CreateDelegate(TFuncProto, method); | |||
} | |||
public static TFuncProto BuildDelegate<TFuncProto>(MethodInfo method, object instance) where TFuncProto : Delegate | |||
{ | |||
return (TFuncProto) Delegate.CreateDelegate(typeof(TFuncProto), instance, method, true); | |||
} | |||
public static Delegate BuildDelegateRaw(MethodInfo method, object instance, Type TFuncProto) | |||
{ | |||
return Delegate.CreateDelegate(TFuncProto, instance, method, true); | |||
} | |||
public static TFuncProto MethodAsDelegate<TFuncProto>(Type class_, string methodName, Type[] parameters = null, Type[] generics = null, object instance = null) where TFuncProto : Delegate | |||
{ | |||
MethodInfo method = AccessTools.Method(class_, methodName, parameters, generics); | |||
if (instance != null) | |||
{ | |||
return BuildDelegate<TFuncProto>(method, instance); | |||
} | |||
return BuildDelegate<TFuncProto>(method); | |||
} | |||
public static Delegate MethodAsDelegateRaw(Type class_, Type TFuncProto, string methodName, Type[] parameters = null, Type[] generics = null, object instance = null) | |||
{ | |||
MethodInfo method = AccessTools.Method(class_, methodName, parameters, generics); | |||
if (instance != null) | |||
{ | |||
return BuildDelegateRaw(method, instance, TFuncProto); | |||
} | |||
return BuildDelegateRaw(method, TFuncProto); | |||
} | |||
public static TFuncProto MethodAsDelegate<TFuncProto>(string typeColonName, Type[] parameters = null, Type[] generics = null, object instance = null) where TFuncProto : Delegate | |||
{ | |||
MethodInfo method = AccessTools.Method(typeColonName, parameters, generics); | |||
if (instance != null) | |||
{ | |||
return BuildDelegate<TFuncProto>(method, instance); | |||
} | |||
return BuildDelegate<TFuncProto>(method); | |||
} | |||
public static Delegate MethodAsDelegateRaw(string typeColonName, Type TFuncProto, Type[] parameters = null, Type[] generics = null, object instance = null) | |||
{ | |||
MethodInfo method = AccessTools.Method(typeColonName, parameters, generics); | |||
if (instance != null) | |||
{ | |||
return BuildDelegateRaw(method, instance, TFuncProto); | |||
} | |||
return BuildDelegateRaw(method, TFuncProto); | |||
} | |||
public static PropertyInfo GetIndexer(this Type type, Type[] arguments = null, BindingFlags bindingFlags = BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.SetField | BindingFlags.SetProperty | BindingFlags.Static) | |||
{ | |||
foreach (PropertyInfo p in type.GetProperties(bindingFlags)) | |||
{ | |||
if (arguments == null && p.GetIndexParameters().Length != 0) return p; | |||
if (arguments != null) | |||
{ | |||
uint count = 0; | |||
foreach (ParameterInfo param in p.GetIndexParameters()) | |||
{ | |||
if (param.ParameterType != arguments[count]) | |||
{ | |||
break; | |||
} | |||
count++; | |||
} | |||
if (count == arguments.Length) | |||
{ | |||
return p; | |||
} | |||
} | |||
} | |||
return null; | |||
} | |||
} | |||
} |
@@ -0,0 +1,102 @@ | |||
using System; | |||
using System.Diagnostics; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Text; | |||
using CLre_server.API.Tools; | |||
using CLre_server.WebStatus; | |||
using GameNetworkLayer.Shared; | |||
using HarmonyLib; | |||
using Svelto.ECS; | |||
using UnityEngine; | |||
namespace CLre_server | |||
{ | |||
public class CLre : IllusionPlugin.IEnhancedPlugin // the Illusion Plugin Architecture (IPA) will ignore classes that don't implement IPlugin | |||
{ | |||
public override string Name { get; } = Assembly.GetExecutingAssembly().GetName().Name; | |||
public override string Version { get; } = Assembly.GetExecutingAssembly().GetName().Version.ToString(); | |||
internal static Harmony harmonyInstance = null; | |||
// called when Cardlife shuts down | |||
public override void OnApplicationQuit() | |||
{ | |||
WebServer.Deinit(); | |||
harmonyInstance.UnpatchAll(); | |||
} | |||
// called when Cardlife starts up | |||
public override void OnApplicationStart() | |||
{ | |||
#if DEBUG | |||
FileLog.Reset(); | |||
Harmony.DEBUG = true; | |||
// enable CLre debug functionality | |||
AccessToolsWarnings.Enable(); | |||
NetServerListener.Enable(); | |||
#endif | |||
// init all Harmony patches in project | |||
harmonyInstance = new Harmony(Name); | |||
harmonyInstance.PatchAll(); | |||
// patches for bugs | |||
Fixes.InitLogSooner.Init(); | |||
// misc | |||
LogIPAPlugins(); // log plugins again so they show up in the log, and not just stdout | |||
Fixes.BugfixAttributeUtility.LogBugfixes(); // log bugfixes that are applied | |||
#if DEBUG | |||
// test CLre debug functionality | |||
Type netData = AccessTools.TypeByName("Game.Handhelds.DrawingStateMessage"); | |||
NetServerSender.DebugSendMessage(netData, harmonyInstance, | |||
NetServerSender.GetLogMethod(netData)); | |||
API.Utility.Logging.MetaLog("Patched SendMessage<Game.Handhelds.DrawingStateMessage>"); | |||
netData = AccessTools.TypeByName("Shared.Inventory.HandheldEquipmentRequest"); | |||
NetServerSender.DebugSendMessage(netData, harmonyInstance, | |||
NetServerSender.GetLogMethod(netData)); | |||
API.Utility.Logging.MetaLog("Patched SendMessage<Shared.Inventory.HandheldEquipmentRequest>"); | |||
NetServerListener.DebugReceiveMessage(NetworkDispatcherCode.EACMessageServerToClient, | |||
NetServerListener.Log); | |||
// API debug and testing | |||
API.MainServer.Server.Instance.FrameworkReady += (_, __) => API.Utility.Logging.MetaLog("(!) Server framework ready for business"); | |||
API.MainServer.Server.Instance.FrameworkExit += (_, __) => API.Utility.Logging.MetaLog("(!) Server framework shutting down"); // this seems to never happen | |||
API.MainServer.Server.Instance.InitStart += (_, __) => API.Utility.Logging.MetaLog("(!) Server initialising"); | |||
API.MainServer.Server.Instance.InitComplete += (_, __) => API.Utility.Logging.MetaLog("(!) Server successfully initialised"); | |||
#endif | |||
WebServer.Init(); | |||
// Log info | |||
API.Utility.Logging.MetaLog($"{Name} init complete."); | |||
} | |||
private static void LogIPAPlugins() | |||
{ | |||
StringBuilder sb = new StringBuilder(); | |||
sb.AppendFormat("Running on Unity {0}\n", Application.unityVersion); | |||
sb.AppendFormat("Running on CardLife Server {0} (aka {1})\n", Game.Utilities.VersionReader.GetVersion(), Application.version); | |||
sb.AppendFormat("-----------------------------\n"); | |||
sb.AppendFormat("Loading plugins from {0} and found {1}\n", System.IO.Path.Combine(Environment.CurrentDirectory, "Plugins"), IllusionInjector.PluginManager.Plugins.Count()); | |||
sb.AppendFormat("-----------------------------\n"); | |||
foreach (IllusionPlugin.IPlugin plugin in IllusionInjector.PluginManager.Plugins) | |||
{ | |||
sb.AppendFormat(" {0}: {1}\n", plugin.Name, plugin.Version); | |||
} | |||
sb.AppendFormat("-----------------------------\n"); | |||
API.Utility.Logging.Log(sb.ToString()); | |||
} | |||
public override void OnGUI() | |||
{ | |||
if (GUI.Button(new Rect(10, 10, 50, 50), "QUIT")) | |||
{ | |||
Application.Quit(); // yeet | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,292 @@ | |||
<Project Sdk="Microsoft.NET.Sdk"> | |||
<PropertyGroup> | |||
<TargetFramework>net472</TargetFramework> | |||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> | |||
<Version>0.0.2</Version> | |||
<Authors>NGnius</Authors> | |||
<PackageLicenseExpression>MIT</PackageLicenseExpression> | |||
<PackageProjectUrl>https://git.exmods.org/NGnius/CLre</PackageProjectUrl> | |||
<NeutralLanguage>en-CA</NeutralLanguage> | |||
</PropertyGroup> | |||
<ItemGroup> | |||
<PackageReference Include="Lib.Harmony" Version="2.0.4" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<Reference Include="Microsoft.CSharp" /> | |||
</ItemGroup> | |||
<!--Start Dependencies--> | |||
<ItemGroup> | |||
<Reference Include="Accessibility"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Accessibility.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Assembly-CSharp-firstpass"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Assembly-CSharp"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Assembly-CSharp.dll</HintPath> | |||
</Reference> | |||
<Reference Include="BehaviorDesignerRuntime"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\BehaviorDesignerRuntime.dll</HintPath> | |||
</Reference> | |||
<Reference Include="DOTween"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\DOTween.dll</HintPath> | |||
</Reference> | |||
<Reference Include="EasyAntiCheat.Client"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\EasyAntiCheat.Client.dll</HintPath> | |||
</Reference> | |||
<Reference Include="EasyAntiCheat.Server"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\EasyAntiCheat.Server.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Fabric.AudioSpline"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Fabric.AudioSpline.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Fabric.Core"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Fabric.Core.dll</HintPath> | |||
</Reference> | |||
<Reference Include="log4net"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\log4net.dll</HintPath> | |||
</Reference> | |||
<Reference Include="mscorlib"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\mscorlib.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Novell.Directory.Ldap"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Novell.Directory.Ldap.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Photon3Unity3D"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Photon3Unity3D.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Rewired_Core"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Rewired_Core.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Rewired_Windows_Lib"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Rewired_Windows_Lib.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Svelto.Common"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Svelto.Common.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Svelto.ECS"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Svelto.ECS.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Svelto.Tasks"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Svelto.Tasks.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Unity.Postprocessing.Runtime"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Unity.Postprocessing.Runtime.dll</HintPath> | |||
</Reference> | |||
<Reference Include="Unity.TextMeshPro"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\Unity.TextMeshPro.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.AccessibilityModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.AccessibilityModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.AIModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.AIModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.AnimationModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.AnimationModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.ARModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.ARModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.AssetBundleModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.AssetBundleModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.AudioModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.AudioModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.BaselibModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.BaselibModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.ClothModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.ClothModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.ClusterInputModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.ClusterInputModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.ClusterRendererModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.ClusterRendererModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.CoreModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.CoreModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.CrashReportingModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.CrashReportingModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.DirectorModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.DirectorModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.FileSystemHttpModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.FileSystemHttpModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.GameCenterModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.GameCenterModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.GridModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.GridModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.HotReloadModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.HotReloadModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.ImageConversionModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.ImageConversionModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.IMGUIModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.InputModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.InputModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.JSONSerializeModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.JSONSerializeModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.LocalizationModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.LocalizationModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.Networking"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.Networking.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.ParticleSystemModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.ParticleSystemModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.PerformanceReportingModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.PerformanceReportingModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.Physics2DModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.Physics2DModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.PhysicsModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.PhysicsModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.ProfilerModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.ProfilerModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.ScreenCaptureModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.ScreenCaptureModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.SharedInternalsModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.SharedInternalsModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.SpatialTracking"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.SpatialTracking.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.SpriteMaskModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.SpriteMaskModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.SpriteShapeModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.SpriteShapeModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.StreamingModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.StreamingModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.StyleSheetsModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.StyleSheetsModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.SubstanceModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.SubstanceModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.TerrainModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.TerrainModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.TerrainPhysicsModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.TerrainPhysicsModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.TextCoreModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.TextCoreModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.TextRenderingModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.TextRenderingModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.TilemapModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.TilemapModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.Timeline"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.Timeline.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.TimelineModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.TimelineModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.TLSModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.TLSModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UI"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UI.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UIElementsModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UIElementsModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UIModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UIModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UmbraModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UmbraModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UNETModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UNETModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UnityAnalyticsModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UnityAnalyticsModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UnityConnectModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UnityConnectModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UnityTestProtocolModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UnityTestProtocolModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UnityWebRequestAssetBundleModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UnityWebRequestAssetBundleModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UnityWebRequestAudioModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UnityWebRequestAudioModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UnityWebRequestModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UnityWebRequestModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UnityWebRequestTextureModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UnityWebRequestTextureModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.UnityWebRequestWWWModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.UnityWebRequestWWWModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.VehiclesModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.VehiclesModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.VFXModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.VFXModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.VideoModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.VideoModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.VRModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.VRModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.WindModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.WindModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="UnityEngine.XRModule"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\UnityEngine.XRModule.dll</HintPath> | |||
</Reference> | |||
<Reference Include="IllusionInjector"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\IllusionInjector.dll</HintPath> | |||
</Reference> | |||
<Reference Include="IllusionPlugin"> | |||
<HintPath>..\..\cl\CardlifeGameServer_Data\Managed\IllusionPlugin.dll</HintPath> | |||
</Reference> | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Remove="WebStatus\Assets\index.html" /> | |||
<EmbeddedResource Include="WebStatus\Assets\index.html" /> | |||
<None Remove="WebStatus\Assets\error404.html" /> | |||
<EmbeddedResource Include="WebStatus\Assets\error404.html" /> | |||
</ItemGroup> | |||
<!--End Dependencies--> | |||
</Project> |
@@ -0,0 +1,118 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Text; | |||
namespace CLre_server.Fixes | |||
{ | |||
[AttributeUsage(AttributeTargets.Class)] | |||
public class BugfixAttribute : Attribute | |||
{ | |||
public string name { get; set; } | |||
public string description { get; set; } | |||
public Type target { get; set; } | |||
public string more { get; set; } | |||
public uint id { get; set; } | |||
public BugfixType component { get; set; } | |||
} | |||
public enum BugfixType : byte | |||
{ | |||
Miscellaneous = 0x00, | |||
HarmonyPatch, | |||
Initialiser, | |||
Workaround, | |||
API, | |||
Debug | |||
} | |||
internal static class BugfixAttributeUtility | |||
{ | |||
public static void LogBugfixes() | |||
{ | |||
List<uint> keys = new List<uint>(); | |||
Dictionary<uint, BugfixStruct> fixes = new Dictionary<uint, BugfixStruct>(); | |||
foreach (Type t in Assembly.GetExecutingAssembly().GetTypes()) | |||
{ | |||
BugfixAttribute b = t.GetCustomAttribute<BugfixAttribute>(true); | |||
if (b != null) | |||
{ | |||
if (!fixes.ContainsKey(b.id)) | |||
{ | |||
BugfixStruct bugfixStruct = new BugfixStruct{id = b.id}; | |||
bugfixStruct.Merge(b); | |||
fixes[b.id] = bugfixStruct; | |||
keys.Add(b.id); | |||
} | |||
else | |||
{ | |||
BugfixStruct bugfixStruct = fixes[b.id]; | |||
bugfixStruct.Merge(b); | |||
fixes[b.id] = bugfixStruct; | |||
} | |||
} | |||
} | |||
keys.Sort(); | |||
//keys.Sort((u, u1) => u.CompareTo(u1)); | |||
StringBuilder sb = new StringBuilder(); | |||
sb.AppendFormat("Applying {0} CLre fixes\n", keys.Count); | |||
sb.AppendFormat("-----------------------------\n"); | |||
foreach (uint i in keys) | |||
{ | |||
sb.Append(fixes[i].ToString()); | |||
sb.Append("\n"); | |||
} | |||
sb.AppendFormat("-----------------------------\n"); | |||
API.Utility.Logging.Log(sb.ToString()); | |||
} | |||
private struct BugfixStruct | |||
{ | |||
public string name; | |||
public string description; | |||
public Type target; | |||
public string more; | |||
public uint id; | |||
private uint total; | |||
private uint[] bugfixTypeCount; | |||
public void Merge(BugfixAttribute b) | |||
{ | |||
if (name == null && b.name != null) name = b.name; | |||
if (description == null && b.description != null) description = b.description; | |||
if (target == null && b.target != null) target = b.target; | |||
if (more == null && b.more != null) more = b.more; | |||
total++; | |||
if (bugfixTypeCount == null) bugfixTypeCount = new uint[Enum.GetNames(typeof(BugfixType)).Length]; | |||
bugfixTypeCount[(byte) b.component]++; | |||
} | |||
public override string ToString() | |||
{ | |||
StringBuilder sb = new StringBuilder(); | |||
sb.AppendFormat(" {0}: ", name); | |||
if (more != null) | |||
{ | |||
sb.AppendFormat("[MORE: {0}] ", more); | |||
} | |||
else if (description != null) | |||
{ | |||
sb.Append(description); | |||
sb.Append(" "); | |||
} | |||
if (target != null) | |||
{ | |||
sb.AppendFormat("[TARGET: {0}] ", target.FullName); | |||
} | |||
sb.AppendFormat("[ID: {0}] ", id); | |||
sb.AppendFormat("({0}M/{1}P/{2}I/{3}W/{4}A/{5}D/{6}T)", | |||
bugfixTypeCount[(byte) BugfixType.Miscellaneous], bugfixTypeCount[(byte) BugfixType.HarmonyPatch], | |||
bugfixTypeCount[(byte) BugfixType.Initialiser], bugfixTypeCount[(byte) BugfixType.Workaround], | |||
bugfixTypeCount[(byte) BugfixType.API], bugfixTypeCount[(byte) BugfixType.Debug], total); | |||
return sb.ToString(); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,84 @@ | |||
using System; | |||
using System.Diagnostics; | |||
using System.Reflection; | |||
using System.Runtime.CompilerServices; | |||
using System.Threading; | |||
using HarmonyLib; | |||
namespace CLre_server.Fixes | |||
{ | |||
[Bugfix(name = "InitLogSooner", | |||
description = "Start the logger slightly sooner than Cardlife does", | |||
component = BugfixType.Initialiser, id = 0)] | |||
public static class InitLogSooner | |||
{ | |||
public static int millisecondsTimeout = 5000; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public static void Init() | |||
{ | |||
try | |||
{ | |||
CustomLoggerThread_CreateGameObject_Patch.allowed = true; | |||
CustomLoggerThread.CreateGameObject(); | |||
CustomLoggerThread_CreateGameObject_Patch.allowed = false; | |||
API.Utility.Logging.Log($"Completed early log init, hello!"); | |||
//System.IO.File.WriteAllText("InitLogSooner.log", $"Done at " + System.DateTime.Now.ToString()); | |||
} | |||
catch (Exception e) | |||
{ | |||
API.Utility.Logging.Log($"Failed to initialise log sooner, reason:\n" + e); | |||
System.IO.File.WriteAllText("InitLogSooner.log", e.ToString()); | |||
} | |||
} | |||
[Bugfix(name = "InitLogSooner", | |||
target = typeof(CustomLoggerThread), | |||
component = BugfixType.HarmonyPatch, id = 0)] | |||
[HarmonyPatch(typeof(CustomLoggerThread), "CreateGameObject")] | |||
class CustomLoggerThread_CreateGameObject_Patch | |||
{ | |||
internal static bool allowed = false; | |||
public static bool Prefix() | |||
{ | |||
return allowed; | |||
} | |||
} | |||
[Bugfix(name = "InitLogSooner", | |||
component = BugfixType.HarmonyPatch, id = 0)] | |||
[HarmonyPatch(typeof(CustomLoggerThread), "StartQueue")] | |||
class CustomLoggerThread_StartQueue_Patch | |||
{ | |||
internal static volatile bool IsLogStarted = false; | |||
private delegate void Flusher(); | |||
public static bool Prefix() | |||
{ | |||
// setup thru reflection | |||
FieldInfo quitThreadField = AccessTools.Field(typeof(CustomLoggerThread), "_quitThread"); | |||
MethodInfo flushLoggerMethod = AccessTools.Method(typeof(CustomLoggerThread), "FlushLogger"); | |||
Flusher flushLogger = (Flusher) Delegate.CreateDelegate(typeof(Action), null, flushLoggerMethod); | |||
MethodInfo forceFlushMethod = AccessTools.Method("CustomLogger:ForceFlush"); | |||
Flusher forceFlush = (Flusher) Delegate.CreateDelegate(typeof(Action), null, forceFlushMethod); | |||
Thread.MemoryBarrier(); | |||
IsLogStarted = true; | |||
while (!(bool) quitThreadField.GetValue(null)) | |||
{ | |||
flushLogger(); | |||
forceFlush(); | |||
Thread.Sleep(millisecondsTimeout); | |||
} | |||
IsLogStarted = false; | |||
return false; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,74 @@ | |||
using System.IO; | |||
using System.Net; | |||
using System.Reflection; | |||
using System.Text; | |||
namespace CLre_server.WebStatus | |||
{ | |||
public static class AssetEndpoints | |||
{ | |||
[WebEndpoint("/")] | |||
public static void LandingPage(HttpListenerContext ctx) | |||
{ | |||
ctx.Response.Headers.Add("Content-Type", "text/html"); | |||
Asset(ctx, "CLre_server.WebStatus.Assets.index.html"); | |||
} | |||
[WebEndpoint("/asset")] | |||
public static void AllAssets(HttpListenerContext ctx) | |||
{ | |||
ctx.Response.Headers.Add("Content-Type", "text/html"); | |||
Asset(ctx, ""); | |||
} | |||
[WebEndpoint("/asset/404")] | |||
public static void Asset404(HttpListenerContext ctx) | |||
{ | |||
ctx.Response.Headers.Add("Content-Type", "text/html"); | |||
Asset(ctx, "CLre_server.WebStatus.Assets.error404.html"); | |||
} | |||
private static bool Asset(HttpListenerContext ctx, string name) | |||
{ | |||
Assembly asm = Assembly.GetCallingAssembly(); | |||
Stream resource = asm.GetManifestResourceStream(name); | |||
if (resource == null) | |||
{ | |||
string assetStr = ListAssetsHtml(asm); | |||
byte[] output = Encoding.UTF8.GetBytes(assetStr); | |||
ctx.Response.OutputStream.Write(output, 0, output.Length); | |||
return false; | |||
} | |||
resource.CopyTo(ctx.Response.OutputStream); | |||
return true; | |||
} | |||
private static string ListAssetsHtml(Assembly target) | |||
{ | |||
StringBuilder sb = new StringBuilder("<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'><title>Asset not found</title></head><body><h1>"); | |||
sb.Append(target.GetName().Name); | |||
sb.Append(" available assets</h1>"); | |||
foreach (string asset in target.GetManifestResourceNames()) | |||
{ | |||
sb.Append("<li>"); | |||
sb.Append(asset); | |||
sb.Append("</li>"); | |||
} | |||
sb.Append("</ul></body></html>"); | |||
return sb.ToString(); | |||
} | |||
private static string ListAssetsText(Assembly target) | |||
{ | |||
StringBuilder sb = new StringBuilder(target.FullName); | |||
foreach (string asset in target.GetManifestResourceNames()) | |||
{ | |||
sb.Append("\n\t"); | |||
sb.Append(asset); | |||
} | |||
return sb.ToString(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,13 @@ | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<title>404 Not Found</title> | |||
</head> | |||
<body> | |||
<h1>Page not found</h1> | |||
<div> | |||
<a href="/">Home</a> | |||
</div> | |||
</body> | |||
</html> |
@@ -0,0 +1,16 @@ | |||
<!DOCTYPE html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="UTF-8"> | |||
<title>CLre Server Status</title> | |||
</head> | |||
<body> | |||
<h1 style="margin:auto; text-align: center; justify-content: center;">Welcome to the CLre HTTP server!</h1> | |||
<div style="width: 100%;"> | |||
<a href="/l/current">View log</a> | |||
<br/> | |||
<iframe src="/l/current" style="width: 100%; height: available;">Loading...</iframe> | |||
</div> | |||
</body> | |||
</html> |
@@ -0,0 +1,35 @@ | |||
using System.Reflection; | |||
namespace CLre_server.WebStatus | |||
{ | |||
public class Attributes | |||
{ | |||
} | |||
[System.AttributeUsage(System.AttributeTargets.Method)] | |||
public class WebEndpointAttribute : System.Attribute | |||
{ | |||
private readonly string endpoint; | |||
public WebEndpointAttribute(string path) | |||
{ | |||
endpoint = path; | |||
Assembly asm = Assembly.GetCallingAssembly(); | |||
if (!WebServer._assembliesToCheck.Contains(asm)) | |||
{ | |||
WebServer._assembliesToCheck.Add(asm); | |||
} | |||
if (WebServer.MainInstance != null && WebServer.MainInstance.IsRunning) | |||
{ | |||
} | |||
} | |||
internal string GetPath() | |||
{ | |||
return endpoint; | |||
} | |||
} | |||
} |
@@ -0,0 +1,21 @@ | |||
using System.Net; | |||
using System.Text; | |||
namespace CLre_server.WebStatus | |||
{ | |||
public class ConfigurationEndpoints | |||
{ | |||
[WebEndpoint("/c/game.json")] | |||
public static void GameServerSettings (HttpListenerContext ctx) | |||
{ | |||
ctx.Response.Headers.Add("Content-Type", "application/json"); | |||
GameServer.GameServerSettings gss = API.MainServer.Server.Instance.GameServerSettings; | |||
string json = UnityEngine.JsonUtility.ToJson(gss); | |||
#if DEBUG | |||
API.Utility.Logging.MetaLog("JSONified settings: " + json); | |||
#endif | |||
byte[] output = Encoding.UTF8.GetBytes(json); | |||
ctx.Response.OutputStream.Write(output, 0, output.Length); | |||
} | |||
} | |||
} |
@@ -0,0 +1,44 @@ | |||
using System.Net; | |||
using System.Text; | |||
namespace CLre_server.WebStatus | |||
{ | |||
public static class DebugEndpoints | |||
{ | |||
[WebEndpoint("/d/ping")] | |||
private static void PingPong(HttpListenerContext ctx) | |||
{ | |||
byte[] output = Encoding.UTF8.GetBytes("pong"); | |||
ctx.Response.OutputStream.Write(output, 0, output.Length); | |||
} | |||
[WebEndpoint("/d/version")] | |||
internal static void VersionInfo(HttpListenerContext ctx) | |||
{ | |||
StringBuilder sb = new StringBuilder(); | |||
sb.Append("CardLife Version (Unity): \t"); | |||
sb.Append(UnityEngine.Application.version); | |||
sb.Append("\n"); | |||
sb.Append("CardLife Version (Game): \t"); | |||
sb.Append(Game.Utilities.VersionReader.GetVersion()); | |||
sb.Append("\n"); | |||
sb.Append("Unity Version: \t\t\t"); | |||
sb.Append(UnityEngine.Application.unityVersion); | |||
sb.Append("\n"); | |||
sb.Append("CLre Version: \t\t\t"); | |||
sb.Append(System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString()); | |||
sb.Append("\n"); | |||
byte[] output = Encoding.UTF8.GetBytes(sb.ToString()); | |||
ctx.Response.OutputStream.Write(output, 0, output.Length); | |||
} | |||
#if DEBUG | |||
[WebEndpoint("/d/test")] | |||
internal static void Experiment(HttpListenerContext ctx) | |||
{ | |||
string test = ""; | |||
byte[] output = Encoding.UTF8.GetBytes(test); | |||
ctx.Response.OutputStream.Write(output, 0, output.Length); | |||
} | |||
#endif | |||
} | |||
} |
@@ -0,0 +1,49 @@ | |||
using System.IO; | |||
using System.Net; | |||
using System.Reflection; | |||
using System.Text; | |||
using HarmonyLib; | |||
namespace CLre_server.WebStatus | |||
{ | |||
public class LogEndpoints | |||
{ | |||
[WebEndpoint("/l/current")] | |||
private static void FullLog(HttpListenerContext ctx) | |||
{ | |||
if (CustomLogger_GetFileNameToUse_Patch.currentLogFile == null) | |||
{ | |||
byte[] output = Encoding.UTF8.GetBytes("No log file available"); | |||
ctx.Response.OutputStream.Write(output, 0, output.Length); | |||
return; | |||
} | |||
// copy file because log is already open for writing | |||
string copyFilename = CustomLogger_GetFileNameToUse_Patch.currentLogFile + ".copy"; | |||
File.Copy(CustomLogger_GetFileNameToUse_Patch.currentLogFile, copyFilename, true); | |||
FileStream logFile = new FileStream(copyFilename, FileMode.Open, FileAccess.Read, FileShare.Read); | |||
logFile.CopyTo(ctx.Response.OutputStream); | |||
logFile.Close(); | |||
} | |||
} | |||
[HarmonyPatch] | |||
class CustomLogger_GetFileNameToUse_Patch | |||
{ | |||
internal static string currentLogFile = null; | |||
[HarmonyPostfix] | |||
public static void AfterMethodCall(string __result) | |||
{ | |||
#if DEBUG | |||
API.Utility.Logging.MetaLog($"Current logfile is {__result}"); | |||
#endif | |||
currentLogFile = __result; | |||
} | |||
[HarmonyTargetMethod] | |||
public static MethodBase Target() | |||
{ | |||
return AccessTools.Method("CustomLogger:GetFileNameToUse"); | |||
} | |||
} | |||
} |
@@ -0,0 +1,192 @@ | |||
using System; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
using System.Net; | |||
using System.Reflection; | |||
using System.Text; | |||
using CLre_server.API.Engines; | |||
using CLre_server.API.MainServer; | |||
using Game.CommonComponents; | |||
using HarmonyLib; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS; | |||
using User.Server; | |||
namespace CLre_server.WebStatus | |||
{ | |||
public static class StatusEndpoints | |||
{ | |||
private struct ServerStateCache | |||
{ | |||
public List<PlayerData> OnlinePlayers; | |||
public int MaxPlayers; | |||
public RunState State; | |||
public static ServerStateCache Empty() | |||
{ | |||
return new ServerStateCache | |||
{ | |||
OnlinePlayers = new List<PlayerData>(), | |||
MaxPlayers = -1, | |||
State = RunState.Initialising, | |||
}; | |||
} | |||
public string Json() | |||
{ | |||
// Unity's built-in JSON serializer does not work with arrays or lists :( | |||
// I miss Newtonsoft... | |||
StringBuilder sb = new StringBuilder($"{{\"PlayersMax\":{MaxPlayers},\"PlayerCount\":{OnlinePlayers.Count},\"Status\":\"{State.ToString()}\",\"OnlinePlayers\":["); | |||
foreach (PlayerData p in OnlinePlayers.ToArray()) | |||
{ | |||
sb.Append(UnityEngine.JsonUtility.ToJson(p)); | |||
sb.Append(","); | |||
} | |||
if (OnlinePlayers.Count > 0) sb.Remove(sb.Length - 1, 1); | |||
sb.Append("]}"); | |||
return sb.ToString(); | |||
} | |||
} | |||
[Serializable] | |||
private struct PlayerData | |||
{ | |||
public string id; | |||
public string name; | |||
public bool isDev; | |||
public float x; | |||
public float y; | |||
public float z; | |||
} | |||
private enum RunState : short | |||
{ | |||
Initialising, | |||
Initialised, | |||
Online, | |||
Quitting, | |||
} | |||
private class StatusPollingEngine : ServerEnginePostBuild | |||
{ | |||
public override void Ready() | |||
{ | |||
API.Utility.Logging.MetaLog("StatusPolling Engine ready"); | |||
pollLoop().Run(); | |||
} | |||
public override IEntitiesDB entitiesDB { get; set; } | |||
public override IEntityFactory entityFactory { get; set; } | |||
private delegate void PlayerPositionFunc(); | |||
private PlayerPositionFunc _playerPositionFunc = null; | |||
private IEnumerator pollLoop() | |||
{ | |||
FieldInfo f = AccessTools.Field(AccessTools.TypeByName("User.Server.AccountExclusiveGroups"), "accountGroup"); | |||
ExclusiveGroup accountGroup = (ExclusiveGroup) f.GetValue(null); | |||
while (_clState.State != RunState.Quitting) | |||
{ | |||
ReadOnlyCollectionStruct<AccountIdServerNode> accounts = | |||
entitiesDB.QueryEntityViews<AccountIdServerNode>(accountGroup); | |||
int index = 0; | |||
foreach (AccountIdServerNode user in accounts) | |||
{ | |||
if (index < _clState.OnlinePlayers.Count) | |||
{ | |||
PlayerData p = _clState.OnlinePlayers[index]; | |||
p.id = user.accountId.publicId.ToString(); | |||
p.name = user.accountId.displayName; | |||
p.isDev = (user.accountId.userFlags & UserFlags.Dev) == UserFlags.Dev; | |||
_clState.OnlinePlayers[index] = p; | |||
} | |||
else | |||
{ | |||
PlayerData p = default(PlayerData); | |||
p.id = user.accountId.publicId.ToString(); | |||
p.name = user.accountId.displayName; | |||
p.isDev = (user.accountId.userFlags & UserFlags.Dev) == UserFlags.Dev; | |||
_clState.OnlinePlayers.Add(p); | |||
} | |||
index++; | |||
} | |||
if (index != 0) syncPlayerPositions(); | |||
if (index < _clState.OnlinePlayers.Count) _clState.OnlinePlayers.RemoveRange(index, _clState.OnlinePlayers.Count - index); | |||
//API.Utility.Logging.MetaLog($"Polled {index} Online Users"); | |||
yield return null; | |||
} | |||
} | |||
private void syncPlayerPositions() | |||
{ | |||
if (_playerPositionFunc == null) | |||
{ | |||
// build non-generic method using reflection | |||
MethodInfo method = AccessTools.Method(typeof(StatusPollingEngine), "getPlayerPositionsGeneric", | |||
generics: new[] {AccessTools.TypeByName("Game.Character.ServerCharacterPositionNode")}); | |||
_playerPositionFunc = API.Utility.Reflection.BuildDelegate<PlayerPositionFunc>(method, this); | |||
} | |||
_playerPositionFunc(); | |||
} | |||
#pragma warning disable 0618 | |||
private void getPlayerPositionsGeneric<T>() where T : EntityView // EntityView is deprecated lol | |||
{ | |||
ReadOnlyCollectionStruct<T> scpnCollection = entitiesDB.QueryEntityViews<T>(DEPRECATED_SveltoExtensions.DEPRECATED_GROUP); | |||
//API.Utility.Logging.MetaLog($"Found {scpnCollection.Count} player positions"); | |||
int i = 0; | |||
foreach (T scpn in scpnCollection) | |||
{ | |||
PlayerData p = _clState.OnlinePlayers[i]; | |||
UnityEngine.Vector3 pos = Traverse.Create(scpn).Field<IPositionComponent>("positionComponent") | |||
.Value.position; | |||
p.x = pos.x; | |||
p.y = pos.y; | |||
p.z = pos.z; | |||
_clState.OnlinePlayers[i] = p; | |||
i++; | |||
} | |||
} | |||
#pragma warning restore 0618 | |||
} | |||
private static ServerStateCache _clState = ServerStateCache.Empty(); | |||
internal static void Init() | |||
{ | |||
#if DEBUG | |||
API.Utility.Logging.MetaLog("Status Endpoint initialising"); | |||
#endif | |||
new StatusPollingEngine(); | |||
// register API event callbacks | |||
Server.Instance.InitStart += (_, __) => _clState.State = RunState.Initialising; | |||
Server.Instance.InitComplete += (_, __) => | |||
{ | |||
_clState.State = RunState.Initialised; | |||
_clState.MaxPlayers = Server.Instance.GameServerSettings.GetMaxPlayers(); | |||
}; | |||
Server.Instance.FrameworkReady += (_, __) => | |||
{ | |||
_clState.State = RunState.Online; | |||
_clState.MaxPlayers = Server.Instance.GameServerSettings.GetMaxPlayers(); | |||
}; | |||
Server.Instance.FrameworkExit += (_, __) => _clState.State = RunState.Quitting; | |||
} | |||
[WebEndpoint("/status.json")] | |||
internal static void StatusJson(HttpListenerContext ctx) | |||
{ | |||
ctx.Response.Headers.Add("Content-Type", "application/json"); | |||
string json = _clState.Json(); | |||
#if DEBUG | |||
API.Utility.Logging.MetaLog("JSONified status: " + json); | |||
#endif | |||
byte[] output = Encoding.UTF8.GetBytes(json); | |||
ctx.Response.OutputStream.Write(output, 0, output.Length); | |||
} | |||
} | |||
} |
@@ -0,0 +1,162 @@ | |||
using System; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Net; | |||
using System.Reflection; | |||
using System.Text; | |||
using HarmonyLib; | |||
using Svelto.DataStructures; | |||
namespace CLre_server.WebStatus | |||
{ | |||
public class WebServer | |||
{ | |||
private const uint DEFAULT_PORT = 5030; | |||
private const string DEFAULT_IP = "localhost"; | |||
private readonly HttpListener _httpListener; | |||
public delegate void RequestHandler(HttpListenerContext ctx); | |||
private Dictionary<string, RequestHandler> _handlers = new Dictionary<string, RequestHandler>(); | |||
public bool IsRunning | |||
{ | |||
get => _httpListener.IsListening; | |||
} | |||
private string _ip_addr; | |||
private uint _port; | |||
public static WebServer MainInstance { get; internal set; } | |||
internal static List<Assembly> _assembliesToCheck = new List<Assembly>(new []{typeof(CLre).Assembly}); | |||
public WebServer() : this(DEFAULT_IP, DEFAULT_PORT) | |||
{ | |||
} | |||
public WebServer(string ip, uint port) | |||
{ | |||
if (!HttpListener.IsSupported) | |||
{ | |||
API.Utility.Logging.LogWarning("HTTP Server is unsupported on earlier Windows versions. It will fail to start."); | |||
} | |||
_httpListener = new HttpListener(); | |||
_httpListener.Prefixes.Add($"http://{ip}:{port}/"); | |||
_ip_addr = ip; | |||
_port = port; | |||
} | |||
internal static void Init() | |||
{ | |||
if (Environment.GetCommandLineArgs().Contains("-web")) | |||
{ | |||
API.Utility.Logging.Log("Starting status web server"); | |||
StatusEndpoints.Init(); | |||
MainInstance = new WebServer(); | |||
MainInstance.Start(); | |||
} | |||
else | |||
{ | |||
API.Utility.Logging.Log("Not starting web server (use CLI argument -web to enable)"); | |||
} | |||
} | |||
internal static void Deinit() | |||
{ | |||
if (MainInstance != null) MainInstance.Stop(); | |||
MainInstance = null; | |||
} | |||
public void Start() | |||
{ | |||
LoadHandlers(); | |||
try | |||
{ | |||
_httpListener.Start(); | |||
} | |||
catch (Exception e) | |||
{ | |||
API.Utility.Logging.LogWarning(e); | |||
return; | |||
} | |||
HandleAllRequests().Run(); | |||
} | |||
public void Stop() | |||
{ | |||
try | |||
{ | |||
_httpListener.Stop(); | |||
_httpListener.Close(); | |||
} | |||
catch (Exception e) | |||
{ | |||
API.Utility.Logging.LogWarning(e); | |||
return; | |||
} | |||
} | |||
private IEnumerator HandleAllRequests() | |||
{ | |||
API.Utility.Logging.MetaLog($"Started HTTP web server at http://{_ip_addr}:{_port}/"); | |||
while (_httpListener.IsListening) | |||
{ | |||
var awaiter = _httpListener.GetContextAsync(); | |||
awaiter.GetAwaiter().OnCompleted(() => DoRequest(awaiter.Result)); | |||
yield return null; | |||
} | |||
API.Utility.Logging.MetaLog("Terminated HTTP web server"); | |||
} | |||
private void DoRequest(HttpListenerContext ctx) | |||
{ | |||
string endpoint = ctx.Request.Url.LocalPath.ToLower(); | |||
#if DEBUG | |||
API.Utility.Logging.LogWarning($"Handling HTTP request {endpoint}"); | |||
#endif | |||
bool handled = false; | |||
foreach (string path in _handlers.Keys) | |||
{ | |||
if (endpoint == path) | |||
{ | |||
handled = true; | |||
_handlers[path](ctx); | |||
break; | |||
} | |||
} | |||
if (!handled) | |||
{ | |||
AssetEndpoints.Asset404(ctx); | |||
} | |||
//byte[] output = Encoding.UTF8.GetBytes(endpoint); | |||
//ctx.Response.OutputStream.Write(output, 0, output.Length); | |||
ctx.Response.Close(); | |||
} | |||
private void LoadHandlers() | |||
{ | |||
_handlers = new Dictionary<string, RequestHandler>(); | |||
foreach (Assembly asm in _assembliesToCheck.ToArray()) | |||
{ | |||
foreach (Type t in asm.GetTypes()) | |||
{ | |||
foreach (MethodInfo m in t.GetMethods(AccessTools.all)) | |||
{ | |||
WebEndpointAttribute attr = m.GetCustomAttribute<WebEndpointAttribute>(); | |||
if (attr != null) | |||
{ | |||
// TODO validate that method signature matches that of RequestHandler | |||
string key = attr.GetPath().ToLower(); | |||
API.Utility.Logging.MetaLog($"{t.FullName}:{m.Name} is handling {key}"); | |||
_handlers.Add(key, (RequestHandler) Delegate.CreateDelegate(typeof(RequestHandler), m)); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} |