using Barotrauma; using Microsoft.Xna.Framework; using MoonSharp.Interpreter; using Xunit; using Xunit.Abstractions; namespace TestProject.LuaCs { [Collection("LuaCs")] public class HookPatchTests : IClassFixture { private readonly LuaCsSetup luaCs; public HookPatchTests(LuaCsFixture luaCsFixture, ITestOutputHelper output) { // XXX: we can't have multiple instances of LuaCs patching the // same methods, otherwise we get script ownership exceptions. luaCs = luaCsFixture.LuaCs; luaCs.MessageLogger = (prefix, o) => { o ??= "null"; output?.WriteLine(prefix + o); }; UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); luaCs.Initialize(); luaCs.Lua.Globals["TestValueType"] = UserData.CreateStatic(); luaCs.Lua.Globals["InterfaceImplementingType"] = UserData.CreateStatic(); } private class PatchTargetSimple { public bool ran; public void Run() { ran = true; } } [Fact] public void TestFullMethodReplacement() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetSimple(); using var patchHandle = luaCs.AddPrefix("ptable.PreventExecution = true"); target.Run(); Assert.False(target.ran); } [Fact] public void TestOverrideExistingPatch() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetSimple(); using var patchHandle = luaCs.AddPrefix(@" ptable.PreventExecution = true originalPatchRan = true ", patchId: "test"); target.Run(); Assert.False(target.ran); Assert.True(luaCs.Lua.Globals["originalPatchRan"] as bool?); // Reset this global so we can test if the original patch ran // after replacing it. luaCs.Lua.Globals["originalPatchRan"] = false; // Replace the existing prefix, but don't prevent execution this time luaCs.AddPrefix("replacementPatchRan = true", patchId: "test"); target.Run(); Assert.True(target.ran); // Make sure the original patch didn't run Assert.False(luaCs.Lua.Globals["originalPatchRan"] as bool?); // Test if the replacement patch ran Assert.True(luaCs.Lua.Globals["replacementPatchRan"] as bool?); } [Fact] public void TestRemovePrefix() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetSimple(); using (var patchHandle = luaCs.AddPrefix(@" ptable.PreventExecution = true patchRan = true ")) { target.Run(); Assert.False(target.ran); Assert.True(luaCs.Lua.Globals["patchRan"] as bool?); luaCs.Lua.Globals["patchRan"] = false; } target.Run(); Assert.True(target.ran); Assert.False(luaCs.Lua.Globals["patchRan"] as bool?); } [Fact] public void TestRemovePostfix() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetSimple(); using (var patchHandle = luaCs.AddPostfix(@" patchRan = true ")) { target.Run(); Assert.True(target.ran); Assert.True(luaCs.Lua.Globals["patchRan"] as bool?); target.ran = false; luaCs.Lua.Globals["patchRan"] = false; } target.Run(); Assert.True(target.ran); Assert.False(luaCs.Lua.Globals["patchRan"] as bool?); } public struct TestValueType { public int foo; public TestValueType(int foo) { this.foo = foo; } } private class PatchTargetReturnsObject { public bool ran; public object Run() { ran = true; return 5; } } public interface IBogusInterface { int GetFoo(); } private class InterfaceImplementingType : IBogusInterface { private readonly int foo; public InterfaceImplementingType(int foo) { this.foo = foo; } public int GetFoo() => foo; } [Fact] public void TestReturnBoxed() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetReturnsObject(); using var patchHandle = luaCs.AddPrefix(@" ptable.PreventExecution = true return 123 "); var returnValue = target.Run(); Assert.False(target.ran); Assert.Equal(123, (int)(double)returnValue); } [Fact] public void TestReturnVoid() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetReturnsObject(); // This should have no effect using var patchHandle = luaCs.AddPrefix("return"); var returnValue = target.Run(); Assert.True(target.ran); Assert.Equal(5, returnValue); } [Fact] public void TestReturnNil() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetReturnsObject(); // This should modify the return value to "null" using var patchHandle = luaCs.AddPostfix("return nil"); var returnValue = target.Run(); Assert.True(target.ran); Assert.Null(returnValue); } [Fact] public void TestReturnValueType() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetReturnsObject(); using var patchHandle = luaCs.AddPostfix(@" return TestValueType.__new(100) "); var returnValue = target.Run(); Assert.True(target.ran); Assert.IsType(returnValue); Assert.Equal(100, ((TestValueType)returnValue).foo); } private class PatchTargetReturnsInterface { public bool ran; public IBogusInterface Run() { ran = true; return new InterfaceImplementingType(5); } } [Fact] public void TestReturnInterfaceImplementingType() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetReturnsInterface(); using var patchHandle = luaCs.AddPostfix(@" return InterfaceImplementingType.__new(100); "); var returnValue = target.Run()!; Assert.True(target.ran); Assert.Equal(100, returnValue.GetFoo()); } private class PatchTargetModifyParams { public bool ran; public void Run(int a, out string outString, ref byte refByte, string b) { ran = true; outString = a + b + refByte; } } [Fact] public void TestModifyParameters() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetModifyParams(); using var patchHandle = luaCs.AddPrefix(@" ptable['a'] = Int32(100) ptable['b'] = 'abc' ptable['refByte'] = Byte(4) "); byte refByte = 123; target.Run(5, out var outString, ref refByte, "foo"); Assert.True(target.ran); Assert.Equal("100abc4", outString); } private class PatchTargetVector2 { public bool ran; public string Run(Vector2 vec) { ran = true; return vec.ToString(); } } [Fact] public void TestParameterValueType() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetVector2(); using var patchHandle = luaCs.AddPrefix("patchRan = true"); var returnValue = target.Run(new Vector2(1, 2)); Assert.True(target.ran); Assert.True(luaCs.Lua.Globals["patchRan"] as bool?); Assert.Equal("{X:1 Y:2}", returnValue); } private class PatchTargetNumbers { public bool ran; public sbyte RunSByte(sbyte v) { ran = true; return v; } public byte RunByte(byte v) { ran = true; return v; } public short RunInt16(short v) { ran = true; return v; } public ushort RunUInt16(ushort v) { ran = true; return v; } public int RunInt32(int v) { ran = true; return v; } public uint RunUInt32(uint v) { ran = true; return v; } public long RunInt64(long v) { ran = true; return v; } public ulong RunUInt64(ulong v) { ran = true; return v; } public float RunSingle(float v) { ran = true; return v; } public double RunDouble(double v) { ran = true; return v; } } [Fact] public void TestCastSByte() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetNumbers(); using var patchHandle = luaCs.AddPrefix(@" ptable['v'] = SByte(-6) ", methodName: nameof(PatchTargetNumbers.RunSByte)); var returnValue = target.RunSByte(-5); Assert.True(target.ran); Assert.Equal(-6, returnValue); } [Fact] public void TestCastByte() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetNumbers(); using var patchHandle = luaCs.AddPrefix(@" ptable['v'] = Byte(6) ", methodName: nameof(PatchTargetNumbers.RunByte)); var returnValue = target.RunByte(5); Assert.True(target.ran); Assert.Equal(6, returnValue); } [Fact] public void TestCastInt16() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetNumbers(); using var patchHandle = luaCs.AddPrefix(@" ptable['v'] = Int16(-25000) ", methodName: nameof(PatchTargetNumbers.RunInt16)); var returnValue = target.RunInt16(30000); Assert.True(target.ran); Assert.Equal(-25000, returnValue); } [Fact] public void TestCastUInt16() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetNumbers(); using var patchHandle = luaCs.AddPrefix(@" ptable['v'] = UInt16(60000) ", methodName: nameof(PatchTargetNumbers.RunUInt16)); var returnValue = target.RunUInt16(50000); Assert.True(target.ran); Assert.Equal(60000, returnValue); } [Fact] public void TestCastInt32() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetNumbers(); using var patchHandle = luaCs.AddPrefix(@" ptable['v'] = Int32('7FFFFF00', 16) ", methodName: nameof(PatchTargetNumbers.RunInt32)); var returnValue = target.RunInt32(900000); Assert.True(target.ran); Assert.Equal(0x7FFFFF00, returnValue); } [Fact] public void TestCastUInt32() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetNumbers(); using var patchHandle = luaCs.AddPrefix(@" ptable['v'] = UInt32('AFFFFFFF', 16) ", methodName: nameof(PatchTargetNumbers.RunUInt32)); var returnValue = target.RunUInt32(300500); Assert.True(target.ran); Assert.Equal(0xAFFFFFFF, returnValue); } [Fact] public void TestCastInt64() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetNumbers(); using var patchHandle = luaCs.AddPrefix(@" ptable['v'] = Int64('7555555555555555', 16) ", methodName: nameof(PatchTargetNumbers.RunInt64)); var returnValue = target.RunInt64(0x7FFFFFFF00000000); Assert.True(target.ran); Assert.Equal(0x7555555555555555, returnValue); } [Fact] public void TestCastUInt64() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetNumbers(); using var patchHandle = luaCs.AddPrefix(@" ptable['v'] = UInt64('F555555555555555', 16) ", methodName: nameof(PatchTargetNumbers.RunUInt64)); var returnValue = target.RunUInt64(0xFFFFFFFF00000000); Assert.True(target.ran); Assert.Equal(0xF555555555555555, returnValue); } [Fact] public void TestCastSingle() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetNumbers(); using var patchHandle = luaCs.AddPrefix(@" ptable['v'] = Single(123.456) ", methodName: nameof(PatchTargetNumbers.RunSingle)); var returnValue = target.RunSingle(111.111f); Assert.True(target.ran); Assert.Equal(123.456f, returnValue); } [Fact] public void TestCastDouble() { using var patchTargetHandle = HookPatchHelpers.LockPatchTarget(); var target = new PatchTargetNumbers(); using var patchHandle = luaCs.AddPrefix(@" ptable['v'] = Double(123.456) ", methodName: nameof(PatchTargetNumbers.RunDouble)); var returnValue = target.RunDouble(111.111d); Assert.True(target.ran); Assert.Equal(123.456d, returnValue); } } }