Merge branch 'heads/upstream' into OBT/1.2.0(SpringUpdate)
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.Networking;
|
||||
using MoonSharp.Interpreter;
|
||||
using MoonSharp.Interpreter.Interop;
|
||||
using MoonSharp.Interpreter.Interop.BasicDescriptors;
|
||||
using Sigil;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface IDefaultLuaRegistrar : IService
|
||||
{
|
||||
public void RegisterAll();
|
||||
}
|
||||
|
||||
public class DefaultLuaRegistrar : IDefaultLuaRegistrar
|
||||
{
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
private readonly ILuaUserDataService _userDataService;
|
||||
private readonly ISafeLuaUserDataService _safeUserDataService;
|
||||
private readonly ILoggerService _loggerService;
|
||||
|
||||
private class SteamIDMemberDescriptor : IMemberDescriptor
|
||||
{
|
||||
public bool IsStatic => false;
|
||||
|
||||
public string Name => "SteamID";
|
||||
|
||||
public MemberDescriptorAccess MemberAccess => MemberDescriptorAccess.CanRead;
|
||||
|
||||
public DynValue GetValue(Script script, object obj)
|
||||
{
|
||||
if (obj is Client client)
|
||||
{
|
||||
return DynValue.FromObject(script, ModUtils.Client.GetSteamId(client));
|
||||
}
|
||||
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetValue(Script script, object obj, DynValue value)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public DefaultLuaRegistrar(ILoggerService loggerService, ILuaUserDataService userDataService, ISafeLuaUserDataService safeUserDataService)
|
||||
{
|
||||
_userDataService = userDataService;
|
||||
_safeUserDataService = safeUserDataService;
|
||||
_loggerService = loggerService;
|
||||
}
|
||||
|
||||
private void RegisterShared()
|
||||
{
|
||||
_userDataService.RegisterType("System.TimeSpan");
|
||||
_userDataService.RegisterType("System.Exception");
|
||||
_userDataService.RegisterType("System.Console");
|
||||
_userDataService.RegisterType("System.Exception");
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.Success`2");
|
||||
_userDataService.RegisterType("Barotrauma.Failure`2");
|
||||
_userDataService.RegisterType("Barotrauma.Range`1");
|
||||
_userDataService.RegisterType("Barotrauma.ItemPrefab");
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.InputType");
|
||||
|
||||
List<Assembly> assembliesToScan = [typeof(DefaultLuaRegistrar).Assembly, typeof(Identifier).Assembly, typeof(Microsoft.Xna.Framework.Vector2).Assembly];
|
||||
|
||||
foreach (var type in assembliesToScan.SelectMany(a => a.GetTypes()))
|
||||
{
|
||||
if (type.IsEnum || type.Name.StartsWith("<") || type.IsDefined(typeof(CompilerGeneratedAttribute)) || !_safeUserDataService.IsAllowed(type.FullName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_userDataService.RegisterType(type.FullName);
|
||||
}
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.LuaSByte");
|
||||
_userDataService.RegisterType("Barotrauma.LuaByte");
|
||||
_userDataService.RegisterType("Barotrauma.LuaInt16");
|
||||
_userDataService.RegisterType("Barotrauma.LuaUInt16");
|
||||
_userDataService.RegisterType("Barotrauma.LuaInt32");
|
||||
_userDataService.RegisterType("Barotrauma.LuaUInt32");
|
||||
_userDataService.RegisterType("Barotrauma.LuaInt64");
|
||||
_userDataService.RegisterType("Barotrauma.LuaUInt64");
|
||||
_userDataService.RegisterType("Barotrauma.LuaSingle");
|
||||
_userDataService.RegisterType("Barotrauma.LuaDouble");
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.Level+InterestingPosition");
|
||||
_userDataService.RegisterType("Barotrauma.Networking.RespawnManager+TeamSpecificState");
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.CharacterParams+AIParams");
|
||||
_userDataService.RegisterType("Barotrauma.CharacterParams+TargetParams");
|
||||
_userDataService.RegisterType("Barotrauma.CharacterParams+InventoryParams");
|
||||
_userDataService.RegisterType("Barotrauma.CharacterParams+HealthParams");
|
||||
_userDataService.RegisterType("Barotrauma.CharacterParams+ParticleParams");
|
||||
_userDataService.RegisterType("Barotrauma.CharacterParams+SoundParams");
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.FabricationRecipe+RequiredItemByIdentifier");
|
||||
_userDataService.RegisterType("Barotrauma.FabricationRecipe+RequiredItemByTag");
|
||||
|
||||
_userDataService.MakeFieldAccessible(_userDataService.RegisterType("Barotrauma.StatusEffect"), "user");
|
||||
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.ContentPackageManager+PackageSource");
|
||||
_userDataService.RegisterType("Barotrauma.ContentPackageManager+EnabledPackages");
|
||||
|
||||
_userDataService.RegisterType("System.Xml.Linq.XElement");
|
||||
_userDataService.RegisterType("System.Xml.Linq.XName");
|
||||
_userDataService.RegisterType("System.Xml.Linq.XAttribute");
|
||||
_userDataService.RegisterType("System.Xml.Linq.XContainer");
|
||||
_userDataService.RegisterType("System.Xml.Linq.XDocument");
|
||||
_userDataService.RegisterType("System.Xml.Linq.XNode");
|
||||
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.Networking.ServerSettings+SavedClientPermission");
|
||||
_userDataService.RegisterType("Barotrauma.Inventory+ItemSlot");
|
||||
|
||||
|
||||
_userDataService.MakeFieldAccessible(_userDataService.RegisterType("Barotrauma.Items.Components.CustomInterface"), "customInterfaceElementList");
|
||||
_userDataService.RegisterType("Barotrauma.Items.Components.CustomInterface+CustomInterfaceElement");
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.DebugConsole+Command");
|
||||
|
||||
{
|
||||
var descriptor = _userDataService.RegisterType("Barotrauma.NetLobbyScreen");
|
||||
|
||||
#if SERVER
|
||||
_userDataService.MakeFieldAccessible(descriptor, "subs");
|
||||
#endif
|
||||
}
|
||||
|
||||
_userDataService.RegisterType("FarseerPhysics.Dynamics.Body");
|
||||
_userDataService.RegisterType("FarseerPhysics.Dynamics.World");
|
||||
_userDataService.RegisterType("FarseerPhysics.Dynamics.Fixture");
|
||||
_userDataService.RegisterType("FarseerPhysics.ConvertUnits");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.AABB");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.ContactFeature");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.ManifoldPoint");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.ContactID");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.Manifold");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.RayCastInput");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.ClipVertex");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.RayCastOutput");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.EPAxis");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.ReferenceFace");
|
||||
_userDataService.RegisterType("FarseerPhysics.Collision.Collision");
|
||||
|
||||
_userDataService.RegisterType("Voronoi2.DoubleVector2");
|
||||
_userDataService.RegisterType("Voronoi2.Site");
|
||||
_userDataService.RegisterType("Voronoi2.Edge");
|
||||
_userDataService.RegisterType("Voronoi2.Halfedge");
|
||||
_userDataService.RegisterType("Voronoi2.VoronoiCell");
|
||||
_userDataService.RegisterType("Voronoi2.GraphEdge");
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.PrefabCollection`1");
|
||||
_userDataService.RegisterType("Barotrauma.PrefabSelector`1");
|
||||
_userDataService.RegisterType("Barotrauma.Pair`2");
|
||||
|
||||
_userDataService.RegisterExtensionType("Barotrauma.MathUtils");
|
||||
_userDataService.RegisterExtensionType("Barotrauma.XMLExtensions");
|
||||
|
||||
var itemPrefabDescriptor = (StandardUserDataDescriptor)_userDataService.RegisterType("Barotrauma.ItemPrefab");
|
||||
itemPrefabDescriptor.AddMember("GetItemPrefab", new MethodMemberDescriptor(typeof(ModUtils.ItemPrefab).GetMethod(nameof(ModUtils.ItemPrefab.GetItemPrefab), BindingFlags.NonPublic | BindingFlags.Static)));
|
||||
|
||||
var clientDescriptor = (StandardUserDataDescriptor)_userDataService.RegisterType("Barotrauma.Networking.Client");
|
||||
clientDescriptor.AddMember("ClientList", new PropertyMemberDescriptor(typeof(ModUtils.Client).GetProperty(nameof(ModUtils.Client.ClientList), BindingFlags.NonPublic | BindingFlags.Static), InteropAccessMode.LazyOptimized));
|
||||
clientDescriptor.AddMember("SteamID", new SteamIDMemberDescriptor());
|
||||
|
||||
|
||||
#if SERVER
|
||||
clientDescriptor.AddMember("UnbanPlayer", new MethodMemberDescriptor(typeof(ModUtils.Client).GetMethod(nameof(ModUtils.Client.UnbanPlayer), BindingFlags.NonPublic | BindingFlags.Static), InteropAccessMode.LazyOptimized));
|
||||
clientDescriptor.AddMember("BanPlayer", new MethodMemberDescriptor(typeof(ModUtils.Client).GetMethod(nameof(ModUtils.Client.BanPlayer), BindingFlags.NonPublic | BindingFlags.Static), InteropAccessMode.LazyOptimized));
|
||||
#endif
|
||||
|
||||
_userDataService.RegisterExtensionType(typeof(ClientExtensions).FullName);
|
||||
_userDataService.RegisterExtensionType(typeof(ItemExtensions).FullName);
|
||||
_userDataService.RegisterExtensionType(typeof(MapEntityExtensions).FullName);
|
||||
_userDataService.RegisterExtensionType(typeof(QualityExtensions).FullName);
|
||||
|
||||
|
||||
var toolBox = UserData.RegisterType(typeof(ToolBox));
|
||||
#if CLIENT
|
||||
_userDataService.RemoveMember(toolBox, "OpenFileWithShell");
|
||||
#endif
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
private void RegisterClient()
|
||||
{
|
||||
_userDataService.RegisterType("Microsoft.Xna.Framework.Graphics.Effect");
|
||||
_userDataService.RegisterType("Microsoft.Xna.Framework.Graphics.EffectParameterCollection");
|
||||
_userDataService.RegisterType("Microsoft.Xna.Framework.Graphics.EffectParameter");
|
||||
|
||||
_userDataService.RegisterType("Microsoft.Xna.Framework.Graphics.SpriteBatch");
|
||||
_userDataService.RegisterType("Microsoft.Xna.Framework.Graphics.Texture2D");
|
||||
_userDataService.RegisterType("EventInput.KeyboardDispatcher");
|
||||
_userDataService.RegisterType("EventInput.KeyEventArgs");
|
||||
_userDataService.RegisterType("Microsoft.Xna.Framework.Input.Keys");
|
||||
_userDataService.RegisterType("Microsoft.Xna.Framework.Input.KeyboardState");
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.Anchor");
|
||||
_userDataService.RegisterType("Barotrauma.Alignment");
|
||||
_userDataService.RegisterType("Barotrauma.Pivot");
|
||||
_userDataService.RegisterType("Barotrauma.Key");
|
||||
_userDataService.RegisterType("Barotrauma.PlayerInput");
|
||||
|
||||
|
||||
_userDataService.RegisterType("Barotrauma.Inventory+SlotReference");
|
||||
}
|
||||
#elif SERVER
|
||||
private void RegisterServer()
|
||||
{
|
||||
_userDataService.RegisterType("Barotrauma.Character+TeamChangeEventData");
|
||||
}
|
||||
#endif
|
||||
|
||||
public void RegisterAll()
|
||||
{
|
||||
RegisterShared();
|
||||
#if CLIENT
|
||||
RegisterClient();
|
||||
#elif SERVER
|
||||
RegisterServer();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ILuaConfigService : ILuaService
|
||||
{
|
||||
FluentResults.Result LoadSavedValueForConfig(ISettingBase setting);
|
||||
bool TryGetConfig<T>(ContentPackage package, string internalName, out T instance) where T : ISettingBase;
|
||||
FluentResults.Result SaveConfigValue(ISettingBase setting);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
/// <summary>
|
||||
/// Service for providing stateful functions and in-memory storage for lua functions
|
||||
/// </summary>
|
||||
public interface ILuaDataService : ILuaService
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using Barotrauma.LuaCs.Compatibility;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ILuaSafeEventService : ILuaService, ILuaCsHook
|
||||
{
|
||||
/// <summary>
|
||||
/// Subscribes lua scripts via <see cref="ImpromptuInterface"/> for the given <see cref="IEvent{T}"/> interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="identifier"></param>
|
||||
/// <param name="callbacks">A 'method name'=='signature action' dictionary matching the interface method list.</param>
|
||||
void Subscribe<T>(string identifier, IDictionary<string, LuaCsFunc> callbacks) where T : class, IEvent<T>;
|
||||
/// <summary>
|
||||
/// Removes a subscriber from an event that subscribed under the given identifier.
|
||||
/// </summary>
|
||||
/// <param name="eventName"></param>
|
||||
/// <param name="identifier"></param>
|
||||
void Unsubscribe(string eventName, string identifier);
|
||||
/// <summary>
|
||||
/// Send an event to all subscribers to an interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Interface type.</typeparam>
|
||||
/// <param name="subscriberRunner">Execution runner, the subscriber is provided as the first argument in the lua runner.</param>
|
||||
/// <returns></returns>
|
||||
void PublishLuaEvent<T>(LuaCsFunc subscriberRunner) where T : class, IEvent<T>;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the target method name for legacy <see cref="ILuaCsHook.Add(string, LuaCsFunc)"/> to target on new <see cref="IEvent{T}"/>
|
||||
/// interfaces.
|
||||
/// </summary>
|
||||
/// <param name="luaEventName">The <see cref="ILuaCsHook.Add(string, LuaCsFunc)"/> legacy event name.</param>
|
||||
/// <param name="targetMethod">.</param>
|
||||
/// <typeparam name="T">The event interface type.</typeparam>
|
||||
/// <returns>Operation success.</returns>
|
||||
/// <exception cref="ArgumentNullException">The <see cref="luaEventName"/> is <b>null or empty.</b></exception>
|
||||
public FluentResults.Result RegisterLuaEventAlias<T>(string luaEventName, string targetMethod) where T : class, IEvent<T>;
|
||||
}
|
||||
|
||||
public interface ILuaEventService : ILuaSafeEventService
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ILuaNetworkingService : ILuaService
|
||||
{
|
||||
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ILuaPackageManagementService : ILuaService
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ILuaPackageService : ILuaService
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using Barotrauma.LuaCs.Compatibility;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using static Barotrauma.LuaCs.Compatibility.ILuaCsHook;
|
||||
using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ILuaPatcher : IReusableService
|
||||
{
|
||||
string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before);
|
||||
string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before);
|
||||
string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before);
|
||||
string Patch(string className, string methodName, LuaCsPatchFunc patch, HookMethodType hookType = HookMethodType.Before);
|
||||
bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, HookMethodType hookType);
|
||||
bool RemovePatch(string identifier, string className, string methodName, HookMethodType hookType);
|
||||
|
||||
void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, HookMethodType hookType = HookMethodType.Before, IAssemblyPlugin owner = null);
|
||||
public void HookMethod(string identifier, string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before);
|
||||
public void HookMethod(string identifier, string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before);
|
||||
public void HookMethod(string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before);
|
||||
public void HookMethod(string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using FluentResults;
|
||||
using MoonSharp.Interpreter.Loaders;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ILuaScriptLoader : IService, IScriptLoader, ISafeStorageValidation
|
||||
{
|
||||
void ClearCaches();
|
||||
/// <summary>
|
||||
/// Whether caching is enabled/disabled.
|
||||
/// </summary>
|
||||
/// <param name="useCaching"></param>
|
||||
void SetCachingPolicy(bool useCaching);
|
||||
Task<Result<ImmutableArray<(ContentPath Path, Result<string>)>>> CacheResourcesAsync(ImmutableArray<ILuaScriptResourceInfo> resourceInfos);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ILuaService : IService
|
||||
{
|
||||
|
||||
}
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
using System;
|
||||
using MoonSharp.Interpreter;
|
||||
using Microsoft.Xna.Framework;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch;
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.LuaCs;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class LuaConverters
|
||||
{
|
||||
private readonly ILuaScriptManagementService _luaScriptManagementService;
|
||||
|
||||
public LuaConverters(ILuaScriptManagementService luaScriptManagementService)
|
||||
{
|
||||
_luaScriptManagementService = luaScriptManagementService;
|
||||
}
|
||||
|
||||
private DynValue Call(object function, params object[] arguments) => _luaScriptManagementService.CallFunctionSafe(function, arguments);
|
||||
|
||||
public void RegisterLuaConverters()
|
||||
{
|
||||
RegisterAction<Item>();
|
||||
RegisterAction<Character>();
|
||||
RegisterAction<Character, Character>();
|
||||
RegisterAction<Entity>();
|
||||
RegisterAction<float>();
|
||||
RegisterAction();
|
||||
|
||||
RegisterFunc<Fixture, Vector2, Vector2, float, float>();
|
||||
RegisterFunc<AIObjective, bool>();
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsAction), v => (LuaCsAction)(args =>
|
||||
{
|
||||
if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript)
|
||||
{
|
||||
Call(v.Function, args);
|
||||
}
|
||||
}));
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsFunc), v => (LuaCsFunc)(args =>
|
||||
{
|
||||
if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript)
|
||||
{
|
||||
return Call(v.Function, args);
|
||||
}
|
||||
return default;
|
||||
}));
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsCompatPatchFunc), v => (LuaCsCompatPatchFunc)((self, args) =>
|
||||
{
|
||||
if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript)
|
||||
{
|
||||
return Call(v.Function, self, args);
|
||||
}
|
||||
return default;
|
||||
}));
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(LuaCsPatchFunc), v => (LuaCsPatchFunc)((self, args) =>
|
||||
{
|
||||
if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript)
|
||||
{
|
||||
return Call(v.Function, self, args);
|
||||
}
|
||||
return default;
|
||||
}));
|
||||
|
||||
|
||||
void RegisterHandler<T>(Func<Closure, T> converter) => Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(T), v => converter(v.Function));
|
||||
|
||||
RegisterHandler(f => (Character.OnDeathHandler)((a1, a2) => Call(f, a1, a2)));
|
||||
RegisterHandler(f => (Character.OnAttackedHandler)((a1, a2) => Call(f, a1, a2)));
|
||||
|
||||
#if CLIENT
|
||||
RegisterAction<Microsoft.Xna.Framework.Graphics.SpriteBatch, GUICustomComponent>();
|
||||
RegisterAction<float, Microsoft.Xna.Framework.Graphics.SpriteBatch>();
|
||||
RegisterAction<Microsoft.Xna.Framework.Graphics.SpriteBatch, float>();
|
||||
|
||||
{
|
||||
RegisterHandler(f => (GUIComponent.SecondaryButtonDownHandler)(
|
||||
(a1, a2) => Call(f, a1, a2)?.CastToBool() ?? default));
|
||||
|
||||
RegisterHandler(f => (GUIButton.OnClickedHandler)(
|
||||
(a1, a2) => Call(f, a1, a2)?.CastToBool() ?? default));
|
||||
RegisterHandler(f => (GUIButton.OnButtonDownHandler)(
|
||||
() => Call(f)?.CastToBool() ?? default));
|
||||
RegisterHandler(f => (GUIButton.OnPressedHandler)(
|
||||
() => Call(f)?.CastToBool() ?? default));
|
||||
|
||||
RegisterHandler(f => (GUIColorPicker.OnColorSelectedHandler)(
|
||||
(a1, a2) => Call(f, a1, a2)?.CastToBool() ?? default));
|
||||
|
||||
RegisterHandler(f => (GUIDropDown.OnSelectedHandler)(
|
||||
(a1, a2) => Call(f, a1, a2)?.CastToBool() ?? default));
|
||||
|
||||
RegisterHandler(f => (GUIListBox.OnSelectedHandler)(
|
||||
(a1, a2) => Call(f, a1, a2)?.CastToBool() ?? default));
|
||||
RegisterHandler(f => (GUIListBox.OnRearrangedHandler)(
|
||||
(a1, a2) => Call(f, a1, a2)));
|
||||
RegisterHandler(f => (GUIListBox.CheckSelectedHandler)(
|
||||
() => Call(f)?.ToObject() ?? default));
|
||||
|
||||
RegisterHandler(f => (GUINumberInput.OnValueEnteredHandler)(
|
||||
(a1) => Call(f, a1)));
|
||||
RegisterHandler(f => (GUINumberInput.OnValueChangedHandler)(
|
||||
(a1) => Call(f, a1)));
|
||||
|
||||
RegisterHandler(f => (GUIProgressBar.ProgressGetterHandler)(
|
||||
() => (float)(Call(f)?.CastToNumber() ?? default)));
|
||||
|
||||
RegisterHandler(f => (GUIRadioButtonGroup.RadioButtonGroupDelegate)(
|
||||
(a1, a2) => Call(f, a1, a2)));
|
||||
|
||||
RegisterHandler(f => (GUIScrollBar.OnMovedHandler)(
|
||||
(a1, a2) => Call(f, a1, a2)?.CastToBool() ?? default));
|
||||
RegisterHandler(f => (GUIScrollBar.ScrollConversion)(
|
||||
(a1, a2) => (float)(Call(f, a1, a2)?.CastToNumber() ?? default)));
|
||||
|
||||
RegisterHandler(f => (GUITextBlock.TextGetterHandler)(
|
||||
() => Call(f, new object[0])?.CastToString() ?? default));
|
||||
|
||||
RegisterHandler(f => (GUITextBox.OnEnterHandler)(
|
||||
(a1, a2) => Call(f, a1, a2)?.CastToBool() ?? default));
|
||||
RegisterHandler(f => (GUITextBox.OnTextChangedHandler)(
|
||||
(a1, a2) => Call(f, a1, a2)?.CastToBool() ?? default));
|
||||
RegisterHandler(f => (TextBoxEvent)(
|
||||
(a1, a2) => Call(f, a1, a2)));
|
||||
|
||||
RegisterHandler(f => (GUITickBox.OnSelectedHandler)(
|
||||
(a1) => Call(f, a1)?.CastToBool() ?? default));
|
||||
|
||||
RegisterHandler(f => (GUITextBlock.ClickableArea.OnClickDelegate)(
|
||||
(a1, a2) => Call(f, a1, a2)));
|
||||
}
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(NetMessageReceived), v => (NetMessageReceived)((arg1) =>
|
||||
{
|
||||
if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript)
|
||||
{
|
||||
Call(v.Function, arg1);
|
||||
}
|
||||
}));
|
||||
#elif SERVER
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(NetMessageReceived), v => (NetMessageReceived)((arg1, arg2) =>
|
||||
{
|
||||
if (v.Function.OwnerScript == _luaScriptManagementService.InternalScript)
|
||||
{
|
||||
Call(v.Function, arg1, arg2);
|
||||
}
|
||||
}));
|
||||
#endif
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Table, typeof(Pair<JobPrefab, int>), v =>
|
||||
{
|
||||
return new Pair<JobPrefab, int>((JobPrefab)v.Table.Get(1).ToObject(), (int)v.Table.Get(2).CastToNumber());
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion<ulong>((Script script, ulong v) =>
|
||||
{
|
||||
return DynValue.NewString(v.ToString());
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.String, typeof(ulong), v =>
|
||||
{
|
||||
return ulong.Parse(v.String);
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
|
||||
scriptDataType: DataType.UserData,
|
||||
clrDataType: typeof(sbyte),
|
||||
canConvert: luaValue => luaValue.UserData?.Object is LuaSByte,
|
||||
converter: luaValue => luaValue.UserData.Object is LuaSByte v
|
||||
? (sbyte)v
|
||||
: throw new ScriptRuntimeException("use SByte(value) to pass primitive type 'sbyte' to C#"));
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
|
||||
scriptDataType: DataType.UserData,
|
||||
clrDataType: typeof(byte),
|
||||
canConvert: luaValue => luaValue.UserData?.Object is LuaByte,
|
||||
converter: luaValue => luaValue.UserData.Object is LuaByte v
|
||||
? (byte)v
|
||||
: throw new ScriptRuntimeException("use Byte(value) to pass primitive type 'byte' to C#"));
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
|
||||
scriptDataType: DataType.UserData,
|
||||
clrDataType: typeof(short),
|
||||
canConvert: luaValue => luaValue.UserData?.Object is LuaInt16,
|
||||
converter: luaValue => luaValue.UserData.Object is LuaInt16 v
|
||||
? (short)v
|
||||
: throw new ScriptRuntimeException("use Int16(value) to pass primitive type 'short' to C#"));
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
|
||||
scriptDataType: DataType.UserData,
|
||||
clrDataType: typeof(ushort),
|
||||
canConvert: luaValue => luaValue.UserData?.Object is LuaUInt16,
|
||||
converter: luaValue => luaValue.UserData.Object is LuaUInt16 v
|
||||
? (ushort)v
|
||||
: throw new ScriptRuntimeException("use UInt16(value) to pass primitive type 'ushort' to C#"));
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
|
||||
scriptDataType: DataType.UserData,
|
||||
clrDataType: typeof(int),
|
||||
canConvert: luaValue => luaValue.UserData?.Object is LuaInt32,
|
||||
converter: luaValue => luaValue.UserData.Object is LuaInt32 v
|
||||
? (int)v
|
||||
: throw new ScriptRuntimeException("use Int32(value) to pass primitive type 'int' to C#"));
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
|
||||
scriptDataType: DataType.UserData,
|
||||
clrDataType: typeof(uint),
|
||||
canConvert: luaValue => luaValue.UserData?.Object is LuaUInt32,
|
||||
converter: luaValue => luaValue.UserData.Object is LuaUInt32 v
|
||||
? (uint)v
|
||||
: throw new ScriptRuntimeException("use UInt32(value) to pass primitive type 'uint' to C#"));
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
|
||||
scriptDataType: DataType.UserData,
|
||||
clrDataType: typeof(long),
|
||||
canConvert: luaValue => luaValue.UserData?.Object is LuaInt64,
|
||||
converter: luaValue => luaValue.UserData.Object is LuaInt64 v
|
||||
? (long)v
|
||||
: throw new ScriptRuntimeException("use Int64(value) to pass primitive type 'long' to C#"));
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
|
||||
scriptDataType: DataType.UserData,
|
||||
clrDataType: typeof(ulong),
|
||||
canConvert: luaValue => luaValue.UserData?.Object is LuaUInt64,
|
||||
converter: luaValue => luaValue.UserData.Object is LuaUInt64 v
|
||||
? (ulong)v
|
||||
: throw new ScriptRuntimeException("use UInt64(value) to pass primitive type 'ulong' to C#"));
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
|
||||
scriptDataType: DataType.UserData,
|
||||
clrDataType: typeof(float),
|
||||
canConvert: luaValue => luaValue.UserData?.Object is LuaSingle,
|
||||
converter: luaValue => luaValue.UserData.Object is LuaSingle v
|
||||
? (float)v
|
||||
: throw new ScriptRuntimeException("use Single(value) to pass primitive type 'float' to C#"));
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(
|
||||
scriptDataType: DataType.UserData,
|
||||
clrDataType: typeof(double),
|
||||
canConvert: luaValue => luaValue.UserData?.Object is LuaDouble,
|
||||
converter: luaValue => luaValue.UserData.Object is LuaDouble v
|
||||
? (double)v
|
||||
: throw new ScriptRuntimeException("use Double(value) to pass primitive type 'double' to C#"));
|
||||
|
||||
RegisterOption<Character>(DataType.UserData);
|
||||
RegisterOption<AccountId>(DataType.UserData);
|
||||
RegisterOption<ContentPackageId>(DataType.UserData);
|
||||
RegisterOption<SteamId>(DataType.UserData);
|
||||
RegisterOption<DateTime>(DataType.UserData);
|
||||
RegisterOption<BannedPlayer>(DataType.UserData);
|
||||
RegisterOption<Address>(DataType.UserData);
|
||||
|
||||
RegisterOption<int>(DataType.Number);
|
||||
|
||||
RegisterEither<Address, AccountId>();
|
||||
|
||||
RegisterImmutableArray<FactionPrefab.HireableCharacter>();
|
||||
}
|
||||
|
||||
private static void RegisterImmutableArray<T>()
|
||||
{
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Table, typeof(ImmutableArray<T>), v =>
|
||||
{
|
||||
return v.ToObject<T[]>().ToImmutableArray();
|
||||
});
|
||||
}
|
||||
|
||||
private static void RegisterEither<T1, T2>()
|
||||
{
|
||||
DynValue convertEitherIntoDynValue(Either<T1, T2> either)
|
||||
{
|
||||
if (either.TryGet(out T1 value1))
|
||||
{
|
||||
return UserData.Create(value1);
|
||||
}
|
||||
|
||||
if (either.TryGet(out T2 value2))
|
||||
{
|
||||
return UserData.Create(value2);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion(typeof(EitherT<T1, T2>), (Script v, object obj) =>
|
||||
{
|
||||
if (obj is EitherT<T1, T2> either)
|
||||
{
|
||||
return convertEitherIntoDynValue(either);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion(typeof(EitherU<T1, T2>), (Script v, object obj) =>
|
||||
{
|
||||
if (obj is EitherU<T1, T2> either)
|
||||
{
|
||||
return convertEitherIntoDynValue(either);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private static void RegisterOption<T>(DataType dataType)
|
||||
{
|
||||
Script.GlobalOptions.CustomConverters.SetClrToScriptCustomConversion(typeof(Option<T>), (Script v, object obj) =>
|
||||
{
|
||||
if (obj is Option<T> option)
|
||||
{
|
||||
if (option.TryUnwrap(out T outValue))
|
||||
{
|
||||
return UserData.Create(outValue);
|
||||
}
|
||||
}
|
||||
|
||||
return DynValue.Nil;
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(dataType, typeof(Option<T>), v =>
|
||||
{
|
||||
return Option<T>.Some(v.ToObject<T>());
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Nil, typeof(Option<T>), v =>
|
||||
{
|
||||
return Option<T>.None();
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterAction<T>()
|
||||
{
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action<T>), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return (Action<T>)(p => Call(function, p));
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action<T>), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return (Action<T>)(p => Call(function, p));
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterAction<T1, T2>()
|
||||
{
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action<T1, T2>), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return (Action<T1, T2>)((a1, a2) => Call(function, a1, a2));
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action<T1, T2>), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return (Action<T1, T2>)((a1, a2) => Call(function, a1, a2));
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterAction()
|
||||
{
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Action), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return (Action)(() => Call(function));
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Action), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return (Action)(() => Call(function));
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterFunc<T1>()
|
||||
{
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Func<T1>), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return () => function.Call().ToObject<T1>();
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Func<T1>), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return () => function.Call().ToObject<T1>();
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterFunc<T1, T2>()
|
||||
{
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Func<T1, T2>), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return (T1 a) => function.Call(a).ToObject<T2>();
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.ClrFunction, typeof(Func<T1, T2>), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return (T1 a) => function.Call(a).ToObject<T2>();
|
||||
});
|
||||
}
|
||||
|
||||
private void RegisterFunc<T1, T2, T3, T4, T5>()
|
||||
{
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Func<T1, T2, T3, T4, T5>), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return (T1 a, T2 b, T3 c, T4 d) => function.Call(a, b, c, d).ToObject<T5>();
|
||||
});
|
||||
|
||||
Script.GlobalOptions.CustomConverters.SetScriptToClrCustomConversion(DataType.Function, typeof(Func<T1, T2, T3, T4, T5>), v =>
|
||||
{
|
||||
var function = v.Function;
|
||||
return (T1 a, T2 b, T3 c, T4 d) => function.Call(a, b, c, d).ToObject<T5>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MoonSharp.Interpreter;
|
||||
using Barotrauma.LuaCs;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
public partial class LuaCsLogger
|
||||
{
|
||||
public static void HandleException(Exception ex, LuaCsMessageOrigin origin)
|
||||
{
|
||||
LuaCsSetup.Instance.Logger.HandleException(ex);
|
||||
}
|
||||
|
||||
public static void LogError(string message, LuaCsMessageOrigin origin)
|
||||
{
|
||||
LuaCsSetup.Instance.Logger.LogError(message);
|
||||
}
|
||||
|
||||
public static void LogError(string message)
|
||||
{
|
||||
LuaCsSetup.Instance.Logger.LogError(message);
|
||||
}
|
||||
|
||||
public static void LogMessage(string message, Color? serverColor = null, Color? clientColor = null)
|
||||
{
|
||||
LuaCsSetup.Instance.Logger.LogMessage(message, serverColor, clientColor);
|
||||
}
|
||||
|
||||
public static void Log(string message, Color? color = null, ServerLog.MessageType messageType = ServerLog.MessageType.ServerMessage)
|
||||
{
|
||||
LuaCsSetup.Instance.Logger.Log(message, color, messageType);
|
||||
}
|
||||
}
|
||||
|
||||
partial class LuaCsSetup
|
||||
{
|
||||
// Compatibility with cs mods that use this method.
|
||||
public static void PrintLuaError(object message) => LuaCsSetup.Instance.Logger.LogError($"{message}");
|
||||
public static void PrintCsError(object message) => LuaCsSetup.Instance.Logger.LogError($"{message}");
|
||||
public static void PrintGenericError(object message) => LuaCsSetup.Instance.Logger.LogError($"{message}");
|
||||
|
||||
internal void PrintMessage(object message) => LuaCsSetup.Instance.Logger.LogMessage($"{message}");
|
||||
|
||||
public static void PrintCsMessage(object message) => LuaCsSetup.Instance.Logger.LogMessage($"{message}");
|
||||
|
||||
internal void HandleException(Exception ex, LuaCsMessageOrigin origin) => LuaCsSetup.Instance.Logger.HandleException(ex);
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
using Barotrauma.LuaCs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public interface IPerformanceData
|
||||
{
|
||||
public string Identifier { get; }
|
||||
public long ElapsedTicks { get; }
|
||||
}
|
||||
|
||||
public class SimplePerformanceData : IPerformanceData
|
||||
{
|
||||
public string Identifier { get; }
|
||||
public long ElapsedTicks { get; }
|
||||
|
||||
public SimplePerformanceData(string identifier, long elapsedTicks)
|
||||
{
|
||||
Identifier = identifier;
|
||||
ElapsedTicks = elapsedTicks;
|
||||
}
|
||||
}
|
||||
|
||||
public class PerformanceCounterService : IReusableService
|
||||
{
|
||||
public bool EnablePerformanceCounter { get; set; } = false;
|
||||
|
||||
private Dictionary<string, List<IPerformanceData>> _data = new Dictionary<string, List<IPerformanceData>>();
|
||||
|
||||
public void AddElapsedTicks(IPerformanceData data)
|
||||
{
|
||||
if (!EnablePerformanceCounter) { return; }
|
||||
|
||||
if (!_data.ContainsKey(data.Identifier))
|
||||
{
|
||||
_data.Add(data.Identifier, new List<IPerformanceData>());
|
||||
}
|
||||
|
||||
_data[data.Identifier].Add(data);
|
||||
|
||||
Trim(data.Identifier, 100);
|
||||
}
|
||||
|
||||
public T GetLatestSnapshot<T>(string identifier) where T : class, IPerformanceData
|
||||
{
|
||||
if (!_data.ContainsKey(identifier)) { return default; }
|
||||
|
||||
return (T)_data[identifier].Last();
|
||||
}
|
||||
|
||||
public T[] GetSnapshot<T>(string identifier, int length) where T : class, IPerformanceData, new()
|
||||
{
|
||||
if (!_data.ContainsKey(identifier)) { return new T[] { }; }
|
||||
|
||||
length = Math.Min(length, _data[identifier].Count);
|
||||
|
||||
return _data[identifier].GetRange(_data[identifier].Count - length, length).Cast<T>().ToArray();
|
||||
}
|
||||
|
||||
public void Trim(string identifier, int maxSize)
|
||||
{
|
||||
if (!_data.ContainsKey(identifier)) { return; }
|
||||
|
||||
if (_data[identifier].Count > maxSize)
|
||||
{
|
||||
_data[identifier].RemoveRange(0, _data[identifier].Count - maxSize);
|
||||
}
|
||||
}
|
||||
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
_data = new Dictionary<string, List<IPerformanceData>>();
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
public bool IsDisposed { get; }
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
using Steamworks;
|
||||
using Steamworks.Data;
|
||||
using Barotrauma.Steam;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using Steamworks.Ugc;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LuaCsSteam
|
||||
{
|
||||
private struct WorkshopItemDownload
|
||||
{
|
||||
public Steamworks.Ugc.Item Item;
|
||||
public string Destination;
|
||||
public LuaCsAction Callback;
|
||||
}
|
||||
|
||||
double lastTimeChecked = 0;
|
||||
List<WorkshopItemDownload> itemsBeingDownloaded = new List<WorkshopItemDownload>();
|
||||
|
||||
public LuaCsSteam()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private static void CopyFolder(string sourceDirName, string destDirName, bool copySubDirs, bool overwriteExisting = false)
|
||||
{
|
||||
// Get the subdirectories for the specified directory.
|
||||
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
|
||||
|
||||
if (!dir.Exists)
|
||||
{
|
||||
throw new System.IO.DirectoryNotFoundException(
|
||||
"Source directory does not exist or could not be found: "
|
||||
+ sourceDirName);
|
||||
}
|
||||
|
||||
IEnumerable<DirectoryInfo> dirs = dir.GetDirectories();
|
||||
// If the destination directory doesn't exist, create it.
|
||||
if (!Directory.Exists(destDirName))
|
||||
{
|
||||
Directory.CreateDirectory(destDirName);
|
||||
}
|
||||
|
||||
// Get the files in the directory and copy them to the new location.
|
||||
IEnumerable<FileInfo> files = dir.GetFiles();
|
||||
foreach (FileInfo file in files)
|
||||
{
|
||||
string tempPath = Path.Combine(destDirName, file.Name);
|
||||
if (!overwriteExisting && File.Exists(tempPath)) { continue; }
|
||||
file.CopyTo(tempPath, true);
|
||||
}
|
||||
|
||||
// If copying subdirectories, copy them and their contents to new location.
|
||||
if (copySubDirs)
|
||||
{
|
||||
foreach (DirectoryInfo subdir in dirs)
|
||||
{
|
||||
string tempPath = Path.Combine(destDirName, subdir.Name);
|
||||
CopyFolder(subdir.FullName, tempPath, copySubDirs, overwriteExisting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void DownloadWorkshopItemAsync(WorkshopItemDownload download, bool startDownload = false)
|
||||
{
|
||||
if (startDownload)
|
||||
{
|
||||
SteamManager.Workshop.NukeDownload(download.Item);
|
||||
SteamUGC.Download(download.Item.Id, true);
|
||||
itemsBeingDownloaded.Add(download);
|
||||
}
|
||||
|
||||
if (download.Item.IsInstalled && Directory.Exists(download.Item.Directory))
|
||||
{
|
||||
if (download.Callback != null)
|
||||
{
|
||||
download.Callback(download.Item);
|
||||
}
|
||||
|
||||
itemsBeingDownloaded.Remove(download);
|
||||
CopyFolder(download.Item.Directory, download.Destination, true, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async void DownloadWorkshopItem(ulong id, string destination, LuaCsAction callback)
|
||||
{
|
||||
if (!LuaCsFile.IsPathAllowedException(destination)) { return; }
|
||||
|
||||
Option<Steamworks.Ugc.Item> itemOption = await SteamManager.Workshop.GetItem(id);
|
||||
|
||||
if (itemOption.TryUnwrap(out Steamworks.Ugc.Item item))
|
||||
{
|
||||
DownloadWorkshopItemAsync(new WorkshopItemDownload()
|
||||
{
|
||||
Item = item,
|
||||
Destination = destination,
|
||||
Callback = callback
|
||||
}, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Tried to download invalid workshop item {id}.");
|
||||
}
|
||||
}
|
||||
|
||||
public void DownloadWorkshopItem(Steamworks.Ugc.Item item, string destination, LuaCsAction callback)
|
||||
{
|
||||
DownloadWorkshopItemAsync(new WorkshopItemDownload()
|
||||
{
|
||||
Item = item,
|
||||
Destination = destination,
|
||||
Callback = callback
|
||||
}, true);
|
||||
}
|
||||
|
||||
public async void GetWorkshopItem(UInt64 id, LuaCsAction callback)
|
||||
{
|
||||
Option<Steamworks.Ugc.Item> itemOption = await SteamManager.Workshop.GetItem(id);
|
||||
|
||||
if (itemOption.TryUnwrap(out Steamworks.Ugc.Item item))
|
||||
{
|
||||
callback(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (itemsBeingDownloaded.Count > 0 && Timing.TotalTime > lastTimeChecked) // SteamUGC.OnDownloadItemResult for some reason doesn't work, so i need to do this stupid thing.
|
||||
{
|
||||
foreach (var item in itemsBeingDownloaded.ToArray())
|
||||
{
|
||||
DownloadWorkshopItemAsync(item);
|
||||
}
|
||||
|
||||
lastTimeChecked = Timing.TotalTime + 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
using Barotrauma.LuaCs.Events;
|
||||
using Barotrauma.LuaCs;
|
||||
using Barotrauma.LuaCs.Compatibility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class LuaCsTimer : ILuaCsTimer, IEventUpdate
|
||||
{
|
||||
public static double Time => Timing.TotalTime;
|
||||
public static double GetTime() => Time;
|
||||
public static double AccumulatorMax
|
||||
{
|
||||
get
|
||||
{
|
||||
return Timing.AccumulatorMax;
|
||||
}
|
||||
set
|
||||
{
|
||||
Timing.AccumulatorMax = value;
|
||||
}
|
||||
}
|
||||
|
||||
private class TimerComparer : IComparer<TimedAction>
|
||||
{
|
||||
public int Compare(TimedAction timedAction1, TimedAction timedAction2)
|
||||
{
|
||||
if (timedAction1 == null || timedAction2 == null)
|
||||
return 0;
|
||||
return -Math.Sign(timedAction2.ExecutionTime - timedAction1.ExecutionTime);
|
||||
}
|
||||
}
|
||||
|
||||
private class TimedAction
|
||||
{
|
||||
public LuaCsAction Action
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public double ExecutionTime
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public TimedAction(LuaCsAction action, int delayMs)
|
||||
{
|
||||
this.Action = action;
|
||||
ExecutionTime = Time + (delayMs / 1000f);
|
||||
}
|
||||
}
|
||||
|
||||
private List<TimedAction> timedActions = new List<TimedAction>();
|
||||
|
||||
private readonly IEventService _eventService;
|
||||
private readonly ILoggerService _loggerService;
|
||||
|
||||
public LuaCsTimer(IEventService eventService, ILoggerService loggerService)
|
||||
{
|
||||
_eventService = eventService;
|
||||
_loggerService = loggerService;
|
||||
SubscribeToEvents();
|
||||
}
|
||||
|
||||
private void AddTimer(TimedAction timedAction)
|
||||
{
|
||||
if (timedAction == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(timedAction));
|
||||
}
|
||||
|
||||
lock (timedActions)
|
||||
{
|
||||
int insertionPoint = timedActions.BinarySearch(timedAction, new TimerComparer());
|
||||
|
||||
if (insertionPoint < 0)
|
||||
{
|
||||
insertionPoint = ~insertionPoint;
|
||||
}
|
||||
|
||||
timedActions.Insert(insertionPoint, timedAction);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
timedActions = new List<TimedAction>();
|
||||
}
|
||||
|
||||
public void Wait(LuaCsAction action, int millisecondDelay)
|
||||
{
|
||||
TimedAction timedAction = new TimedAction(action, millisecondDelay);
|
||||
AddTimer(timedAction);
|
||||
}
|
||||
|
||||
public void NextFrame(LuaCsAction action)
|
||||
{
|
||||
TimedAction timedAction = new TimedAction(action, 0);
|
||||
AddTimer(timedAction);
|
||||
}
|
||||
|
||||
public void OnUpdate(double fixedDeltaTime)
|
||||
{
|
||||
lock (timedActions)
|
||||
{
|
||||
TimedAction[] timedCopy = timedActions.ToArray();
|
||||
for (int i = 0; i < timedCopy.Length; i++)
|
||||
{
|
||||
TimedAction timedAction = timedCopy[i];
|
||||
if (Time >= timedAction.ExecutionTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
timedAction.Action();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_loggerService.HandleException(e);
|
||||
}
|
||||
|
||||
timedActions.Remove(timedAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeToEvents()
|
||||
{
|
||||
_eventService.Subscribe<IEventUpdate>(this);
|
||||
}
|
||||
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
SubscribeToEvents();
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_eventService.Unsubscribe<IEventUpdate>(this);
|
||||
}
|
||||
|
||||
public bool IsDisposed => false;
|
||||
}
|
||||
}
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using MoonSharp.Interpreter;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.LuaCs;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LuaCsFile
|
||||
{
|
||||
public static bool CanReadFromPath(string path)
|
||||
{
|
||||
string getFullPath(string p) => System.IO.Path.GetFullPath(p).CleanUpPath();
|
||||
|
||||
path = getFullPath(path);
|
||||
|
||||
bool pathStartsWith(string prefix) => path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
string localModsDir = getFullPath(ContentPackage.LocalModsDir);
|
||||
string workshopModsDir = getFullPath(ContentPackage.WorkshopModsDir);
|
||||
#if CLIENT
|
||||
string tempDownloadDir = getFullPath(ModReceiver.DownloadFolder);
|
||||
#endif
|
||||
if (pathStartsWith(getFullPath(string.IsNullOrEmpty(GameSettings.CurrentConfig.SavePath) ? SaveUtil.DefaultSaveFolder : GameSettings.CurrentConfig.SavePath)))
|
||||
return true;
|
||||
|
||||
if (pathStartsWith(localModsDir))
|
||||
return true;
|
||||
|
||||
if (pathStartsWith(workshopModsDir))
|
||||
return true;
|
||||
|
||||
#if CLIENT
|
||||
if (pathStartsWith(tempDownloadDir))
|
||||
return true;
|
||||
#endif
|
||||
|
||||
if (pathStartsWith(getFullPath(".")))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool CanWriteToPath(string path)
|
||||
{
|
||||
const long LuaCsPackageId = 2559634234;
|
||||
|
||||
string getFullPath(string p) => System.IO.Path.GetFullPath(p).CleanUpPath();
|
||||
|
||||
path = getFullPath(path);
|
||||
|
||||
bool pathStartsWith(string prefix) => path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (pathStartsWith(getFullPath(LuaCsSetup.GetLuaCsPackage().Path)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pathStartsWith(getFullPath(string.IsNullOrEmpty(GameSettings.CurrentConfig.SavePath) ? SaveUtil.DefaultSaveFolder : GameSettings.CurrentConfig.SavePath)))
|
||||
return true;
|
||||
|
||||
if (pathStartsWith(getFullPath(ContentPackage.LocalModsDir)))
|
||||
return true;
|
||||
|
||||
if (pathStartsWith(getFullPath(ContentPackage.WorkshopModsDir)))
|
||||
return true;
|
||||
#if CLIENT
|
||||
if (pathStartsWith(getFullPath(ModReceiver.DownloadFolder)))
|
||||
return true;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsPathAllowedException(string path, bool write = true, LuaCsMessageOrigin origin = LuaCsMessageOrigin.Unknown)
|
||||
{
|
||||
if (write)
|
||||
{
|
||||
if (CanWriteToPath(path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("File access to \"" + path + "\" not allowed.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CanReadFromPath(path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("File access to \"" + path + "\" not allowed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsPathAllowedLuaException(string path, bool write = true) =>
|
||||
IsPathAllowedException(path, write, LuaCsMessageOrigin.LuaMod);
|
||||
public static bool IsPathAllowedCsException(string path, bool write = true) =>
|
||||
IsPathAllowedException(path, write, LuaCsMessageOrigin.CSharpMod);
|
||||
|
||||
public static string Read(string path)
|
||||
{
|
||||
if (!IsPathAllowedException(path, false))
|
||||
return "";
|
||||
|
||||
return File.ReadAllText(path);
|
||||
}
|
||||
|
||||
public static void Write(string path, string text)
|
||||
{
|
||||
if (!IsPathAllowedException(path))
|
||||
return;
|
||||
|
||||
File.WriteAllText(path, text);
|
||||
}
|
||||
|
||||
public static void Delete(string path)
|
||||
{
|
||||
if (!IsPathAllowedException(path))
|
||||
return;
|
||||
|
||||
File.Delete(path);
|
||||
}
|
||||
|
||||
public static void DeleteDirectory(string path)
|
||||
{
|
||||
if (!IsPathAllowedException(path))
|
||||
return;
|
||||
|
||||
Directory.Delete(path, true);
|
||||
}
|
||||
|
||||
public static void Move(string path, string destination)
|
||||
{
|
||||
if (!IsPathAllowedException(path))
|
||||
return;
|
||||
|
||||
if (!IsPathAllowedException(destination))
|
||||
return;
|
||||
|
||||
File.Move(path, destination, true);
|
||||
}
|
||||
|
||||
public static FileStream OpenRead(string path)
|
||||
{
|
||||
if (!IsPathAllowedException(path))
|
||||
return null;
|
||||
|
||||
return File.Open(path, FileMode.Open, FileAccess.Read);
|
||||
}
|
||||
public static FileStream OpenWrite(string path)
|
||||
{
|
||||
if (!IsPathAllowedException(path))
|
||||
return null;
|
||||
|
||||
if (File.Exists(path)) return File.Open(path, FileMode.Truncate, FileAccess.Write);
|
||||
else return File.Open(path, FileMode.Create, FileAccess.Write);
|
||||
}
|
||||
|
||||
public static bool Exists(string path)
|
||||
{
|
||||
if (!IsPathAllowedException(path, false))
|
||||
return false;
|
||||
|
||||
return File.Exists(path);
|
||||
}
|
||||
|
||||
public static bool CreateDirectory(string path)
|
||||
{
|
||||
if (!IsPathAllowedException(path))
|
||||
return false;
|
||||
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool DirectoryExists(string path)
|
||||
{
|
||||
if (!IsPathAllowedException(path, false))
|
||||
return false;
|
||||
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
|
||||
public static string[] GetFiles(string path)
|
||||
{
|
||||
if (!IsPathAllowedException(path, false))
|
||||
return null;
|
||||
|
||||
return Directory.GetFiles(path);
|
||||
}
|
||||
|
||||
public static string[] GetDirectories(string path)
|
||||
{
|
||||
if (!IsPathAllowedException(path, false))
|
||||
return new string[] { };
|
||||
|
||||
return Directory.GetDirectories(path);
|
||||
}
|
||||
|
||||
public static string[] DirSearch(string sDir)
|
||||
{
|
||||
if (!IsPathAllowedException(sDir, false))
|
||||
return new string[] { };
|
||||
|
||||
List<string> files = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (string f in Directory.GetFiles(sDir))
|
||||
{
|
||||
files.Add(f);
|
||||
}
|
||||
|
||||
foreach (string d in Directory.GetDirectories(sDir))
|
||||
{
|
||||
foreach (string f in Directory.GetFiles(d))
|
||||
{
|
||||
files.Add(f);
|
||||
}
|
||||
DirSearch(d);
|
||||
}
|
||||
}
|
||||
catch (System.Exception excpt)
|
||||
{
|
||||
Console.WriteLine(excpt.Message);
|
||||
}
|
||||
|
||||
return files.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,560 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.LuaCs;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace Barotrauma.LuaCs
|
||||
{
|
||||
partial class LuaGame : IReusableService
|
||||
{
|
||||
public bool IsSingleplayer => GameMain.IsSingleplayer;
|
||||
public bool IsMultiplayer => GameMain.IsMultiplayer;
|
||||
public string SaveFolder => string.IsNullOrEmpty(GameSettings.CurrentConfig.SavePath) ? SaveUtil.DefaultSaveFolder : GameSettings.CurrentConfig.SavePath;
|
||||
|
||||
#if CLIENT
|
||||
public GameClient Client
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.Client;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Paused => GameMain.Instance?.Paused == true;
|
||||
public byte SessionId => GameMain.Client.SessionId;
|
||||
public byte MyID => SessionId; // compatibility
|
||||
|
||||
public ChatMode ActiveChatMode => GameMain.ActiveChatMode;
|
||||
|
||||
public ChatBox ChatBox
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GameMain.IsSingleplayer)
|
||||
return GameMain.GameSession.CrewManager.ChatBox;
|
||||
else
|
||||
return GameMain.Client.ChatBox;
|
||||
}
|
||||
}
|
||||
|
||||
public Sounds.SoundManager SoundManager
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.SoundManager;
|
||||
}
|
||||
}
|
||||
|
||||
public Lights.LightManager LightManager
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.LightManager;
|
||||
}
|
||||
}
|
||||
|
||||
public SubEditorScreen SubEditorScreen
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.SubEditorScreen;
|
||||
}
|
||||
}
|
||||
|
||||
public MainMenuScreen MainMenuScreen
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.MainMenuScreen;
|
||||
}
|
||||
}
|
||||
|
||||
public Particles.ParticleManager ParticleManager
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.ParticleManager;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSubEditor
|
||||
{
|
||||
get
|
||||
{
|
||||
return Screen.Selected is SubEditorScreen;
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
public GameServer Server
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.Server;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDedicated
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.Server.ServerPeer is LidgrenServerPeer;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Paused => false;
|
||||
#endif
|
||||
|
||||
public ServerSettings ServerSettings
|
||||
{
|
||||
get
|
||||
{
|
||||
#if SERVER
|
||||
return GameMain.Server.ServerSettings;
|
||||
#else
|
||||
return GameMain.Client.ServerSettings;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public RespawnManager RespawnManager
|
||||
{
|
||||
get
|
||||
{
|
||||
#if SERVER
|
||||
return GameMain.Server.RespawnManager;
|
||||
#else
|
||||
return GameMain.Client.RespawnManager;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public List<DebugConsole.Command> Commands => DebugConsole.Commands;
|
||||
|
||||
public bool? ForceVoice = null;
|
||||
public bool? ForceLocalVoice = null;
|
||||
|
||||
public DynValue Settings;
|
||||
|
||||
public bool allowWifiChat = false;
|
||||
public bool overrideTraitors = false;
|
||||
public bool overrideRespawnSub = false;
|
||||
public bool overrideSignalRadio = false;
|
||||
public bool disableSpamFilter = false;
|
||||
public bool disableDisconnectCharacter = false;
|
||||
public bool enableControlHusk = false;
|
||||
public int MapEntityUpdateInterval
|
||||
{
|
||||
get { return MapEntity.MapEntityUpdateInterval; }
|
||||
set { MapEntity.MapEntityUpdateInterval = value; }
|
||||
}
|
||||
|
||||
public int GapUpdateInterval
|
||||
{
|
||||
get { return 1; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public int PoweredUpdateInterval
|
||||
{
|
||||
get { return MapEntity.PoweredUpdateInterval; }
|
||||
set { MapEntity.PoweredUpdateInterval = value; }
|
||||
}
|
||||
|
||||
public int CharacterUpdateInterval
|
||||
{
|
||||
get { return Character.CharacterUpdateInterval; }
|
||||
set { Character.CharacterUpdateInterval = value; }
|
||||
}
|
||||
|
||||
|
||||
public HashSet<Item> UpdatePriorityItems = new HashSet<Item>();
|
||||
public HashSet<Character> UpdatePriorityCharacters = new HashSet<Character>();
|
||||
|
||||
public void AddPriorityItem(Item item)
|
||||
{
|
||||
UpdatePriorityItems.Add(item);
|
||||
}
|
||||
|
||||
public void RemovePriorityItem(Item item)
|
||||
{
|
||||
UpdatePriorityItems.Remove(item);
|
||||
}
|
||||
|
||||
public void ClearPriorityItem()
|
||||
{
|
||||
UpdatePriorityItems.Clear();
|
||||
}
|
||||
|
||||
public void AddPriorityCharacter(Character character)
|
||||
{
|
||||
UpdatePriorityCharacters.Add(character);
|
||||
}
|
||||
|
||||
public void RemovePriorityCharacter(Character character)
|
||||
{
|
||||
UpdatePriorityCharacters.Remove(character);
|
||||
}
|
||||
|
||||
public void ClearPriorityCharacter()
|
||||
{
|
||||
UpdatePriorityCharacters.Clear();
|
||||
}
|
||||
|
||||
public bool RoundStarted
|
||||
{
|
||||
|
||||
get
|
||||
{
|
||||
if (GameMain.IsSingleplayer) { return GameMain.GameSession != null && GameMain.GameSession.IsRunning; }
|
||||
#if SERVER
|
||||
return GameMain.Server?.GameStarted == true;
|
||||
#else
|
||||
return GameMain.Client?.GameStarted == true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public GameSession GameSession
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.GameSession;
|
||||
}
|
||||
}
|
||||
|
||||
public NetLobbyScreen NetLobbyScreen
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.NetLobbyScreen;
|
||||
}
|
||||
}
|
||||
|
||||
public GameScreen GameScreen
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.GameScreen;
|
||||
}
|
||||
}
|
||||
|
||||
public World World
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.World;
|
||||
}
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
public ServerPeer Peer
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.Server.ServerPeer;
|
||||
}
|
||||
}
|
||||
#else
|
||||
public ClientPeer Peer
|
||||
{
|
||||
get
|
||||
{
|
||||
return GameMain.Client.ClientPeer;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private readonly IConsoleCommandsService _consoleCommands;
|
||||
|
||||
public LuaGame(IConsoleCommandsService consoleCommands)
|
||||
{
|
||||
UserData.RegisterType(typeof(GameSettings));
|
||||
Settings = UserData.CreateStatic(typeof(GameSettings));
|
||||
_consoleCommands = consoleCommands;
|
||||
}
|
||||
|
||||
public void OverrideTraitors(bool o)
|
||||
{
|
||||
overrideTraitors = o;
|
||||
}
|
||||
|
||||
public void OverrideRespawnSub(bool o)
|
||||
{
|
||||
overrideRespawnSub = o;
|
||||
}
|
||||
|
||||
public void AllowWifiChat(bool o)
|
||||
{
|
||||
allowWifiChat = o;
|
||||
}
|
||||
|
||||
public void OverrideSignalRadio(bool o)
|
||||
{
|
||||
overrideSignalRadio = o;
|
||||
}
|
||||
|
||||
public void DisableSpamFilter(bool o)
|
||||
{
|
||||
disableSpamFilter = o;
|
||||
}
|
||||
|
||||
public void DisableDisconnectCharacter(bool o)
|
||||
{
|
||||
disableDisconnectCharacter = o;
|
||||
}
|
||||
|
||||
|
||||
public void EnableControlHusk(bool o)
|
||||
{
|
||||
enableControlHusk = o;
|
||||
}
|
||||
|
||||
public static void Explode(Vector2 pos, float range = 100, float force = 30, float damage = 30, float structureDamage = 30, float itemDamage = 30, float empStrength = 0, float ballastFloraStrength = 0)
|
||||
{
|
||||
new Explosion(range, force, damage, structureDamage, itemDamage, empStrength, ballastFloraStrength).Explode(pos, null);
|
||||
}
|
||||
|
||||
public static string SpawnItem(string name, Vector2 pos, bool inventory = false, Character character = null)
|
||||
{
|
||||
string error;
|
||||
DebugConsole.SpawnItem(new string[] { name, inventory ? "inventory" : "cursor" }, pos, character, out error);
|
||||
return error;
|
||||
}
|
||||
|
||||
public static ContentPackage[] GetEnabledContentPackages()
|
||||
{
|
||||
return ContentPackageManager.EnabledPackages.All.ToArray();
|
||||
}
|
||||
|
||||
public static ItemPrefab GetItemPrefab(string itemNameOrId)
|
||||
{
|
||||
ItemPrefab itemPrefab =
|
||||
(MapEntityPrefab.Find(itemNameOrId, identifier: null, showErrorMessages: false) ??
|
||||
MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab;
|
||||
|
||||
return itemPrefab;
|
||||
}
|
||||
|
||||
public static Submarine GetRespawnSub()
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server.RespawnManager == null) { return null; }
|
||||
return GameMain.Server.RespawnManager.GetShuttle(CharacterTeamType.Team1);
|
||||
#else
|
||||
if (GameMain.Client.RespawnManager == null) { return null; }
|
||||
return GameMain.Client.RespawnManager.GetShuttle(CharacterTeamType.Team1);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Items.Components.Steering GetSubmarineSteering(Submarine sub)
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != sub) continue;
|
||||
|
||||
var steering = item.GetComponent<Items.Components.Steering>();
|
||||
if (steering != null)
|
||||
{
|
||||
return steering;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static WifiComponent GetWifiComponent(Item item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
return item.GetComponent<WifiComponent>();
|
||||
}
|
||||
|
||||
public static LightComponent GetLightComponent(Item item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
return item.GetComponent<LightComponent>();
|
||||
}
|
||||
|
||||
public static CustomInterface GetCustomInterface(Item item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
return item.GetComponent<CustomInterface>();
|
||||
}
|
||||
|
||||
public static Fabricator GetFabricatorComponent(Item item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
return item.GetComponent<Fabricator>();
|
||||
}
|
||||
|
||||
public static Holdable GetHoldableComponent(Item item)
|
||||
{
|
||||
if (item == null) return null;
|
||||
return item.GetComponent<Holdable>();
|
||||
}
|
||||
|
||||
public static void ExecuteCommand(string command)
|
||||
{
|
||||
DebugConsole.ExecuteCommand(command);
|
||||
}
|
||||
|
||||
public static Signal CreateSignal(string value, int stepsTaken = 1, Character sender = null, Item source = null, float power = 0, float strength = 1)
|
||||
{
|
||||
return new Signal(value, stepsTaken, sender, source, power, strength);
|
||||
}
|
||||
|
||||
public void RemoveCommand(string name)
|
||||
{
|
||||
_consoleCommands.RemoveCommand(name);
|
||||
|
||||
for (var i = DebugConsole.Commands.Count - 1; i >= 0; i--)
|
||||
{
|
||||
foreach (var cmdname in DebugConsole.Commands[i].Names)
|
||||
{
|
||||
if (cmdname == name)
|
||||
{
|
||||
DebugConsole.Commands.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddCommand(string name, string help, LuaCsAction onExecute, LuaCsFunc getValidArgs = null, bool isCheat = false)
|
||||
{
|
||||
_consoleCommands.RegisterCommand(name, help,
|
||||
(string[] args) =>
|
||||
{
|
||||
onExecute(new object[] { args });
|
||||
},
|
||||
() =>
|
||||
{
|
||||
if (getValidArgs == null) { return null; }
|
||||
var validArgs = getValidArgs();
|
||||
if (validArgs is DynValue luaValue)
|
||||
{
|
||||
return luaValue.ToObject<string[][]>();
|
||||
}
|
||||
return (string[][])validArgs;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void AddCommand(string name, LuaCsAction onExecute, LuaCsFunc getValidArgs = null, bool isCheat = false)
|
||||
{
|
||||
_consoleCommands.RegisterCommand(name, "",
|
||||
(string[] args) =>
|
||||
{
|
||||
onExecute(new object[] { args });
|
||||
},
|
||||
() =>
|
||||
{
|
||||
if (getValidArgs == null) { return null; }
|
||||
var validArgs = getValidArgs();
|
||||
if (validArgs is DynValue luaValue)
|
||||
{
|
||||
return luaValue.ToObject<string[][]>();
|
||||
}
|
||||
return (string[][])validArgs;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public bool IsDisposed => throw new NotImplementedException();
|
||||
|
||||
public void AssignOnExecute(string names, object onExecute) => DebugConsole.AssignOnExecute(names, (string[] args) =>
|
||||
{
|
||||
LuaCsSetup.Instance.LuaScriptManagementService.CallFunctionSafe(onExecute, new object[] { args });
|
||||
});
|
||||
|
||||
public void SaveGame(string path)
|
||||
{
|
||||
if (!LuaCsFile.CanWriteToPath(path)) { throw new ScriptRuntimeException($"Saving files to {path} is disallowed."); }
|
||||
SaveUtil.SaveGame(CampaignDataPath.CreateRegular(path));
|
||||
}
|
||||
|
||||
public void LoadGame(string path)
|
||||
{
|
||||
SaveUtil.LoadGame(CampaignDataPath.CreateRegular(path));
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
public void LoadCampaign(string path, Client client = null)
|
||||
{
|
||||
MultiPlayerCampaign.LoadCampaign(CampaignDataPath.CreateRegular(path), client);
|
||||
}
|
||||
|
||||
public static void SendMessage(string msg, ChatMessageType? messageType = null, Client sender = null, Character character = null)
|
||||
{
|
||||
GameMain.Server.SendChatMessage(msg, messageType, sender, character);
|
||||
}
|
||||
|
||||
public static void SendTraitorMessage(WriteOnlyMessage message, Client client)
|
||||
{
|
||||
GameMain.Server.SendTraitorMessage(message, client);
|
||||
}
|
||||
|
||||
public static void SendDirectChatMessage(string sendername, string text, Character sender, ChatMessageType messageType = ChatMessageType.Private, Client client = null, string iconStyle = "")
|
||||
{
|
||||
ChatMessage cm = ChatMessage.Create(sendername, text, messageType, sender);
|
||||
cm.IconStyle = iconStyle;
|
||||
GameMain.Server.SendDirectChatMessage(cm, client);
|
||||
}
|
||||
|
||||
public static void SendDirectChatMessage(ChatMessage chatMessage, Client client)
|
||||
{
|
||||
GameMain.Server.SendDirectChatMessage(chatMessage, client);
|
||||
}
|
||||
|
||||
public static void Log(string message, ServerLog.MessageType type)
|
||||
{
|
||||
GameServer.Log(message, type);
|
||||
}
|
||||
|
||||
public static void DispatchRespawnSub()
|
||||
{
|
||||
GameMain.Server.RespawnManager.DispatchShuttle(GameMain.Server.RespawnManager.GetTeamSpecificState(CharacterTeamType.Team1));
|
||||
}
|
||||
|
||||
public static GameServer.TryStartGameResult StartGame()
|
||||
{
|
||||
return GameMain.Server.TryStartGame();
|
||||
}
|
||||
|
||||
public static void EndGame()
|
||||
{
|
||||
GameMain.Server.EndGame();
|
||||
}
|
||||
|
||||
public void AssignOnClientRequestExecute(string names, LuaCsAction onExecute) =>
|
||||
_consoleCommands.AssignOnClientRequestExecute(names, (Client client, Vector2 position, string[] args) => onExecute(client, position, args));
|
||||
|
||||
#endif
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
MapEntityUpdateInterval = 1;
|
||||
CharacterUpdateInterval = 1;
|
||||
|
||||
_consoleCommands.RemoveRegisteredCommands();
|
||||
}
|
||||
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
Stop();
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
using MoonSharp.Interpreter.Platforms;
|
||||
using MoonSharp.Interpreter;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class LuaPlatformAccessor : PlatformAccessorBase
|
||||
{
|
||||
public static FileMode ParseFileMode(string mode)
|
||||
{
|
||||
mode = mode.Replace("b", "");
|
||||
|
||||
if (mode == "r")
|
||||
return FileMode.Open;
|
||||
else if (mode == "r+")
|
||||
return FileMode.OpenOrCreate;
|
||||
else if (mode == "w")
|
||||
return FileMode.Create;
|
||||
else if (mode == "w+")
|
||||
return FileMode.Truncate;
|
||||
else
|
||||
return FileMode.Append;
|
||||
}
|
||||
|
||||
public static FileAccess ParseFileAccess(string mode)
|
||||
{
|
||||
mode = mode.Replace("b", "");
|
||||
|
||||
if (mode == "r")
|
||||
return FileAccess.Read;
|
||||
else if (mode == "r+")
|
||||
return FileAccess.ReadWrite;
|
||||
else if (mode == "w")
|
||||
return FileAccess.ReadWrite;
|
||||
else if (mode == "w+")
|
||||
return FileAccess.ReadWrite;
|
||||
else
|
||||
return FileAccess.Write;
|
||||
}
|
||||
|
||||
public override string GetEnvironmentVariable(string envvarname)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public override CoreModules FilterSupportedCoreModules(CoreModules module)
|
||||
{
|
||||
return module;
|
||||
}
|
||||
|
||||
public override Stream IO_OpenFile(Script script, string filename, Encoding encoding, string mode)
|
||||
{
|
||||
if (!LuaCsFile.IsPathAllowedLuaException(filename)) { return Stream.Null; }
|
||||
|
||||
FileStream stream = new FileStream(filename, ParseFileMode(mode), ParseFileAccess(mode), FileShare.ReadWrite | FileShare.Delete);
|
||||
return stream;
|
||||
}
|
||||
|
||||
public override Stream IO_GetStandardStream(StandardFileType type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case StandardFileType.StdIn:
|
||||
return Console.OpenStandardInput();
|
||||
case StandardFileType.StdOut:
|
||||
return Console.OpenStandardOutput();
|
||||
case StandardFileType.StdErr:
|
||||
return Console.OpenStandardError();
|
||||
default:
|
||||
throw new ArgumentException("type");
|
||||
}
|
||||
}
|
||||
|
||||
public override string IO_OS_GetTempFilename()
|
||||
{
|
||||
return "LocalMods/temp.txt";
|
||||
}
|
||||
|
||||
public override void OS_ExitFast(int exitCode)
|
||||
{
|
||||
throw new ScriptRuntimeException("usage of os.exit is not allowed.");
|
||||
}
|
||||
|
||||
public override bool OS_FileExists(string file)
|
||||
{
|
||||
return LuaCsFile.Exists(file);
|
||||
}
|
||||
|
||||
public override void OS_FileDelete(string file)
|
||||
{
|
||||
LuaCsFile.Delete(file);
|
||||
}
|
||||
|
||||
public override void OS_FileMove(string src, string dst)
|
||||
{
|
||||
LuaCsFile.Move(src, dst);
|
||||
}
|
||||
|
||||
public override int OS_Execute(string cmdline)
|
||||
{
|
||||
throw new ScriptRuntimeException("usage of os.execute is not allowed.");
|
||||
}
|
||||
|
||||
public override string GetPlatformNamePrefix()
|
||||
{
|
||||
return "lua";
|
||||
}
|
||||
|
||||
public override void DefaultPrint(string content)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using MoonSharp.Interpreter;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LuaRequire
|
||||
{
|
||||
private Script lua { get; set; }
|
||||
private Dictionary<string, DynValue> loadedModules { get; set; }
|
||||
|
||||
private bool GetExistingReturnValue(string moduleName, ref DynValue returnValue)
|
||||
{
|
||||
return loadedModules.TryGetValue(
|
||||
moduleName,
|
||||
out returnValue
|
||||
);
|
||||
}
|
||||
|
||||
private string FixContentPackagePath(string contentPackagePath)
|
||||
{
|
||||
contentPackagePath = Path.TrimEndingDirectorySeparator(
|
||||
new FileInfo(contentPackagePath) // filelist.xml
|
||||
.Directory
|
||||
.FullName
|
||||
.CleanUpPathCrossPlatform()
|
||||
);
|
||||
|
||||
return contentPackagePath;
|
||||
}
|
||||
private string GetContentPackagePath(string path)
|
||||
{
|
||||
IEnumerable<ContentPackage> allContentPackages = ContentPackageManager.AllPackages;
|
||||
foreach (ContentPackage contentPackage in allContentPackages)
|
||||
{
|
||||
string contentPackagePath = FixContentPackagePath(contentPackage.Path);
|
||||
if (path.StartsWith(contentPackagePath))
|
||||
{
|
||||
return contentPackagePath;
|
||||
}
|
||||
}
|
||||
|
||||
// Return null if we can't find a content package that
|
||||
// this module belongs to.
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetContentPackagePath(string moduleName, Table environment)
|
||||
{
|
||||
string filePath = lua.Options
|
||||
.ScriptLoader
|
||||
.ResolveModuleName(
|
||||
moduleName,
|
||||
environment
|
||||
);
|
||||
filePath = Path.TrimEndingDirectorySeparator(
|
||||
new FileInfo(filePath)
|
||||
.Directory
|
||||
.FullName
|
||||
.CleanUpPathCrossPlatform()
|
||||
);
|
||||
|
||||
return GetContentPackagePath(filePath);
|
||||
}
|
||||
|
||||
private void SaveReturnValue(string moduleName, DynValue returnValue)
|
||||
{
|
||||
loadedModules[moduleName] = returnValue;
|
||||
}
|
||||
|
||||
private void ExecuteModule(string moduleName, Table environment, ref DynValue returnValue)
|
||||
{
|
||||
DynValue loadFunc = lua.RequireModule(
|
||||
moduleName,
|
||||
environment
|
||||
);
|
||||
string packagePath = GetContentPackagePath(
|
||||
moduleName,
|
||||
environment
|
||||
);
|
||||
|
||||
returnValue = lua.Call(
|
||||
loadFunc,
|
||||
packagePath
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
// Lua modules that have been previously loaded by require() will
|
||||
// not be loaded again; instead, their initial return value is
|
||||
// preserved and returned again on subsequent attempts.
|
||||
public DynValue Require(string moduleName, Table globalContext)
|
||||
{
|
||||
DynValue returnValue = null;
|
||||
Table environment = globalContext ?? lua.Globals;
|
||||
|
||||
if (GetExistingReturnValue(moduleName, ref returnValue))
|
||||
return returnValue;
|
||||
|
||||
ExecuteModule(moduleName, environment, ref returnValue);
|
||||
if (
|
||||
returnValue == null
|
||||
|| returnValue.IsNil()
|
||||
|| returnValue.IsVoid()
|
||||
)
|
||||
returnValue = DynValue.NewBoolean(true);
|
||||
SaveReturnValue(moduleName, returnValue);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
public LuaRequire(Script lua)
|
||||
{
|
||||
this.lua = lua;
|
||||
loadedModules = new Dictionary<string, DynValue>();
|
||||
}
|
||||
}
|
||||
}
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MoonSharp.Interpreter;
|
||||
using MoonSharp.Interpreter.Interop;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class LuaSafeUserData
|
||||
{
|
||||
public IUserDataDescriptor this[string index]
|
||||
{
|
||||
get => LuaUserData.Descriptors.GetValueOrDefault(index);
|
||||
}
|
||||
|
||||
private static bool CanBeRegistered(string typeName)
|
||||
{
|
||||
if (typeName.StartsWith("Barotrauma.Lua", StringComparison.Ordinal) ||
|
||||
typeName.StartsWith("Barotrauma.Cs", StringComparison.Ordinal) ||
|
||||
typeName.StartsWith("Barotrauma.LuaCs", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeName == "System.Single") { return true; }
|
||||
|
||||
if (typeName.StartsWith("System.Collections", StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
if (typeName.StartsWith("Microsoft.Xna", StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
if (typeName.StartsWith("Barotrauma.IO", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
if (typeName.StartsWith("Barotrauma.ToolBox", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
if (typeName.StartsWith("Barotrauma.SaveUtil", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
if (typeName.StartsWith("Barotrauma.", StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool CanBeReRegistered(string typeName)
|
||||
{
|
||||
if (typeName.StartsWith("Barotrauma.Lua", StringComparison.Ordinal) ||
|
||||
typeName.StartsWith("Barotrauma.Cs", StringComparison.Ordinal) ||
|
||||
typeName.StartsWith("Barotrauma.LuaCs", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsAllowed(string typeName)
|
||||
{
|
||||
if (!CanBeReRegistered(typeName) && LuaUserData.IsRegistered(typeName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CanBeRegistered(typeName) && !LuaUserData.IsRegistered(typeName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void CheckAllowed(string typeName)
|
||||
{
|
||||
if (!IsAllowed(typeName))
|
||||
{
|
||||
throw new ScriptRuntimeException($"Type {typeName} can't be registered");
|
||||
}
|
||||
}
|
||||
|
||||
public static Type GetType(string typeName)
|
||||
{
|
||||
CheckAllowed(typeName);
|
||||
|
||||
return LuaUserData.GetType(typeName);
|
||||
}
|
||||
|
||||
public static IUserDataDescriptor RegisterType(string typeName)
|
||||
{
|
||||
CheckAllowed(typeName);
|
||||
|
||||
return LuaUserData.RegisterType(typeName);
|
||||
}
|
||||
|
||||
public static IUserDataDescriptor RegisterTypeBarotrauma(string typeName)
|
||||
{
|
||||
return RegisterType($"Barotrauma.{typeName}");
|
||||
}
|
||||
|
||||
public static void RegisterExtensionType(string typeName)
|
||||
{
|
||||
CheckAllowed(typeName);
|
||||
LuaUserData.RegisterExtensionType(typeName);
|
||||
}
|
||||
|
||||
public static bool IsRegistered(string typeName)
|
||||
{
|
||||
return LuaUserData.IsRegistered(typeName);
|
||||
}
|
||||
|
||||
public static void UnregisterType(string typeName, bool deleteHistory = false)
|
||||
{
|
||||
LuaUserData.UnregisterType(typeName, deleteHistory);
|
||||
}
|
||||
public static IUserDataDescriptor RegisterGenericType(string typeName, params string[] typeNameArguements)
|
||||
{
|
||||
CheckAllowed(typeName);
|
||||
return LuaUserData.RegisterGenericType(typeName, typeNameArguements);
|
||||
}
|
||||
|
||||
public static void UnregisterGenericType(string typeName, params string[] typeNameArguements)
|
||||
{
|
||||
LuaUserData.UnregisterGenericType(typeName, typeNameArguements);
|
||||
}
|
||||
|
||||
public static bool IsTargetType(object obj, string typeName)
|
||||
{
|
||||
return LuaUserData.IsTargetType(obj, typeName);
|
||||
}
|
||||
|
||||
public static string TypeOf(object obj)
|
||||
{
|
||||
return LuaUserData.TypeOf(obj);
|
||||
}
|
||||
|
||||
public static object CreateStatic(string typeName)
|
||||
{
|
||||
CheckAllowed(typeName);
|
||||
return LuaUserData.CreateStatic(typeName);
|
||||
}
|
||||
|
||||
public static object CreateEnumTable(string typeName)
|
||||
{
|
||||
return LuaUserData.CreateEnumTable(typeName);
|
||||
}
|
||||
|
||||
public static void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName)
|
||||
{
|
||||
LuaUserData.MakeFieldAccessible(IUUD, fieldName);
|
||||
}
|
||||
|
||||
public static void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null)
|
||||
{
|
||||
LuaUserData.MakeMethodAccessible(IUUD, methodName, parameters);
|
||||
}
|
||||
|
||||
public static void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName)
|
||||
{
|
||||
LuaUserData.MakePropertyAccessible(IUUD, propertyName);
|
||||
}
|
||||
|
||||
public static void AddMethod(IUserDataDescriptor IUUD, string methodName, object function)
|
||||
{
|
||||
LuaUserData.AddMethod(IUUD, methodName, function);
|
||||
}
|
||||
|
||||
public static void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value)
|
||||
{
|
||||
LuaUserData.AddField(IUUD, fieldName, value);
|
||||
}
|
||||
|
||||
public static void RemoveMember(IUserDataDescriptor IUUD, string memberName)
|
||||
{
|
||||
LuaUserData.RemoveMember(IUUD, memberName);
|
||||
}
|
||||
|
||||
public static bool HasMember(object obj, string memberName)
|
||||
{
|
||||
return LuaUserData.HasMember(obj, memberName);
|
||||
}
|
||||
|
||||
public static void AddCallMetaTable(object userdata) { }
|
||||
|
||||
public static DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor)
|
||||
{
|
||||
return LuaUserData.CreateUserDataFromDescriptor(scriptObject, desiredTypeDescriptor);
|
||||
}
|
||||
|
||||
public static DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType)
|
||||
{
|
||||
return LuaUserData.CreateUserDataFromType(scriptObject, desiredType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public struct LuaSByte
|
||||
{
|
||||
public readonly sbyte Value;
|
||||
|
||||
public LuaSByte(double v)
|
||||
{
|
||||
Value = (sbyte)v;
|
||||
}
|
||||
|
||||
public LuaSByte(string v, int radix = 10)
|
||||
{
|
||||
Value = Convert.ToSByte(v, radix);
|
||||
}
|
||||
|
||||
public static implicit operator sbyte(LuaSByte luaValue) => luaValue.Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public struct LuaByte
|
||||
{
|
||||
public readonly byte Value;
|
||||
|
||||
public LuaByte(double v)
|
||||
{
|
||||
Value = (byte)v;
|
||||
}
|
||||
|
||||
public LuaByte(string v, int radix = 10)
|
||||
{
|
||||
Value = Convert.ToByte(v, radix);
|
||||
}
|
||||
|
||||
public static implicit operator byte(LuaByte luaValue) => luaValue.Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public struct LuaInt16
|
||||
{
|
||||
public readonly short Value;
|
||||
|
||||
public LuaInt16(double v)
|
||||
{
|
||||
Value = (short)v;
|
||||
}
|
||||
|
||||
public LuaInt16(string v, int radix = 10)
|
||||
{
|
||||
Value = Convert.ToInt16(v, radix);
|
||||
}
|
||||
|
||||
public static implicit operator short(LuaInt16 luaValue) => luaValue.Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public struct LuaUInt16
|
||||
{
|
||||
public readonly ushort Value;
|
||||
|
||||
public LuaUInt16(double v)
|
||||
{
|
||||
Value = (ushort)v;
|
||||
}
|
||||
|
||||
public LuaUInt16(string v, int radix = 10)
|
||||
{
|
||||
Value = Convert.ToUInt16(v, radix);
|
||||
}
|
||||
|
||||
public static implicit operator ushort(LuaUInt16 luaValue) => luaValue.Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public struct LuaInt32
|
||||
{
|
||||
public readonly int Value;
|
||||
|
||||
public LuaInt32(double v)
|
||||
{
|
||||
Value = (int)v;
|
||||
}
|
||||
|
||||
public LuaInt32(string v, int radix = 10)
|
||||
{
|
||||
Value = Convert.ToInt32(v, radix);
|
||||
}
|
||||
|
||||
public static implicit operator int(LuaInt32 luaValue) => luaValue.Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public struct LuaUInt32
|
||||
{
|
||||
public readonly uint Value;
|
||||
|
||||
public LuaUInt32(double v)
|
||||
{
|
||||
Value = (uint)v;
|
||||
}
|
||||
|
||||
public LuaUInt32(string v, int radix = 10)
|
||||
{
|
||||
Value = Convert.ToUInt32(v, radix);
|
||||
}
|
||||
|
||||
public static implicit operator uint(LuaUInt32 luaValue) => luaValue.Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public struct LuaInt64
|
||||
{
|
||||
public readonly long Value;
|
||||
|
||||
public LuaInt64(double v)
|
||||
{
|
||||
Value = (long)v;
|
||||
}
|
||||
|
||||
public LuaInt64(double lo, double hi)
|
||||
{
|
||||
Value = Convert.ToUInt32(lo) | (long)Convert.ToInt32(hi) << 32;
|
||||
}
|
||||
|
||||
public LuaInt64(string v, int radix = 10)
|
||||
{
|
||||
Value = Convert.ToInt64(v, radix);
|
||||
}
|
||||
|
||||
public static implicit operator long(LuaInt64 luaValue) => luaValue.Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public struct LuaUInt64
|
||||
{
|
||||
public readonly ulong Value;
|
||||
|
||||
public LuaUInt64(double v)
|
||||
{
|
||||
Value = (ulong)v;
|
||||
}
|
||||
|
||||
public LuaUInt64(double lo, double hi)
|
||||
{
|
||||
Value = Convert.ToUInt32(lo) | (ulong)Convert.ToUInt32(hi) << 32;
|
||||
}
|
||||
|
||||
public LuaUInt64(string v, int radix = 10)
|
||||
{
|
||||
Value = Convert.ToUInt64(v, radix);
|
||||
}
|
||||
|
||||
public static implicit operator ulong(LuaUInt64 luaValue) => luaValue.Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public struct LuaSingle
|
||||
{
|
||||
public readonly float Value;
|
||||
|
||||
public LuaSingle(double v)
|
||||
{
|
||||
Value = (float)v;
|
||||
}
|
||||
|
||||
public LuaSingle(string v)
|
||||
{
|
||||
Value = float.Parse(v);
|
||||
}
|
||||
|
||||
public static implicit operator float(LuaSingle luaValue) => luaValue.Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public struct LuaDouble
|
||||
{
|
||||
public readonly double Value;
|
||||
|
||||
public LuaDouble(double v)
|
||||
{
|
||||
Value = v;
|
||||
}
|
||||
|
||||
public LuaDouble(string v)
|
||||
{
|
||||
Value = double.Parse(v);
|
||||
}
|
||||
|
||||
public static implicit operator double(LuaDouble luaValue) => luaValue.Value;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
global using LuaCsHook = Barotrauma.LuaCs.Compatibility.ILuaCsHook;
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.LuaCs.Compatibility;
|
||||
using MoonSharp.Interpreter;
|
||||
using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
// XXX: this can't be renamed because of backward compatibility with C# mods
|
||||
public delegate object LuaCsPatch(object self, Dictionary<string, object> args);
|
||||
}
|
||||
|
||||
namespace Barotrauma.LuaCs
|
||||
{
|
||||
partial class LuaPatcherService
|
||||
{
|
||||
private static LuaPatcherService instance;
|
||||
|
||||
private Dictionary<long, HashSet<(string, LuaCsCompatPatchFunc)>> compatHookPrefixMethods = new Dictionary<long, HashSet<(string, LuaCsCompatPatchFunc)>>();
|
||||
private Dictionary<long, HashSet<(string, LuaCsCompatPatchFunc)>> compatHookPostfixMethods = new Dictionary<long, HashSet<(string, LuaCsCompatPatchFunc)>>();
|
||||
|
||||
private static void _hookLuaCsPatch(MethodBase __originalMethod, object[] __args, object __instance, out object result, ILuaCsHook.HookMethodType hookType)
|
||||
{
|
||||
result = null;
|
||||
|
||||
try
|
||||
{
|
||||
var funcAddr = ((long)__originalMethod.MethodHandle.GetFunctionPointer());
|
||||
HashSet<(string, LuaCsCompatPatchFunc)> methodSet = null;
|
||||
switch (hookType)
|
||||
{
|
||||
case ILuaCsHook.HookMethodType.Before:
|
||||
instance.compatHookPrefixMethods.TryGetValue(funcAddr, out methodSet);
|
||||
break;
|
||||
case ILuaCsHook.HookMethodType.After:
|
||||
instance.compatHookPostfixMethods.TryGetValue(funcAddr, out methodSet);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Invalid {nameof(ILuaCsHook.HookMethodType)} enum value.", nameof(hookType));
|
||||
}
|
||||
|
||||
if (methodSet != null)
|
||||
{
|
||||
var @params = __originalMethod.GetParameters();
|
||||
var args = new Dictionary<string, object>();
|
||||
for (int i = 0; i < @params.Length; i++)
|
||||
{
|
||||
args.Add(@params[i].Name, __args[i]);
|
||||
}
|
||||
|
||||
foreach (var tuple in methodSet)
|
||||
{
|
||||
var _result = tuple.Item2(__instance, args);
|
||||
if (_result != null)
|
||||
{
|
||||
if (_result is DynValue res)
|
||||
{
|
||||
if (!res.IsNil())
|
||||
{
|
||||
if (__originalMethod is MethodInfo mi && mi.ReturnType != typeof(void))
|
||||
{
|
||||
result = res.ToObject(mi.ReturnType);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = res.ToObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = _result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LuaCsLogger.LogError($"Error in {__originalMethod.Name}:", LuaCsMessageOrigin.Unknown);
|
||||
LuaCsLogger.HandleException(ex, LuaCsMessageOrigin.Unknown);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static bool HookLuaCsPatchPrefix(MethodBase __originalMethod, object[] __args, object __instance)
|
||||
{
|
||||
_hookLuaCsPatch(__originalMethod, __args, __instance, out object result, ILuaCsHook.HookMethodType.Before);
|
||||
return result == null;
|
||||
}
|
||||
|
||||
private static void HookLuaCsPatchPostfix(MethodBase __originalMethod, object[] __args, object __instance) =>
|
||||
_hookLuaCsPatch(__originalMethod, __args, __instance, out object _, ILuaCsHook.HookMethodType.After);
|
||||
|
||||
private static bool HookLuaCsPatchRetPrefix(MethodBase __originalMethod, object[] __args, ref object __result, object __instance)
|
||||
{
|
||||
_hookLuaCsPatch(__originalMethod, __args, __instance, out object result, ILuaCsHook.HookMethodType.Before);
|
||||
if (result != null)
|
||||
{
|
||||
__result = result;
|
||||
return false;
|
||||
}
|
||||
else return true;
|
||||
}
|
||||
|
||||
private static void HookLuaCsPatchRetPostfix(MethodBase __originalMethod, object[] __args, ref object __result, object __instance)
|
||||
{
|
||||
_hookLuaCsPatch(__originalMethod, __args, __instance, out object result, ILuaCsHook.HookMethodType.After);
|
||||
if (result != null) __result = result;
|
||||
}
|
||||
|
||||
private static MethodInfo _miHookLuaCsPatchPrefix = typeof(LuaPatcherService).GetMethod("HookLuaCsPatchPrefix", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
private static MethodInfo _miHookLuaCsPatchPostfix = typeof(LuaPatcherService).GetMethod("HookLuaCsPatchPostfix", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
private static MethodInfo _miHookLuaCsPatchRetPrefix = typeof(LuaPatcherService).GetMethod("HookLuaCsPatchRetPrefix", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
private static MethodInfo _miHookLuaCsPatchRetPostfix = typeof(LuaPatcherService).GetMethod("HookLuaCsPatchRetPostfix", BindingFlags.NonPublic | BindingFlags.Static);
|
||||
|
||||
// TODO: deprecate this
|
||||
|
||||
public void HookMethod(string identifier, MethodBase method, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookType = ILuaCsHook.HookMethodType.Before, IAssemblyPlugin owner = null)
|
||||
{
|
||||
if (identifier == null || method == null || patch == null)
|
||||
{
|
||||
LuaCsLogger.HandleException(new ArgumentNullException("Identifier, Method and Patch arguments must not be null."), LuaCsMessageOrigin.Unknown);
|
||||
return;
|
||||
}
|
||||
ValidatePatchTarget(method);
|
||||
|
||||
var funcAddr = ((long)method.MethodHandle.GetFunctionPointer());
|
||||
var patches = Harmony.GetPatchInfo(method);
|
||||
|
||||
if (hookType == ILuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
if (method is MethodInfo mi && mi.ReturnType != typeof(void))
|
||||
{
|
||||
if (patches == null || patches.Prefixes == null || patches.Prefixes.Find(patch => patch.PatchMethod == _miHookLuaCsPatchRetPrefix) == null)
|
||||
{
|
||||
harmony.Patch(method, prefix: new HarmonyMethod(_miHookLuaCsPatchRetPrefix));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (patches == null || patches.Prefixes == null || patches.Prefixes.Find(patch => patch.PatchMethod == _miHookLuaCsPatchPrefix) == null)
|
||||
{
|
||||
harmony.Patch(method, prefix: new HarmonyMethod(_miHookLuaCsPatchPrefix));
|
||||
}
|
||||
}
|
||||
|
||||
if (compatHookPrefixMethods.TryGetValue(funcAddr, out HashSet<(string, LuaCsCompatPatchFunc)> methodSet))
|
||||
{
|
||||
if (identifier != "")
|
||||
{
|
||||
methodSet.RemoveWhere(tuple => tuple.Item1 == identifier);
|
||||
}
|
||||
|
||||
methodSet.Add((identifier, patch));
|
||||
}
|
||||
else if (patch != null)
|
||||
{
|
||||
compatHookPrefixMethods.Add(funcAddr, new HashSet<(string, LuaCsCompatPatchFunc)>() { (identifier, patch) });
|
||||
}
|
||||
|
||||
}
|
||||
else if (hookType == ILuaCsHook.HookMethodType.After)
|
||||
{
|
||||
if (method is MethodInfo mi && mi.ReturnType != typeof(void))
|
||||
{
|
||||
if (patches == null || patches.Postfixes == null || patches.Postfixes.Find(patch => patch.PatchMethod == _miHookLuaCsPatchRetPostfix) == null)
|
||||
{
|
||||
harmony.Patch(method, postfix: new HarmonyMethod(_miHookLuaCsPatchRetPostfix));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (patches == null || patches.Postfixes == null || patches.Postfixes.Find(patch => patch.PatchMethod == _miHookLuaCsPatchPostfix) == null)
|
||||
{
|
||||
harmony.Patch(method, postfix: new HarmonyMethod(_miHookLuaCsPatchPostfix));
|
||||
}
|
||||
}
|
||||
|
||||
if (compatHookPostfixMethods.TryGetValue(funcAddr, out HashSet<(string, LuaCsCompatPatchFunc)> methodSet))
|
||||
{
|
||||
if (identifier != "")
|
||||
{
|
||||
methodSet.RemoveWhere(tuple => tuple.Item1 == identifier);
|
||||
}
|
||||
|
||||
methodSet.Add((identifier, patch));
|
||||
}
|
||||
else if (patch != null)
|
||||
{
|
||||
compatHookPostfixMethods.Add(funcAddr, new HashSet<(string, LuaCsCompatPatchFunc)>() { (identifier, patch) });
|
||||
}
|
||||
}
|
||||
}
|
||||
public void HookMethod(string identifier, string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
var method = ResolveMethod(className, methodName, parameterNames);
|
||||
if (method == null) return;
|
||||
if (method.GetParameters().Any(x => x.ParameterType.IsByRef))
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(HookMethod)} doesn't support ByRef parameters; use {nameof(Patch)} instead.");
|
||||
}
|
||||
HookMethod(identifier, method, patch, hookMethodType);
|
||||
}
|
||||
public void HookMethod(string identifier, string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) =>
|
||||
HookMethod(identifier, className, methodName, null, patch, hookMethodType);
|
||||
public void HookMethod(string className, string methodName, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) =>
|
||||
HookMethod("", className, methodName, null, patch, hookMethodType);
|
||||
public void HookMethod(string className, string methodName, string[] parameterNames, LuaCsCompatPatchFunc patch, ILuaCsHook.HookMethodType hookMethodType = ILuaCsHook.HookMethodType.Before) =>
|
||||
HookMethod("", className, methodName, parameterNames, patch, hookMethodType);
|
||||
|
||||
|
||||
public void UnhookMethod(string identifier, MethodBase method, ILuaCsHook.HookMethodType hookType = ILuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
var funcAddr = (long)method.MethodHandle.GetFunctionPointer();
|
||||
|
||||
Dictionary<long, HashSet<(string, LuaCsCompatPatchFunc)>> methods;
|
||||
if (hookType == ILuaCsHook.HookMethodType.Before) methods = compatHookPrefixMethods;
|
||||
else if (hookType == ILuaCsHook.HookMethodType.After) methods = compatHookPostfixMethods;
|
||||
else throw null;
|
||||
|
||||
if (methods.ContainsKey(funcAddr)) methods[funcAddr]?.RemoveWhere(t => t.Item1 == identifier);
|
||||
}
|
||||
protected void UnhookMethod(string identifier, string className, string methodName, string[] parameterNames, ILuaCsHook.HookMethodType hookType = ILuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
var method = ResolveMethod(className, methodName, parameterNames);
|
||||
if (method == null) return;
|
||||
UnhookMethod(identifier, method, hookType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,816 @@
|
||||
using Barotrauma.LuaCs;
|
||||
using HarmonyLib;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MoonSharp.Interpreter;
|
||||
using MoonSharp.Interpreter.Interop;
|
||||
using Sigil;
|
||||
using Sigil.NonGeneric;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public delegate void LuaCsAction(params object[] args);
|
||||
public delegate object LuaCsFunc(params object[] args);
|
||||
public delegate DynValue LuaCsPatchFunc(object instance, LuaPatcherService.ParameterTable ptable);
|
||||
}
|
||||
|
||||
namespace Barotrauma.LuaCs
|
||||
{
|
||||
public partial class LuaPatcherService : ILuaPatcher
|
||||
{
|
||||
private class LuaCsHookCallback
|
||||
{
|
||||
public string name;
|
||||
public string hookName;
|
||||
public LuaCsFunc func;
|
||||
|
||||
public LuaCsHookCallback(string name, string hookName, LuaCsFunc func)
|
||||
{
|
||||
this.name = name;
|
||||
this.hookName = hookName;
|
||||
this.func = func;
|
||||
}
|
||||
}
|
||||
|
||||
private class LuaCsPatch
|
||||
{
|
||||
public string Identifier { get; set; }
|
||||
|
||||
public LuaCsPatchFunc PatchFunc { get; set; }
|
||||
}
|
||||
|
||||
private class PatchedMethod
|
||||
{
|
||||
public PatchedMethod(MethodInfo harmonyPrefix, MethodInfo harmonyPostfix)
|
||||
{
|
||||
HarmonyPrefixMethod = harmonyPrefix;
|
||||
HarmonyPostfixMethod = harmonyPostfix;
|
||||
Prefixes = new Dictionary<string, LuaCsPatch>();
|
||||
Postfixes = new Dictionary<string, LuaCsPatch>();
|
||||
}
|
||||
|
||||
public MethodInfo HarmonyPrefixMethod { get; }
|
||||
|
||||
public MethodInfo HarmonyPostfixMethod { get; }
|
||||
|
||||
public IEnumerator<LuaCsPatch> GetPrefixEnumerator() => Prefixes.Values.GetEnumerator();
|
||||
|
||||
public IEnumerator<LuaCsPatch> GetPostfixEnumerator() => Postfixes.Values.GetEnumerator();
|
||||
|
||||
public Dictionary<string, LuaCsPatch> Prefixes { get; }
|
||||
|
||||
public Dictionary<string, LuaCsPatch> Postfixes { get; }
|
||||
}
|
||||
|
||||
public class ParameterTable
|
||||
{
|
||||
private readonly Dictionary<string, object> parameters;
|
||||
private bool returnValueModified;
|
||||
private object returnValue;
|
||||
|
||||
public ParameterTable(Dictionary<string, object> dict)
|
||||
{
|
||||
parameters = dict;
|
||||
}
|
||||
|
||||
public object this[string paramName]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ModifiedParameters.TryGetValue(paramName, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
return OriginalParameters[paramName];
|
||||
}
|
||||
set
|
||||
{
|
||||
ModifiedParameters[paramName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public object OriginalReturnValue { get; private set; }
|
||||
|
||||
public object ReturnValue
|
||||
{
|
||||
get
|
||||
{
|
||||
if (returnValueModified) return returnValue;
|
||||
return OriginalReturnValue;
|
||||
}
|
||||
set
|
||||
{
|
||||
returnValueModified = true;
|
||||
returnValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool PreventExecution { get; set; }
|
||||
|
||||
public Dictionary<string, object> OriginalParameters => parameters;
|
||||
|
||||
[MoonSharpHidden]
|
||||
public Dictionary<string, object> ModifiedParameters { get; } = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
private struct MethodKey : IEquatable<MethodKey>
|
||||
{
|
||||
public ModuleHandle ModuleHandle { get; set; }
|
||||
|
||||
public int MetadataToken { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is MethodKey key && Equals(key);
|
||||
}
|
||||
|
||||
public bool Equals(MethodKey other)
|
||||
{
|
||||
return ModuleHandle.Equals(other.ModuleHandle) && MetadataToken == other.MetadataToken;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(ModuleHandle, MetadataToken);
|
||||
}
|
||||
|
||||
public static bool operator ==(MethodKey left, MethodKey right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(MethodKey left, MethodKey right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public static MethodKey Create(MethodBase method) => new MethodKey
|
||||
{
|
||||
ModuleHandle = method.Module.ModuleHandle,
|
||||
MetadataToken = method.MetadataToken,
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly string[] prohibitedHooks =
|
||||
{
|
||||
"Barotrauma.Lua",
|
||||
"Barotrauma.Cs",
|
||||
"Barotrauma.ContentPackageManager",
|
||||
};
|
||||
|
||||
|
||||
private Harmony harmony;
|
||||
private Lazy<ModuleBuilder> patchModuleBuilder;
|
||||
private readonly Dictionary<MethodKey, PatchedMethod> registeredPatches = new Dictionary<MethodKey, PatchedMethod>();
|
||||
|
||||
public LuaPatcherService()
|
||||
{
|
||||
instance = this;
|
||||
|
||||
harmony = new Harmony("LuaCsForBarotrauma");
|
||||
patchModuleBuilder = new Lazy<ModuleBuilder>(CreateModuleBuilder);
|
||||
|
||||
UserData.RegisterType<ParameterTable>();
|
||||
|
||||
// whats this for?
|
||||
/*
|
||||
var hookType = UserData.RegisterType<EventService>();
|
||||
var hookDesc = (StandardUserDataDescriptor)hookType;
|
||||
typeof(EventService).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).ToList().ForEach(m => {
|
||||
if (
|
||||
m.Name.Contains("HookMethod") ||
|
||||
m.Name.Contains("UnhookMethod") ||
|
||||
m.Name.Contains("EnqueueFunction") ||
|
||||
m.Name.Contains("EnqueueTimedFunction")
|
||||
)
|
||||
{
|
||||
hookDesc.AddMember(m.Name, new MethodMemberDescriptor(m, InteropAccessMode.Default));
|
||||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
private static void ValidatePatchTarget(MethodBase method)
|
||||
{
|
||||
if (prohibitedHooks.Any(h => method.DeclaringType.FullName.StartsWith(h)))
|
||||
{
|
||||
throw new ArgumentException("Hooks into the modding environment are prohibited.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeIdentifier(string identifier)
|
||||
{
|
||||
return identifier?.Trim().ToLowerInvariant();
|
||||
}
|
||||
|
||||
private ModuleBuilder CreateModuleBuilder()
|
||||
{
|
||||
var assemblyName = $"LuaCsHookPatch-{Guid.NewGuid():N}";
|
||||
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.RunAndCollect);
|
||||
var moduleBuilder = assemblyBuilder.DefineDynamicModule("LuaCsHookPatch");
|
||||
|
||||
// This code emits the Roslyn attribute
|
||||
// "IgnoresAccessChecksToAttribute" so we can freely access
|
||||
// the Barotrauma assembly from our dynamic patches.
|
||||
// This is important because the generated IL references
|
||||
// non-public types/members.
|
||||
|
||||
// class IgnoresAccessChecksToAttribute {
|
||||
var typeBuilder = moduleBuilder.DefineType(
|
||||
name: "System.Runtime.CompilerServices.IgnoresAccessChecksToAttribute",
|
||||
attr: TypeAttributes.NotPublic | TypeAttributes.Sealed | TypeAttributes.Class,
|
||||
parent: typeof(Attribute));
|
||||
|
||||
// [AttributeUsage(AllowMultiple = true)]
|
||||
var attributeUsageAttribute = new CustomAttributeBuilder(
|
||||
con: typeof(AttributeUsageAttribute).GetConstructor(new[] { typeof(AttributeTargets) }),
|
||||
constructorArgs: new object[] { AttributeTargets.Assembly },
|
||||
namedProperties: new[] { typeof(AttributeUsageAttribute).GetProperty("AllowMultiple") },
|
||||
propertyValues: new object[] { true });
|
||||
typeBuilder.SetCustomAttribute(attributeUsageAttribute);
|
||||
|
||||
// private readonly string assemblyName;
|
||||
var attributeTypeFieldBuilder = typeBuilder.DefineField(
|
||||
fieldName: "assemblyName",
|
||||
type: typeof(string),
|
||||
attributes: FieldAttributes.Private | FieldAttributes.InitOnly);
|
||||
|
||||
var ctor = Emit.BuildConstructor(
|
||||
parameterTypes: new[] { typeof(string) },
|
||||
type: typeBuilder,
|
||||
attributes: MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
|
||||
callingConvention: CallingConventions.Standard | CallingConventions.HasThis);
|
||||
// IL: this.assemblyName = arg;
|
||||
ctor.LoadArgument(0);
|
||||
ctor.LoadArgument(1);
|
||||
ctor.StoreField(attributeTypeFieldBuilder);
|
||||
ctor.Return();
|
||||
ctor.CreateConstructor();
|
||||
|
||||
// public string AttributeName => this.assemblyName;
|
||||
var attributeNameGetter = Emit.BuildMethod(
|
||||
returnType: typeof(string),
|
||||
parameterTypes: new Type[0],
|
||||
type: typeBuilder,
|
||||
name: "get_AttributeName",
|
||||
attributes: MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
|
||||
callingConvention: CallingConventions.Standard | CallingConventions.HasThis);
|
||||
attributeNameGetter.LoadArgument(0);
|
||||
attributeNameGetter.LoadField(attributeTypeFieldBuilder);
|
||||
attributeNameGetter.Return();
|
||||
|
||||
var attributeName = typeBuilder.DefineProperty(
|
||||
name: "AttributeName",
|
||||
attributes: PropertyAttributes.None,
|
||||
returnType: typeof(string),
|
||||
parameterTypes: null);
|
||||
attributeName.SetGetMethod(attributeNameGetter.CreateMethod());
|
||||
// }
|
||||
|
||||
var type = typeBuilder.CreateTypeInfo().AsType();
|
||||
|
||||
// The assembly names are hardcoded, otherwise it would
|
||||
// break unit tests.
|
||||
var assembliesToExpose = new[] { "Barotrauma", "DedicatedServer" };
|
||||
foreach (var name in assembliesToExpose)
|
||||
{
|
||||
var attr = new CustomAttributeBuilder(
|
||||
con: type.GetConstructor(new[] { typeof(string)}),
|
||||
constructorArgs: new[] { name });
|
||||
assemblyBuilder.SetCustomAttribute(attr);
|
||||
}
|
||||
|
||||
return moduleBuilder;
|
||||
}
|
||||
|
||||
private static MethodBase ResolveMethod(string className, string methodName, string[] parameters)
|
||||
{
|
||||
var classType = LuaCsSetup.Instance.PluginManagementService.GetType(className);
|
||||
if (classType == null) throw new ScriptRuntimeException($"invalid class name '{className}'");
|
||||
|
||||
const BindingFlags BINDING_FLAGS = BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
MethodBase method = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (parameters != null)
|
||||
{
|
||||
Type[] parameterTypes = new Type[parameters.Length];
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
Type type = LuaCsSetup.Instance.PluginManagementService.GetType(parameters[i]);
|
||||
if (type == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"invalid parameter type '{parameters[i]}'");
|
||||
}
|
||||
parameterTypes[i] = type;
|
||||
}
|
||||
|
||||
method = methodName switch
|
||||
{
|
||||
".cctor" => classType.TypeInitializer,
|
||||
".ctor" => classType.GetConstructors(BINDING_FLAGS)
|
||||
.Except(new[] { classType.TypeInitializer })
|
||||
.Where(x => x.GetParameters().Select(x => x.ParameterType).SequenceEqual(parameterTypes))
|
||||
.SingleOrDefault(),
|
||||
_ => classType.GetMethod(methodName, BINDING_FLAGS, null, parameterTypes, null),
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
ConstructorInfo GetCtor()
|
||||
{
|
||||
var ctors = classType.GetConstructors(BINDING_FLAGS)
|
||||
.Except(new[] { classType.TypeInitializer })
|
||||
.GetEnumerator();
|
||||
|
||||
if (!ctors.MoveNext()) return null;
|
||||
var ctor = ctors.Current;
|
||||
|
||||
if (ctors.MoveNext()) throw new AmbiguousMatchException();
|
||||
return ctor;
|
||||
}
|
||||
|
||||
method = methodName switch
|
||||
{
|
||||
".cctor" => throw new ScriptRuntimeException("type initializers can't have parameters"),
|
||||
".ctor" => GetCtor(),
|
||||
_ => classType.GetMethod(methodName, BINDING_FLAGS),
|
||||
};
|
||||
}
|
||||
}
|
||||
catch (AmbiguousMatchException)
|
||||
{
|
||||
throw new ScriptRuntimeException("ambiguous method signature");
|
||||
}
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
var parameterNamesStr = parameters == null ? "" : string.Join(", ", parameters);
|
||||
throw new ScriptRuntimeException($"method '{methodName}({parameterNamesStr})' not found in class '{className}'");
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
private class DynamicParameterMapping
|
||||
{
|
||||
public DynamicParameterMapping(string name, Type originalMethodParamType, Type harmonyPatchParamType)
|
||||
{
|
||||
ParameterName = name;
|
||||
OriginalMethodParamType = originalMethodParamType;
|
||||
HarmonyPatchParamType = harmonyPatchParamType;
|
||||
}
|
||||
|
||||
public string ParameterName { get; set; }
|
||||
|
||||
public Type OriginalMethodParamType { get; set; }
|
||||
|
||||
public Type HarmonyPatchParamType { get; set; }
|
||||
}
|
||||
|
||||
private static readonly Regex InvalidIdentifierCharsRegex = new Regex(@"[^\w\d]", RegexOptions.Compiled);
|
||||
|
||||
private const string FIELD_LUACS = "LuaCs";
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
// If you need to debug this:
|
||||
// - use https://sharplab.io ; it's a very useful for resource for writing IL by hand.
|
||||
// - use il.NewMessage("") or il.WriteLine("") to see where the IL crashes at runtime.
|
||||
private MethodInfo CreateDynamicHarmonyPatch(string identifier, MethodBase original, LuaCsHook.HookMethodType hookType)
|
||||
{
|
||||
var parameters = new List<DynamicParameterMapping>
|
||||
{
|
||||
new DynamicParameterMapping("__originalMethod", null, typeof(MethodBase)),
|
||||
new DynamicParameterMapping("__instance", null, typeof(object)),
|
||||
};
|
||||
|
||||
var hasReturnType = original is MethodInfo mi && mi.ReturnType != typeof(void);
|
||||
if (hasReturnType)
|
||||
{
|
||||
parameters.Add(new DynamicParameterMapping("__result", null, typeof(object).MakeByRefType()));
|
||||
}
|
||||
|
||||
foreach (var parameter in original.GetParameters())
|
||||
{
|
||||
var paramName = parameter.Name;
|
||||
var originalMethodParamType = parameter.ParameterType;
|
||||
var harmonyPatchParamType = originalMethodParamType.IsByRef
|
||||
? originalMethodParamType
|
||||
// Make all parameters modifiable by the harmony patch
|
||||
: originalMethodParamType.MakeByRefType();
|
||||
parameters.Add(new DynamicParameterMapping(paramName, originalMethodParamType, harmonyPatchParamType));
|
||||
}
|
||||
|
||||
static string MangleName(object o) => InvalidIdentifierCharsRegex.Replace(o?.ToString(), "_");
|
||||
|
||||
var moduleBuilder = patchModuleBuilder.Value;
|
||||
var mangledName = original.DeclaringType != null
|
||||
? $"{MangleName(original.DeclaringType)}-{MangleName(original)}"
|
||||
: MangleName(original);
|
||||
var typeBuilder = moduleBuilder.DefineType($"Patch_{identifier}_{Guid.NewGuid():N}_{mangledName}", TypeAttributes.Public);
|
||||
|
||||
var luaCsField = typeBuilder.DefineField(FIELD_LUACS, typeof(LuaCsSetup), FieldAttributes.Public | FieldAttributes.Static);
|
||||
|
||||
var methodName = hookType == LuaCsHook.HookMethodType.Before ? "HarmonyPrefix" : "HarmonyPostfix";
|
||||
var il = Emit.BuildMethod(
|
||||
returnType: hookType == LuaCsHook.HookMethodType.Before ? typeof(bool) : typeof(void),
|
||||
parameterTypes: parameters.Select(x => x.HarmonyPatchParamType).ToArray(),
|
||||
type: typeBuilder,
|
||||
name: methodName,
|
||||
attributes: MethodAttributes.Public | MethodAttributes.Static,
|
||||
callingConvention: CallingConventions.Standard);
|
||||
|
||||
var labelReturn = il.DefineLabel("endOfFunction");
|
||||
|
||||
il.BeginExceptionBlock(out var exceptionBlock);
|
||||
|
||||
// IL: var harmonyReturnValue = true;
|
||||
var harmonyReturnValue = il.DeclareLocal<bool>("harmonyReturnValue");
|
||||
il.LoadConstant(true);
|
||||
il.StoreLocal(harmonyReturnValue);
|
||||
|
||||
// IL: var patchKey = MethodKey.Create(__originalMethod);
|
||||
var patchKey = il.DeclareLocal<MethodKey>("patchKey");
|
||||
il.LoadArgument(0); // load __originalMethod
|
||||
il.CastClass<MethodBase>();
|
||||
il.Call(typeof(MethodKey).GetMethod(nameof(MethodKey.Create)));
|
||||
il.StoreLocal(patchKey);
|
||||
|
||||
// IL: var patchExists = instance.registeredPatches.TryGetValue(patchKey, out MethodPatches patches)
|
||||
var patchExists = il.DeclareLocal<bool>("patchExists");
|
||||
var patches = il.DeclareLocal<PatchedMethod>("patches");
|
||||
il.LoadField(typeof(LuaPatcherService).GetField(nameof(instance), BindingFlags.NonPublic | BindingFlags.Static));
|
||||
il.LoadField(typeof(LuaPatcherService).GetField(nameof(registeredPatches), BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
il.LoadLocal(patchKey);
|
||||
il.LoadLocalAddress(patches); // out parameter
|
||||
il.Call(typeof(Dictionary<MethodKey, PatchedMethod>).GetMethod("TryGetValue"));
|
||||
il.StoreLocal(patchExists);
|
||||
|
||||
// IL: if (!patchExists)
|
||||
il.LoadLocal(patchExists);
|
||||
il.IfNot((il) =>
|
||||
{
|
||||
// XXX: if we get here, it's probably because a patched
|
||||
// method was running when `reloadlua` was executed.
|
||||
// This can happen with a postfix on
|
||||
// `Barotrauma.Networking.GameServer#Update`.
|
||||
il.Leave(labelReturn);
|
||||
});
|
||||
|
||||
// IL: var parameterDict = new Dictionary<string, object>(<paramCount>);
|
||||
var parameterDict = il.DeclareLocal<Dictionary<string, object>>("parameterDict");
|
||||
il.LoadConstant(parameters.Count(x => x.OriginalMethodParamType != null)); // preallocate the dictionary using the # of args
|
||||
il.NewObject(typeof(Dictionary<string, object>), typeof(int));
|
||||
il.StoreLocal(parameterDict);
|
||||
|
||||
for (ushort i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
// Skip parameters that don't exist in the original method
|
||||
if (parameters[i].OriginalMethodParamType == null) continue;
|
||||
|
||||
// IL: parameterDict.Add(<paramName>, <paramValue>);
|
||||
il.LoadLocal(parameterDict);
|
||||
il.LoadConstant(parameters[i].ParameterName);
|
||||
il.LoadArgument(i);
|
||||
il.ToObject(parameters[i].HarmonyPatchParamType);
|
||||
il.Call(typeof(Dictionary<string, object>).GetMethod("Add"));
|
||||
}
|
||||
|
||||
// IL: var ptable = new ParameterTable(parameterDict);
|
||||
var ptable = il.DeclareLocal<ParameterTable>("ptable");
|
||||
il.LoadLocal(parameterDict);
|
||||
il.NewObject(typeof(ParameterTable), typeof(Dictionary<string, object>));
|
||||
il.StoreLocal(ptable);
|
||||
|
||||
if (hasReturnType && hookType == LuaCsHook.HookMethodType.After)
|
||||
{
|
||||
// IL: ptable.OriginalReturnValue = __result;
|
||||
il.LoadLocal(ptable);
|
||||
il.LoadArgument(2); // ref __result
|
||||
il.ToObject(parameters[2].HarmonyPatchParamType);
|
||||
il.Call(typeof(ParameterTable).GetProperty(nameof(ParameterTable.OriginalReturnValue)).GetSetMethod(nonPublic: true));
|
||||
}
|
||||
|
||||
// IL: var enumerator = patches.GetPrefixEnumerator();
|
||||
var enumerator = il.DeclareLocal<IEnumerator<LuaCsPatch>>("enumerator");
|
||||
il.LoadLocal(patches);
|
||||
il.CallVirtual(typeof(PatchedMethod).GetMethod(
|
||||
name: hookType == LuaCsHook.HookMethodType.Before
|
||||
? nameof(PatchedMethod.GetPrefixEnumerator)
|
||||
: nameof(PatchedMethod.GetPostfixEnumerator),
|
||||
bindingAttr: BindingFlags.Public | BindingFlags.Instance));
|
||||
il.StoreLocal(enumerator);
|
||||
|
||||
var labelUpdateParameters = il.DefineLabel("updateParameters");
|
||||
|
||||
// Iterate over prefixes/postfixes
|
||||
il.ForEachEnumerator<LuaCsPatch>(enumerator, (il, current, labelLeave) =>
|
||||
{
|
||||
// IL: var luaReturnValue = current.PatchFunc.Invoke(__instance, ptable);
|
||||
var luaReturnValue = il.DeclareLocal<DynValue>("luaReturnValue");
|
||||
il.LoadLocal(current);
|
||||
il.Call(typeof(LuaCsPatch).GetProperty(nameof(LuaCsPatch.PatchFunc)).GetGetMethod());
|
||||
il.LoadArgument(1); // __instance
|
||||
il.LoadLocal(ptable);
|
||||
il.CallVirtual(typeof(LuaCsPatchFunc).GetMethod("Invoke"));
|
||||
il.StoreLocal(luaReturnValue);
|
||||
|
||||
if (hasReturnType)
|
||||
{
|
||||
// IL: var ptableReturnValue = ptable.ReturnValue;
|
||||
var ptableReturnValue = il.DeclareLocal<object>("ptableReturnValue");
|
||||
il.LoadLocal(ptable);
|
||||
il.Call(typeof(ParameterTable).GetProperty(nameof(ParameterTable.ReturnValue)).GetGetMethod());
|
||||
il.StoreLocal(ptableReturnValue);
|
||||
|
||||
// IL: if (ptableReturnValue != null)
|
||||
il.LoadLocal(ptableReturnValue);
|
||||
il.If((il) =>
|
||||
{
|
||||
// IL: __result = ptableReturnValue;
|
||||
il.LoadArgument(2); // ref __result
|
||||
il.LoadLocal(ptableReturnValue);
|
||||
il.StoreIndirect(typeof(object));
|
||||
il.Break();
|
||||
});
|
||||
|
||||
// IL: if (luaReturnValue != null)
|
||||
il.LoadLocal(luaReturnValue);
|
||||
il.If((il) =>
|
||||
{
|
||||
// IL: if (!luaReturnValue.IsVoid())
|
||||
il.LoadLocal(luaReturnValue);
|
||||
il.Call(typeof(DynValue).GetMethod(nameof(DynValue.IsVoid)));
|
||||
il.IfNot((il) =>
|
||||
{
|
||||
// IL: var csReturnType = Type.GetTypeFromHandle(<original.ReturnType>);
|
||||
var csReturnType = il.DeclareLocal<Type>("csReturnType");
|
||||
il.LoadType(((MethodInfo)original).ReturnType);
|
||||
il.StoreLocal(csReturnType);
|
||||
|
||||
// IL: var csReturnValue = luaReturnValue.ToObject(csReturnType);
|
||||
var csReturnValue = il.DeclareLocal<object>("csReturnValue");
|
||||
il.LoadLocal(luaReturnValue);
|
||||
il.LoadLocal(csReturnType);
|
||||
il.Call(typeof(DynValue).GetMethod(
|
||||
name: nameof(DynValue.ToObject),
|
||||
bindingAttr: BindingFlags.Public | BindingFlags.Instance,
|
||||
binder: null,
|
||||
types: new Type[] { typeof(Type) },
|
||||
modifiers: null));
|
||||
il.StoreLocal(csReturnValue);
|
||||
|
||||
// IL: __result = csReturnValue;
|
||||
il.LoadArgument(2); // ref __result
|
||||
il.LoadLocal(csReturnValue);
|
||||
il.StoreIndirect(typeof(object));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// IL: if (ptable.PreventExecution)
|
||||
il.LoadLocal(ptable);
|
||||
il.Call(typeof(ParameterTable).GetProperty(nameof(ParameterTable.PreventExecution)).GetGetMethod());
|
||||
il.If((il) =>
|
||||
{
|
||||
// IL: harmonyReturnValue = false;
|
||||
il.LoadConstant(false);
|
||||
il.StoreLocal(harmonyReturnValue);
|
||||
|
||||
// IL: break;
|
||||
il.Leave(labelLeave);
|
||||
});
|
||||
});
|
||||
|
||||
// IL: var modifiedParameters = ptable.ModifiedParameters;
|
||||
var modifiedParameters = il.DeclareLocal<Dictionary<string, object>>("modifiedParameters");
|
||||
il.LoadLocal(ptable);
|
||||
il.Call(typeof(ParameterTable).GetProperty(nameof(ParameterTable.ModifiedParameters)).GetGetMethod());
|
||||
il.StoreLocal(modifiedParameters);
|
||||
// IL: object modifiedValue;
|
||||
var modifiedValue = il.DeclareLocal<object>("modifiedValue");
|
||||
|
||||
// Update the parameters
|
||||
for (ushort i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
// Skip parameters that don't exist in the original method
|
||||
if (parameters[i].OriginalMethodParamType == null) continue;
|
||||
|
||||
// IL: if (modifiedParameters.TryGetValue("parameterName", out modifiedValue))
|
||||
il.LoadLocal(modifiedParameters);
|
||||
il.LoadConstant(parameters[i].ParameterName);
|
||||
il.LoadLocalAddress(modifiedValue); // out parameter
|
||||
il.Call(typeof(Dictionary<string, object>).GetMethod(nameof(Dictionary<string, object>.TryGetValue)));
|
||||
il.If((il) =>
|
||||
{
|
||||
// XXX: GetElementType() gets the "real" type behind
|
||||
// the ByRef. This is safe because all the parameters
|
||||
// are made into ByRef to support modification.
|
||||
var paramType = parameters[i].HarmonyPatchParamType.GetElementType();
|
||||
|
||||
// IL: ref argName = modifiedValue;
|
||||
il.LoadArgument(i);
|
||||
il.LoadLocalAndCast(modifiedValue, paramType);
|
||||
if (paramType.IsValueType)
|
||||
{
|
||||
il.StoreObject(paramType);
|
||||
}
|
||||
else
|
||||
{
|
||||
il.StoreIndirect(paramType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
il.MarkLabel(labelReturn);
|
||||
|
||||
// IL: catch (Exception exception)
|
||||
il.BeginCatchAllBlock(exceptionBlock, out var catchBlock);
|
||||
var exception = il.DeclareLocal<Exception>("exception");
|
||||
il.StoreLocal(exception);
|
||||
|
||||
// IL: if (LuaCs != null)
|
||||
il.LoadField(luaCsField);
|
||||
il.If((il) =>
|
||||
{
|
||||
// IL: LuaCs.HandleException(exception, LuaCsMessageOrigin.LuaMod);
|
||||
il.LoadLocal(exception);
|
||||
il.LoadConstant((int)LuaCsMessageOrigin.LuaMod); // underlying enum type is int
|
||||
il.Call(typeof(LuaCsLogger).GetMethod(nameof(LuaCsLogger.HandleException), BindingFlags.Public | BindingFlags.Static));
|
||||
});
|
||||
|
||||
il.EndCatchBlock(catchBlock);
|
||||
|
||||
il.EndExceptionBlock(exceptionBlock);
|
||||
|
||||
// Only prefixes return a bool
|
||||
if (hookType == LuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
il.LoadLocal(harmonyReturnValue);
|
||||
}
|
||||
il.Return();
|
||||
|
||||
var method = il.CreateMethod();
|
||||
for (var i = 0; i < parameters.Count; i++)
|
||||
{
|
||||
method.DefineParameter(i + 1, ParameterAttributes.None, parameters[i].ParameterName);
|
||||
}
|
||||
|
||||
var type = typeBuilder.CreateType();
|
||||
type.GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static).SetValue(null, LuaCsSetup.Instance);
|
||||
return type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
|
||||
}
|
||||
|
||||
private string Patch(string identifier, MethodBase method, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
if (method == null) throw new ArgumentNullException(nameof(method));
|
||||
if (patch == null) throw new ArgumentNullException(nameof(patch));
|
||||
ValidatePatchTarget(method);
|
||||
|
||||
identifier ??= Guid.NewGuid().ToString("N");
|
||||
identifier = NormalizeIdentifier(identifier);
|
||||
|
||||
var patchKey = MethodKey.Create(method);
|
||||
if (!registeredPatches.TryGetValue(patchKey, out var methodPatches))
|
||||
{
|
||||
var harmonyPrefix = CreateDynamicHarmonyPatch(identifier, method, LuaCsHook.HookMethodType.Before);
|
||||
var harmonyPostfix = CreateDynamicHarmonyPatch(identifier, method, LuaCsHook.HookMethodType.After);
|
||||
harmony.Patch(method, prefix: new HarmonyMethod(harmonyPrefix), postfix: new HarmonyMethod(harmonyPostfix));
|
||||
methodPatches = registeredPatches[patchKey] = new PatchedMethod(harmonyPrefix, harmonyPostfix);
|
||||
}
|
||||
|
||||
if (hookType == LuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
if (methodPatches.Prefixes.Remove(identifier))
|
||||
{
|
||||
LuaCsLogger.LogMessage($"Replacing existing prefix: {identifier}");
|
||||
}
|
||||
|
||||
methodPatches.Prefixes.Add(identifier, new LuaCsPatch
|
||||
{
|
||||
Identifier = identifier,
|
||||
PatchFunc = patch,
|
||||
});
|
||||
}
|
||||
else if (hookType == LuaCsHook.HookMethodType.After)
|
||||
{
|
||||
if (methodPatches.Postfixes.Remove(identifier))
|
||||
{
|
||||
LuaCsLogger.LogMessage($"Replacing existing postfix: {identifier}");
|
||||
}
|
||||
|
||||
methodPatches.Postfixes.Add(identifier, new LuaCsPatch
|
||||
{
|
||||
Identifier = identifier,
|
||||
PatchFunc = patch,
|
||||
});
|
||||
}
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public string Patch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
var method = ResolveMethod(className, methodName, parameterTypes);
|
||||
return Patch(identifier, method, patch, hookType);
|
||||
}
|
||||
|
||||
public string Patch(string identifier, string className, string methodName, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
var method = ResolveMethod(className, methodName, null);
|
||||
return Patch(identifier, method, patch, hookType);
|
||||
}
|
||||
|
||||
public string Patch(string className, string methodName, string[] parameterTypes, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
var method = ResolveMethod(className, methodName, parameterTypes);
|
||||
return Patch(null, method, patch, hookType);
|
||||
}
|
||||
|
||||
public string Patch(string className, string methodName, LuaCsPatchFunc patch, LuaCsHook.HookMethodType hookType = LuaCsHook.HookMethodType.Before)
|
||||
{
|
||||
var method = ResolveMethod(className, methodName, null);
|
||||
return Patch(null, method, patch, hookType);
|
||||
}
|
||||
|
||||
private bool RemovePatch(string identifier, MethodBase method, LuaCsHook.HookMethodType hookType)
|
||||
{
|
||||
if (identifier == null) throw new ArgumentNullException(nameof(identifier));
|
||||
identifier = NormalizeIdentifier(identifier);
|
||||
|
||||
var patchKey = MethodKey.Create(method);
|
||||
if (!registeredPatches.TryGetValue(patchKey, out var methodPatches))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return hookType switch
|
||||
{
|
||||
LuaCsHook.HookMethodType.Before => methodPatches.Prefixes.Remove(identifier),
|
||||
LuaCsHook.HookMethodType.After => methodPatches.Postfixes.Remove(identifier),
|
||||
_ => throw new ArgumentException($"Invalid {nameof(LuaCsHook.HookMethodType)} enum value.", nameof(hookType)),
|
||||
};
|
||||
}
|
||||
|
||||
public bool RemovePatch(string identifier, string className, string methodName, string[] parameterTypes, LuaCsHook.HookMethodType hookType)
|
||||
{
|
||||
var method = ResolveMethod(className, methodName, parameterTypes);
|
||||
return RemovePatch(identifier, method, hookType);
|
||||
}
|
||||
|
||||
public bool RemovePatch(string identifier, string className, string methodName, LuaCsHook.HookMethodType hookType)
|
||||
{
|
||||
var method = ResolveMethod(className, methodName, null);
|
||||
return RemovePatch(identifier, method, hookType);
|
||||
}
|
||||
|
||||
private void ClearAll()
|
||||
{
|
||||
harmony?.UnpatchSelf();
|
||||
|
||||
foreach (var (_, patch) in registeredPatches)
|
||||
{
|
||||
// Remove references stored in our dynamic types so the generated
|
||||
// assembly can be garbage-collected.
|
||||
patch.HarmonyPrefixMethod.DeclaringType
|
||||
.GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static)
|
||||
.SetValue(null, null);
|
||||
patch.HarmonyPostfixMethod.DeclaringType
|
||||
.GetField(FIELD_LUACS, BindingFlags.Public | BindingFlags.Static)
|
||||
.SetValue(null, null);
|
||||
}
|
||||
|
||||
registeredPatches.Clear();
|
||||
|
||||
compatHookPrefixMethods.Clear();
|
||||
compatHookPostfixMethods.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
|
||||
ClearAll();
|
||||
}
|
||||
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
ClearAll();
|
||||
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using MoonSharp.Interpreter;
|
||||
using MoonSharp.Interpreter.Loaders;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Barotrauma.LuaCs.Data;
|
||||
using Barotrauma.LuaCs;
|
||||
using FluentResults;
|
||||
|
||||
namespace Barotrauma.LuaCs
|
||||
{
|
||||
public class LuaScriptLoader : ScriptLoaderBase, ILuaScriptLoader
|
||||
{
|
||||
public LuaScriptLoader(ISafeStorageService storageService, Lazy<ILoggerService> loggerService)
|
||||
{
|
||||
this._storageService = storageService;
|
||||
this._loggerService = loggerService;
|
||||
storageService.UseCaching = true;
|
||||
}
|
||||
|
||||
private readonly ISafeStorageService _storageService;
|
||||
private readonly Lazy<ILoggerService> _loggerService;
|
||||
|
||||
public override object LoadFile(string file, Table globalContext)
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
if (file.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var res = _storageService.TryLoadText(file);
|
||||
|
||||
if (res.IsFailed || res is not { Value: { } script})
|
||||
{
|
||||
UnsafeLogErrors($"Failed to load file '{file}'.", res.ToResult());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (script.IsNullOrWhiteSpace())
|
||||
{
|
||||
UnsafeLogErrors($"The file '{file}' is empty. ", res.ToResult());
|
||||
return null;
|
||||
}
|
||||
|
||||
return script;
|
||||
}
|
||||
|
||||
public void ClearCaches()
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
_storageService?.PurgeCache();
|
||||
}
|
||||
|
||||
public void SetCachingPolicy(bool useCaching)
|
||||
{
|
||||
if (_storageService is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!useCaching)
|
||||
{
|
||||
_storageService.PurgeCache();
|
||||
}
|
||||
_storageService.UseCaching = useCaching;
|
||||
}
|
||||
|
||||
public async Task<Result<ImmutableArray<(ContentPath Path, Result<string>)>>> CacheResourcesAsync(ImmutableArray<ILuaScriptResourceInfo> resourceInfos)
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
if (!_storageService.UseCaching)
|
||||
{
|
||||
return FluentResults.Result.Fail($"Caching is not enabled.");
|
||||
}
|
||||
|
||||
return await this._storageService.LoadPackageTextFilesAsync([..resourceInfos.SelectMany(ri => ri.FilePaths)]);
|
||||
}
|
||||
|
||||
public override bool ScriptFileExists(string file)
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
var result = _storageService.FileExists(file);
|
||||
if (result is { IsFailed: true })
|
||||
{
|
||||
UnsafeLogErrors($"Unable to find and load file \"{file}\".", result.ToResult());
|
||||
return false;
|
||||
}
|
||||
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
private void UnsafeLogErrors(string message, FluentResults.Result result = null)
|
||||
{
|
||||
_loggerService.Value.LogError($"{nameof(LuaScriptLoader)}: {message}");
|
||||
if (result is null || result.Errors.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
_loggerService.Value.LogError($"{nameof(LuaScriptLoader)}: Error: {error.Message}.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!ModUtils.Threading.CheckIfClearAndSetBool(ref _isDisposed))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_storageService?.Dispose();
|
||||
_loggerService?.Value.Dispose();
|
||||
}
|
||||
|
||||
private int _isDisposed = 0;
|
||||
public bool IsDisposed => ModUtils.Threading.GetBool(ref _isDisposed);
|
||||
|
||||
public bool IsFileAccessible(string path, bool readOnly, bool checkWhitelistOnly = true)
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
return _storageService.IsFileAccessible(path, readOnly, checkWhitelistOnly);
|
||||
}
|
||||
|
||||
public void AddFileToWhitelist(string path, bool readOnly = true)
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
_storageService.AddFileToWhitelist(path, readOnly);
|
||||
}
|
||||
|
||||
public void AddFilesToWhitelist(ImmutableArray<string> paths, bool readOnly = true)
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
_storageService.AddFilesToWhitelist(paths, readOnly);
|
||||
}
|
||||
|
||||
public void RemoveFileFromAllWhitelists(string path)
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
_storageService.RemoveFileFromAllWhitelists(path);
|
||||
}
|
||||
|
||||
public FluentResults.Result SetReadOnlyWhitelist(ImmutableArray<string> filePaths)
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
return _storageService.SetReadOnlyWhitelist(filePaths);
|
||||
}
|
||||
|
||||
public FluentResults.Result SetReadWriteWhitelist(ImmutableArray<string> filePaths)
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
return _storageService.SetReadWriteWhitelist(filePaths);
|
||||
}
|
||||
|
||||
public void ClearAllWhitelists()
|
||||
{
|
||||
IService.CheckDisposed(this);
|
||||
_storageService.ClearAllWhitelists();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
using MoonSharp.Interpreter;
|
||||
using MoonSharp.Interpreter.Interop;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ILuaUserDataService : IReusableService
|
||||
{
|
||||
IReadOnlyDictionary<string, IUserDataDescriptor> Descriptors { get; }
|
||||
IUserDataDescriptor RegisterType(string typeName);
|
||||
void RegisterExtensionType(string typeName);
|
||||
bool IsRegistered(string typeName);
|
||||
void UnregisterType(string typeName, bool deleteHistory = false);
|
||||
object CreateStatic(string typeName);
|
||||
bool IsTargetType(object obj, string typeName);
|
||||
string TypeOf(object obj);
|
||||
object CreateEnumTable(string typeName);
|
||||
void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName);
|
||||
void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null);
|
||||
void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName);
|
||||
void AddMethod(IUserDataDescriptor IUUD, string methodName, object function);
|
||||
void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value);
|
||||
void RemoveMember(IUserDataDescriptor IUUD, string memberName);
|
||||
bool HasMember(object obj, string memberName);
|
||||
/// <summary>
|
||||
/// See <see cref="CreateUserDataFromType"/>.
|
||||
/// </summary>
|
||||
/// <param name="scriptObject">Lua value to convert and wrap in a userdata.</param>
|
||||
/// <param name="desiredTypeDescriptor">Descriptor of the type of the object to convert the Lua value to. Uses MoonSharp ScriptToClr converters.</param>
|
||||
/// <returns>A userdata that wraps the Lua value converted to an object of the desired type as described by <paramref name="desiredTypeDescriptor"/>.</returns>
|
||||
DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Lua value to a CLR object of a desired type and wraps it in a userdata.
|
||||
/// If the type is not registered, then a new <see cref="MoonSharp.Interpreter.Interop.StandardUserDataDescriptor"/> will be created and used.
|
||||
/// The goal of this method is to allow Lua scripts to create userdata to wrap certain data without having to register types.
|
||||
/// <remarks>Wrapping the value in a userdata preserves the original type during script-to-CLR conversions.</remarks>
|
||||
/// <example>A Lua script needs to pass a List`1 to a CLR method expecting System.Object, MoonSharp gets
|
||||
/// in the way by converting the List`1 to a MoonSharp.Interpreter.Table and breaking everything.
|
||||
/// Registering the List`1 type can break other scripts relying on default converters, so instead
|
||||
/// it is better to manually wrap the List`1 object into a userdata.
|
||||
/// </example>
|
||||
/// </summary>
|
||||
/// <param name="scriptObject">Lua value to convert and wrap in a userdata.</param>
|
||||
/// <param name="desiredType">Type describing the CLR type of the object to convert the Lua value to.</param>
|
||||
/// <returns>A userdata that wraps the Lua value converted to an object of the desired type.</returns>
|
||||
DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType);
|
||||
|
||||
void AddCallMetaTable(object userdata);
|
||||
}
|
||||
|
||||
public class LuaUserDataService : ILuaUserDataService
|
||||
{
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public IReadOnlyDictionary<string, IUserDataDescriptor> Descriptors => descriptors;
|
||||
private ConcurrentDictionary<string, IUserDataDescriptor> descriptors;
|
||||
|
||||
private readonly IPluginManagementService _pluginManagementService;
|
||||
|
||||
public LuaUserDataService(IPluginManagementService pluginManagementService)
|
||||
{
|
||||
descriptors = new ConcurrentDictionary<string, IUserDataDescriptor>();
|
||||
_pluginManagementService = pluginManagementService;
|
||||
}
|
||||
|
||||
public IUserDataDescriptor this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return descriptors.GetValueOrDefault(key);
|
||||
}
|
||||
}
|
||||
|
||||
private Type GetType(string typeName) => _pluginManagementService.GetType(typeName, includeInterfaces: true);
|
||||
|
||||
public IUserDataDescriptor RegisterType(string typeName)
|
||||
{
|
||||
Type type = GetType(typeName);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to register a type that doesn't exist: {typeName}.");
|
||||
}
|
||||
|
||||
var descriptor = UserData.RegisterType(type);
|
||||
descriptors.TryAdd(typeName, descriptor);
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
public void RegisterExtensionType(string typeName)
|
||||
{
|
||||
Type type = GetType(typeName);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to register a type that doesn't exist: {typeName}.");
|
||||
}
|
||||
|
||||
UserData.RegisterExtensionType(type);
|
||||
}
|
||||
|
||||
public bool IsRegistered(string typeName)
|
||||
{
|
||||
Type type = GetType(typeName);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return UserData.GetDescriptorForType(type, true) != null;
|
||||
}
|
||||
|
||||
public void UnregisterType(string typeName, bool deleteHistory = false)
|
||||
{
|
||||
Type type = GetType(typeName);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to unregister a type that doesn't exist: {typeName}.");
|
||||
}
|
||||
|
||||
UserData.UnregisterType(type, deleteHistory);
|
||||
}
|
||||
|
||||
public bool IsTargetType(object obj, string typeName)
|
||||
{
|
||||
if (obj == null) { throw new ScriptRuntimeException("userdata is nil"); }
|
||||
Type targetType = GetType(typeName);
|
||||
if (targetType == null) { throw new ScriptRuntimeException("target type not found"); }
|
||||
|
||||
Type type = obj is Type ? (Type)obj : obj.GetType();
|
||||
return targetType.IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
public string TypeOf(object obj)
|
||||
{
|
||||
if (obj == null) { throw new ScriptRuntimeException("userdata is nil"); }
|
||||
|
||||
return obj.GetType().FullName;
|
||||
}
|
||||
|
||||
public object CreateEnumTable(string typeName)
|
||||
{
|
||||
Type type = GetType(typeName);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to create an enum table with a type that doesn't exist:: {typeName}.");
|
||||
}
|
||||
|
||||
Dictionary<string, object> result = new Dictionary<string, object>();
|
||||
|
||||
foreach (var value in Enum.GetValues(type))
|
||||
{
|
||||
string name = Enum.GetName(type, value);
|
||||
|
||||
result[name] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public object CreateStatic(string typeName)
|
||||
{
|
||||
Type type = GetType(typeName);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to create a static userdata of a type that doesn't exist: {typeName}.");
|
||||
}
|
||||
|
||||
MethodInfo method = typeof(UserData).GetMethod(nameof(UserData.CreateStatic), 1, new Type[0]);
|
||||
MethodInfo generic = method.MakeGenericMethod(type);
|
||||
return generic.Invoke(null, null);
|
||||
}
|
||||
|
||||
private FieldInfo FindFieldRecursively(Type type, string fieldName)
|
||||
{
|
||||
var field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
|
||||
if (field == null && type.BaseType != null)
|
||||
{
|
||||
return FindFieldRecursively(type.BaseType, fieldName);
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
public void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName)
|
||||
{
|
||||
if (IUUD == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to make {fieldName} accessible.");
|
||||
}
|
||||
|
||||
var descriptor = (StandardUserDataDescriptor)IUUD;
|
||||
FieldInfo field = FindFieldRecursively(IUUD.Type, fieldName);
|
||||
|
||||
if (field == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to make field '{fieldName}' accessible, but the field doesn't exist.");
|
||||
}
|
||||
|
||||
descriptor.RemoveMember(fieldName);
|
||||
descriptor.AddMember(fieldName, new FieldMemberDescriptor(field, InteropAccessMode.Default));
|
||||
}
|
||||
|
||||
private MethodInfo FindMethodRecursively(Type type, string methodName, Type[] types = null)
|
||||
{
|
||||
MethodInfo method;
|
||||
|
||||
if (types == null)
|
||||
{
|
||||
method = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
}
|
||||
else
|
||||
{
|
||||
method = type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static, types);
|
||||
}
|
||||
|
||||
if (method == null && type.BaseType != null)
|
||||
{
|
||||
return FindMethodRecursively(type.BaseType, methodName, types);
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
public void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null)
|
||||
{
|
||||
if (IUUD == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to make {methodName} accessible.");
|
||||
}
|
||||
|
||||
Type[] parameterTypes = null;
|
||||
|
||||
|
||||
if (parameters != null)
|
||||
{
|
||||
parameterTypes = new Type[parameters.Length];
|
||||
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
Type type = GetType(parameters[i]);
|
||||
if (type == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"invalid parameter type '{parameters[i]}'");
|
||||
}
|
||||
parameterTypes[i] = type;
|
||||
}
|
||||
}
|
||||
|
||||
var descriptor = (StandardUserDataDescriptor)IUUD;
|
||||
|
||||
MethodBase method;
|
||||
|
||||
try
|
||||
{
|
||||
method = FindMethodRecursively(IUUD.Type, methodName, parameterTypes);
|
||||
}
|
||||
catch (AmbiguousMatchException ex)
|
||||
{
|
||||
throw new ScriptRuntimeException("ambiguous method signature.");
|
||||
}
|
||||
|
||||
if (method == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to make method '{methodName}' accessible, but the method doesn't exist.");
|
||||
}
|
||||
|
||||
descriptor.AddMember(methodName, new MethodMemberDescriptor(method, InteropAccessMode.Default));
|
||||
}
|
||||
|
||||
private PropertyInfo FindPropertyRecursively(Type type, string propertyName)
|
||||
{
|
||||
var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
|
||||
if (property == null && type.BaseType != null)
|
||||
{
|
||||
return FindPropertyRecursively(type.BaseType, propertyName);
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
|
||||
public void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName)
|
||||
{
|
||||
if (IUUD == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to make {propertyName} accessible.");
|
||||
}
|
||||
|
||||
var descriptor = (StandardUserDataDescriptor)IUUD;
|
||||
PropertyInfo property = FindPropertyRecursively(IUUD.Type, propertyName);
|
||||
|
||||
if (property == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to make property '{propertyName}' accessible, but the property doesn't exist.");
|
||||
}
|
||||
|
||||
descriptor.RemoveMember(propertyName);
|
||||
descriptor.AddMember(propertyName, new PropertyMemberDescriptor(property, InteropAccessMode.Default, property.GetGetMethod(true), property.GetSetMethod(true)));
|
||||
}
|
||||
|
||||
public void AddMethod(IUserDataDescriptor IUUD, string methodName, object function)
|
||||
{
|
||||
if (IUUD == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to add method {methodName}.");
|
||||
}
|
||||
|
||||
var descriptor = (StandardUserDataDescriptor)IUUD;
|
||||
|
||||
descriptor.RemoveMember(methodName);
|
||||
descriptor.AddMember(methodName, new ObjectCallbackMemberDescriptor(methodName, (object arg1, ScriptExecutionContext arg2, CallbackArguments arg3) =>
|
||||
{
|
||||
if (LuaCsSetup.Instance != null)
|
||||
{
|
||||
return LuaCsSetup.Instance.CallLuaFunction(function, arg3.GetArray());
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
}
|
||||
|
||||
public void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value)
|
||||
{
|
||||
if (IUUD == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to add field {fieldName}.");
|
||||
}
|
||||
|
||||
var descriptor = (StandardUserDataDescriptor)IUUD;
|
||||
descriptor.RemoveMember(fieldName);
|
||||
descriptor.AddMember(fieldName, new DynValueMemberDescriptor(fieldName, value));
|
||||
}
|
||||
|
||||
public void RemoveMember(IUserDataDescriptor IUUD, string memberName)
|
||||
{
|
||||
if (IUUD == null)
|
||||
{
|
||||
throw new ScriptRuntimeException($"tried to use a UserDataDescriptor that is null to remove the member {memberName}.");
|
||||
}
|
||||
|
||||
var descriptor = (StandardUserDataDescriptor)IUUD;
|
||||
descriptor.RemoveMember(memberName);
|
||||
}
|
||||
|
||||
public bool HasMember(object obj, string memberName)
|
||||
{
|
||||
if (obj == null) { throw new ScriptRuntimeException("object is nil"); }
|
||||
|
||||
Type type;
|
||||
if (obj is Type)
|
||||
{
|
||||
type = (Type)obj;
|
||||
}
|
||||
else if (obj is IUserDataDescriptor descriptor)
|
||||
{
|
||||
type = descriptor.Type;
|
||||
|
||||
if (((StandardUserDataDescriptor)descriptor).HasMember(memberName))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
type = obj.GetType();
|
||||
}
|
||||
|
||||
if (type.GetMember(memberName).Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor)
|
||||
{
|
||||
return UserData.Create(scriptObject.ToObject(desiredTypeDescriptor.Type), desiredTypeDescriptor);
|
||||
}
|
||||
|
||||
public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType)
|
||||
{
|
||||
IUserDataDescriptor descriptor = UserData.GetDescriptorForType(desiredType, true);
|
||||
descriptor ??= new StandardUserDataDescriptor(desiredType, InteropAccessMode.Default);
|
||||
return CreateUserDataFromDescriptor(scriptObject, descriptor);
|
||||
}
|
||||
|
||||
public void AddCallMetaTable(object userdata) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
descriptors.Clear();
|
||||
}
|
||||
|
||||
public FluentResults.Result Reset()
|
||||
{
|
||||
descriptors.Clear();
|
||||
return FluentResults.Result.Ok();
|
||||
}
|
||||
}
|
||||
+236
@@ -0,0 +1,236 @@
|
||||
using Barotrauma;
|
||||
using Barotrauma.LuaCs;
|
||||
using MoonSharp.Interpreter;
|
||||
using MoonSharp.Interpreter.Interop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Barotrauma.LuaCs;
|
||||
|
||||
public interface ISafeLuaUserDataService : IService
|
||||
{
|
||||
bool IsAllowed(string typeName);
|
||||
IUserDataDescriptor RegisterType(string typeName);
|
||||
void RegisterExtensionType(string typeName);
|
||||
bool IsRegistered(string typeName);
|
||||
void UnregisterType(string typeName, bool deleteHistory = false);
|
||||
object CreateStatic(string typeName);
|
||||
bool IsTargetType(object obj, string typeName);
|
||||
string TypeOf(object obj);
|
||||
object CreateEnumTable(string typeName);
|
||||
void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName);
|
||||
void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null);
|
||||
void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName);
|
||||
void AddMethod(IUserDataDescriptor IUUD, string methodName, object function);
|
||||
void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value);
|
||||
void RemoveMember(IUserDataDescriptor IUUD, string memberName);
|
||||
bool HasMember(object obj, string memberName);
|
||||
/// <summary>
|
||||
/// See <see cref="CreateUserDataFromType"/>.
|
||||
/// </summary>
|
||||
/// <param name="scriptObject">Lua value to convert and wrap in a userdata.</param>
|
||||
/// <param name="desiredTypeDescriptor">Descriptor of the type of the object to convert the Lua value to. Uses MoonSharp ScriptToClr converters.</param>
|
||||
/// <returns>A userdata that wraps the Lua value converted to an object of the desired type as described by <paramref name="desiredTypeDescriptor"/>.</returns>
|
||||
DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Lua value to a CLR object of a desired type and wraps it in a userdata.
|
||||
/// If the type is not registered, then a new <see cref="MoonSharp.Interpreter.Interop.StandardUserDataDescriptor"/> will be created and used.
|
||||
/// The goal of this method is to allow Lua scripts to create userdata to wrap certain data without having to register types.
|
||||
/// <remarks>Wrapping the value in a userdata preserves the original type during script-to-CLR conversions.</remarks>
|
||||
/// <example>A Lua script needs to pass a List`1 to a CLR method expecting System.Object, MoonSharp gets
|
||||
/// in the way by converting the List`1 to a MoonSharp.Interpreter.Table and breaking everything.
|
||||
/// Registering the List`1 type can break other scripts relying on default converters, so instead
|
||||
/// it is better to manually wrap the List`1 object into a userdata.
|
||||
/// </example>
|
||||
/// </summary>
|
||||
/// <param name="scriptObject">Lua value to convert and wrap in a userdata.</param>
|
||||
/// <param name="desiredType">Type describing the CLR type of the object to convert the Lua value to.</param>
|
||||
/// <returns>A userdata that wraps the Lua value converted to an object of the desired type.</returns>
|
||||
DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType);
|
||||
void AddCallMetaTable(object userdata);
|
||||
}
|
||||
|
||||
public class SafeLuaUserDataService : ISafeLuaUserDataService
|
||||
{
|
||||
private readonly ILuaUserDataService _userDataService;
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public SafeLuaUserDataService(ILuaUserDataService userDataService)
|
||||
{
|
||||
_userDataService = userDataService;
|
||||
}
|
||||
|
||||
public IUserDataDescriptor this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _userDataService.Descriptors.GetValueOrDefault(key);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanBeRegistered(string typeName)
|
||||
{
|
||||
if (typeName.StartsWith("Barotrauma.Lua", StringComparison.Ordinal) ||
|
||||
typeName.StartsWith("Barotrauma.Cs", StringComparison.Ordinal) ||
|
||||
typeName.StartsWith("Barotrauma.LuaCs", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeName == "System.Single") { return true; }
|
||||
|
||||
if (typeName == "System.Console") { return true; }
|
||||
|
||||
if (typeName.StartsWith("System.Collections", StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
if (typeName.StartsWith("Microsoft.Xna", StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
if (typeName.StartsWith("Barotrauma.IO", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
if (typeName.StartsWith("Barotrauma.ToolBox", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
if (typeName.StartsWith("Barotrauma.SaveUtil", StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
if (typeName.StartsWith("Barotrauma.", StringComparison.Ordinal))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CanBeReRegistered(string typeName)
|
||||
{
|
||||
if (typeName.StartsWith("Barotrauma.Lua", StringComparison.Ordinal) ||
|
||||
typeName.StartsWith("Barotrauma.Cs", StringComparison.Ordinal) ||
|
||||
typeName.StartsWith("Barotrauma.LuaCs", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsAllowed(string typeName)
|
||||
{
|
||||
if (!CanBeReRegistered(typeName) && IsRegistered(typeName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CanBeRegistered(typeName))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CheckAllowed(string typeName)
|
||||
{
|
||||
if (!IsAllowed(typeName))
|
||||
{
|
||||
throw new ScriptRuntimeException($"Type {typeName} can't be registered");
|
||||
}
|
||||
}
|
||||
|
||||
public IUserDataDescriptor RegisterType(string typeName)
|
||||
{
|
||||
CheckAllowed(typeName);
|
||||
return _userDataService.RegisterType(typeName);
|
||||
}
|
||||
|
||||
public void RegisterExtensionType(string typeName)
|
||||
{
|
||||
CheckAllowed(typeName);
|
||||
_userDataService.RegisterExtensionType(typeName);
|
||||
}
|
||||
|
||||
public bool IsRegistered(string typeName)
|
||||
{
|
||||
return _userDataService.IsRegistered(typeName);
|
||||
}
|
||||
|
||||
public void UnregisterType(string typeName, bool deleteHistory = false)
|
||||
{
|
||||
IsAllowed(typeName);
|
||||
_userDataService.UnregisterType(typeName, deleteHistory);
|
||||
}
|
||||
public object CreateStatic(string typeName)
|
||||
{
|
||||
return _userDataService.CreateStatic(typeName);
|
||||
}
|
||||
|
||||
public bool IsTargetType(object obj, string typeName)
|
||||
{
|
||||
return _userDataService.IsTargetType(obj, typeName);
|
||||
}
|
||||
|
||||
public string TypeOf(object obj)
|
||||
{
|
||||
return _userDataService.TypeOf(obj);
|
||||
}
|
||||
|
||||
public object CreateEnumTable(string typeName)
|
||||
{
|
||||
return _userDataService.CreateEnumTable(typeName);
|
||||
}
|
||||
|
||||
public void MakeFieldAccessible(IUserDataDescriptor IUUD, string fieldName)
|
||||
{
|
||||
_userDataService.MakeFieldAccessible(IUUD, fieldName);
|
||||
}
|
||||
|
||||
public void MakeMethodAccessible(IUserDataDescriptor IUUD, string methodName, string[] parameters = null)
|
||||
{
|
||||
_userDataService.MakeMethodAccessible(IUUD, methodName, parameters);
|
||||
}
|
||||
|
||||
public void MakePropertyAccessible(IUserDataDescriptor IUUD, string propertyName)
|
||||
{
|
||||
_userDataService.MakePropertyAccessible(IUUD, propertyName);
|
||||
}
|
||||
|
||||
public void AddMethod(IUserDataDescriptor IUUD, string methodName, object function)
|
||||
{
|
||||
_userDataService.AddMethod(IUUD, methodName, function);
|
||||
}
|
||||
|
||||
public void AddField(IUserDataDescriptor IUUD, string fieldName, DynValue value)
|
||||
{
|
||||
_userDataService.AddField(IUUD, fieldName, value);
|
||||
}
|
||||
|
||||
public void RemoveMember(IUserDataDescriptor IUUD, string memberName)
|
||||
{
|
||||
_userDataService.RemoveMember(IUUD, memberName);
|
||||
}
|
||||
|
||||
public bool HasMember(object obj, string memberName)
|
||||
{
|
||||
return _userDataService.HasMember(obj, memberName);
|
||||
}
|
||||
|
||||
public DynValue CreateUserDataFromDescriptor(DynValue scriptObject, IUserDataDescriptor desiredTypeDescriptor)
|
||||
{
|
||||
return _userDataService.CreateUserDataFromDescriptor(scriptObject, desiredTypeDescriptor);
|
||||
}
|
||||
|
||||
public DynValue CreateUserDataFromType(DynValue scriptObject, Type desiredType)
|
||||
{
|
||||
return _userDataService.CreateUserDataFromType(scriptObject, desiredType);
|
||||
}
|
||||
|
||||
public void AddCallMetaTable(object userdata) { }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user