diff --git a/PostNamazu/Actions/Command.cs b/PostNamazu/Actions/Command.cs index 33df467..ba0dc42 100644 --- a/PostNamazu/Actions/Command.cs +++ b/PostNamazu/Actions/Command.cs @@ -1,8 +1,10 @@ -using GreyMagic; -using PostNamazu.Attributes; -using System; +using System; +using System.Collections.Generic; using System.Text; using System.Threading; +using PostNamazu.Attributes; +using static PostNamazu.Common.I18n; +using GreyMagic; namespace PostNamazu.Actions { @@ -14,8 +16,21 @@ internal class Command : NamazuModule public override void GetOffsets() { base.GetOffsets(); - ProcessChatBoxPtr = SigScanner.ScanText("E8 ?? ?? ?? ?? FE 86 ?? ?? ?? ?? C7 86", nameof(ProcessChatBoxPtr)); - GetUiModulePtr = SigScanner.ScanText("E8 ?? ?? ?? ?? 80 7B 1D 01", nameof(GetUiModulePtr)); + ProcessChatBoxPtr = SigScanner.ScanText("E8 * * * * FE 86 ? ? ? ? C7 86", nameof(ProcessChatBoxPtr)); + GetUiModulePtr = SigScanner.ScanText("E8 * * * * 80 7B 1D 01", nameof(GetUiModulePtr)); + } + + const string CurrentChannelPrefix = "/current "; + void CheckChannel(ref string command) + { + if (!command.StartsWith("/")) + { + throw new ArgumentException(GetLocalizedString("NoChannelError")); + } + if (command.StartsWith(CurrentChannelPrefix)) + { + command = command.Substring(CurrentChannelPrefix.Length); + } } /// @@ -26,13 +41,11 @@ public override void GetOffsets() public void DoTextCommand(string command) { CheckBeforeExecution(command); + CheckChannel(ref command); PluginUI.Log(command); - var assemblyLock = Memory.Executor.AssemblyLock; - - var flag = false; - try { - Monitor.Enter(assemblyLock, ref flag); + ExecuteWithLock(() => + { var array = Encoding.UTF8.GetBytes(command); using AllocatedMemory allocatedMemory = Memory.CreateAllocatedMemory(400), allocatedMemory2 = Memory.CreateAllocatedMemory(array.Length + 30); allocatedMemory2.AllocateOfChunk("cmd", array.Length); @@ -48,11 +61,17 @@ public void DoTextCommand(string command) var uiModulePtr = Memory.CallInjected64(GetUiModulePtr, PostNamazu.FrameworkPtr); var raptureModule = Memory.CallInjected64(Memory.Read(Memory.Read(uiModulePtr) + (0x8 * 9)), uiModulePtr); _ = Memory.CallInjected64(ProcessChatBoxPtr, raptureModule, allocatedMemory.Address, uiModulePtr); - } - finally { - if (flag) Monitor.Exit(assemblyLock); - } + }); } + + protected override Dictionary> LocalizedStrings { get; } = new() + { + ["NoChannelError"] = new() + { + [Language.EN] = $"To avoid sending wrong text to public channels, only commands starting with \"/\" are permitted. Add the prefix \"{CurrentChannelPrefix}\" to post to the current channel.", + [Language.CN] = $"为防止误操作导致错误文本发送至公共频道,仅允许以 \"/\" 开头的指令。如需发送至当前频道,请加前缀 \"{CurrentChannelPrefix}\"。" + }, + }; } } \ No newline at end of file diff --git a/PostNamazu/Actions/Mark.cs b/PostNamazu/Actions/Mark.cs index 4870c5e..18dfc45 100644 --- a/PostNamazu/Actions/Mark.cs +++ b/PostNamazu/Actions/Mark.cs @@ -21,15 +21,16 @@ public override void GetOffsets() { //char __fastcall sub_1407A6A60(__int64 g_MarkingController, __int64 MarkType, __int64 ActorID) try { - MarkingFunc = SigScanner.ScanText("E8 ?? ?? ?? ?? 84 C0 74 ?? 48 8B 06 48 8B CE FF 50 ?? 48 8B C8 BA ?? ?? ?? ?? E8 ?? ?? ?? ?? 33 C0 E9", nameof(MarkingFunc)); + MarkingFunc = SigScanner.ScanText("E8 * * * * 84 C0 74 ? 48 8B 06 48 8B CE FF 50 ? 48 8B C8 BA ? ? ? ? E8 ? ? ? ? 33 C0 E9", nameof(MarkingFunc)); } catch { MarkingFunc = SigScanner.ScanText("48 89 5C 24 10 48 89 6C 24 18 57 48 83 EC 20 8D 42", nameof(MarkingFunc)); } - LocalMarkingFunc = SigScanner.ScanText("E8 ?? ?? ?? ?? 4C 8B C5 8B D7 48 8B CB E8", nameof(LocalMarkingFunc)); //正确 - MarkingController = SigScanner.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 4C 8B 85", 3, nameof(MarkingController)); //正确 + LocalMarkingFunc = SigScanner.ScanText("E8 * * * * 4C 8B C5 8B D7 48 8B CB E8", nameof(LocalMarkingFunc)); //正确 + MarkingController = SigScanner.ScanText("48 8D 0D * * * * 4C 8B 85", nameof(MarkingController)); //正确 } + [Command("mark")] public void DoMarking(string command) { @@ -65,18 +66,12 @@ private void MarkActor(FFXIV_ACT_Plugin.Common.Models.Combatant actor, MarkType { PluginUI.Log($"Mark: Actor={actor.Name} (0x{actor.ID:X8}), Type={markingType} ({(int)markingType}), LocalOnly={localOnly}"); } - var assemblyLock = Memory.Executor.AssemblyLock; - var flag = false; - try + ExecuteWithLock(() => { - Monitor.Enter(assemblyLock, ref flag); _ = !localOnly - ? Memory.CallInjected64(MarkingFunc, MarkingController, markingType, actor.ID) + ? Memory.CallInjected64(MarkingFunc, MarkingController, markingType, actor.ID) : Memory.CallInjected64(LocalMarkingFunc, MarkingController, markingType - 1, actor.ID, 0); //本地标点的markingType从0开始,因此需要-1 - } - finally { - if (flag) Monitor.Exit(assemblyLock); - } + }); } protected override Dictionary> LocalizedStrings { get; } = new() diff --git a/PostNamazu/Actions/NormalCommand.cs b/PostNamazu/Actions/NormalCommand.cs index 60f58fe..4ee5289 100644 --- a/PostNamazu/Actions/NormalCommand.cs +++ b/PostNamazu/Actions/NormalCommand.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Text; using System.Threading; using PostNamazu.Attributes; +using static PostNamazu.Common.I18n; using GreyMagic; namespace PostNamazu.Actions @@ -14,13 +16,28 @@ internal class NormalCommand : NamazuModule public override void GetOffsets() { base.GetOffsets(); - try + try // 7.1 + { + ProcessChatBoxPtr = SigScanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F2 48 8B F9 45 84 C9"); + } + catch // 7.0 { ProcessChatBoxPtr = SigScanner.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9", nameof(ProcessChatBoxPtr)); - }catch { - ProcessChatBoxPtr = SigScanner.ScanText("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 48 8B F2 48 8B F9 45 84 C9", nameof(ProcessChatBoxPtr)); } - GetUiModulePtr = SigScanner.ScanText("E8 ?? ?? ?? ?? 80 7B 1D 01", nameof(GetUiModulePtr)); + GetUiModulePtr = SigScanner.ScanText("E8 * * * * 80 7B 1D 01", nameof(GetUiModulePtr)); + } + + const string CurrentChannelPrefix = "/current "; + void CheckChannel(ref string command) + { + if (!command.StartsWith("/")) + { + throw new ArgumentException(GetLocalizedString("NoChannelError")); + } + if (command.StartsWith(CurrentChannelPrefix)) + { + command = command.Substring(CurrentChannelPrefix.Length); + } } /// @@ -31,14 +48,11 @@ public override void GetOffsets() public void DoNormalTextCommand(string command) { CheckBeforeExecution(command); - + CheckChannel(ref command); PluginUI.Log(command); - var assemblyLock = Memory.Executor.AssemblyLock; - - var flag = false; - try { - Monitor.Enter(assemblyLock, ref flag); + ExecuteWithLock(() => + { var array = Encoding.UTF8.GetBytes(command); using AllocatedMemory allocatedMemory = Memory.CreateAllocatedMemory(400), allocatedMemory2 = Memory.CreateAllocatedMemory(array.Length + 30); allocatedMemory2.AllocateOfChunk("cmd", array.Length); @@ -52,11 +66,17 @@ public void DoNormalTextCommand(string command) allocatedMemory.Write("tLength", array.Length + 1); allocatedMemory.Write("t3", 0x00); var uiModulePtr = Memory.CallInjected64(GetUiModulePtr, PostNamazu.FrameworkPtr); - _ = Memory.CallInjected64(ProcessChatBoxPtr, uiModulePtr,allocatedMemory.Address, IntPtr.Zero, (byte)0); - } - finally { - if (flag) Monitor.Exit(assemblyLock); - } + _ = Memory.CallInjected64(ProcessChatBoxPtr, uiModulePtr, allocatedMemory.Address, IntPtr.Zero, (byte)0); + }); } + + protected override Dictionary> LocalizedStrings { get; } = new() + { + ["NoChannelError"] = new() + { + [Language.EN] = $"To avoid sending wrong text to public channels, only commands starting with \"/\" are permitted. Add the prefix \"{CurrentChannelPrefix}\" to post to the current channel.", + [Language.CN] = $"为防止误操作导致错误文本发送至公共频道,仅允许以 \"/\" 开头的指令。如需发送至当前频道,请加前缀 \"{CurrentChannelPrefix}\"。" + }, + }; } } diff --git a/PostNamazu/Actions/Preset.cs b/PostNamazu/Actions/Preset.cs index 58ba46e..b633cec 100644 --- a/PostNamazu/Actions/Preset.cs +++ b/PostNamazu/Actions/Preset.cs @@ -18,11 +18,11 @@ internal class Preset : NamazuModule public override void GetOffsets() { base.GetOffsets(); - var GetUiModulePtr = SigScanner.ScanText("E8 ?? ?? ?? ?? 80 7B 1D 01", "GetUiModulePtr"); + var GetUiModulePtr = SigScanner.ScanText("E8 * * * * 80 7B 1D 01", "GetUiModulePtr"); UIModulePtr = Memory.CallInjected64(GetUiModulePtr, PostNamazu.FrameworkPtr); var mapIDOffset = SigScanner.Read(SigScanner.ScanText("44 89 81 ? ? ? ? 0F B7 84 24", "mapIDOffset") + 3); - MapIDPtr = SigScanner.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 0F B6 55 ?? 24", 3, nameof(MapIDPtr)) + mapIDOffset; + MapIDPtr = SigScanner.ScanText("48 8D 0D * * * * 0F B6 55 ?? 24", nameof(MapIDPtr)) + mapIDOffset; } private void GetWayMarkSlotOffset() diff --git a/PostNamazu/Actions/WayMark.cs b/PostNamazu/Actions/WayMark.cs index 2f22d15..6d6f9eb 100644 --- a/PostNamazu/Actions/WayMark.cs +++ b/PostNamazu/Actions/WayMark.cs @@ -22,12 +22,11 @@ public class WayMark : NamazuModule public override void GetOffsets() { base.GetOffsets(); - MarkingController = SigScanner.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? 4C 8B 85", 3, nameof(MarkingController)); - // 41 D1 C0 88 81 ? ? ? ? 8B 42 04 + MarkingController = SigScanner.ScanText("48 8D 0D * * * * 4C 8B 85", nameof(MarkingController)); Waymarks = MarkingController + 0x1E0; try { - ExecuteCommandPtr = SigScanner.ScanText("E8 ?? ?? ?? ?? 48 83 C4 ?? C3 CC CC CC CC CC CC CC CC CC CC CC CC 48 83 EC ?? 45 0F B6 C0", nameof(ExecuteCommandPtr)); + ExecuteCommandPtr = SigScanner.ScanText("E8 * * * * 48 83 C4 ?? C3 CC CC CC CC CC CC CC CC CC CC CC CC 48 83 EC ?? 45 0F B6 C0", nameof(ExecuteCommandPtr)); } catch { // 可能和其他插件冲突,加一个备用 @@ -201,12 +200,9 @@ private void WriteWaymark(Waymark waymark, int id = -1) /// 标点,传入 null 时清空标点,单个标点为 null 时忽略。 public void Public(WayMarks waymarks) { - var assemblyLock = Memory.Executor.AssemblyLock; - var flag = false; - try + ExecuteWithLock(() => { - Monitor.Enter(assemblyLock, ref flag); - if (waymarks == null || waymarks.All(waymark => waymark?.Active == false)) + if (waymarks == null || waymarks.All(waymark => waymark?.Active == false)) { // clear all Memory.CallInjected64(ExecuteCommandPtr, 313, 0, 0, 0, 0); if (waymarks == null) @@ -231,11 +227,7 @@ public void Public(WayMarks waymarks) } } } - } - finally - { - if (flag) Monitor.Exit(assemblyLock); - } + }); } public bool GetInCombat() diff --git a/PostNamazu/Common/I18n.cs b/PostNamazu/Common/I18n.cs index b9daac0..48a0ada 100644 --- a/PostNamazu/Common/I18n.cs +++ b/PostNamazu/Common/I18n.cs @@ -66,7 +66,7 @@ public enum Language { EN, CN } ["PostNamazu/XivDetectMemRegionGlobal"] = "Set region to global server (detected by memory).", ["PostNamazu/XivDetectRegionCN"] = "Set region to Chinese server (detected by player name).", ["PostNamazu/XivDetectRegionGlobal"] = "Set region to global server (detected by player name).", - ["PostNamazu/XivFrameworkNotFound"] = "Failed to find memory signature for Framework, some features will not be available. The plugin may need to be updated.", + ["PostNamazu/XivFrameworkNotFound"] = "Failed to find memory signature for Framework, some features will not be available. The plugin may need to be updated. Exception: {0}", ["PostNamazu/XivProcInject"] = "Found FFXIV process {0}.", ["PostNamazu/XivProcInjectException"] = "Error when injecting into FFXIV process, retry later: \n{0}", ["PostNamazu/XivProcInjectFail"] = "Unable to inject into the current FFXIV process, it may have already been injected by another process. Please try restarting the game.", @@ -75,6 +75,7 @@ public enum Language { EN, CN } ["PostNamazuUi/CfgReset"] = "Configuration has been reset.", ["PostNamazuUi/ExportWaymarks"] = "Waymarker text has been copied to the clipboard.", ["PostNamazuUi/ExportWaymarksFail"] = "Failed to read existing waymarkers:\n{0}", + ["SigScanner/RelAddressingFormatError"] = "Relative addressing sigcode ({0}) must contain exactly 4 consecutive stars (* * * *) and no additional * elsewhere.", ["SigScanner/ResultMultiple"] = "Scanned{0} and found {1} memory signatures, unable to determine a unique location.", ["SigScanner/ResultNone"] = "Scanned{0} and did not find the required memory signatures.", diff --git a/PostNamazu/Common/NamazuModule.cs b/PostNamazu/Common/NamazuModule.cs index 77937da..0a50fee 100644 --- a/PostNamazu/Common/NamazuModule.cs +++ b/PostNamazu/Common/NamazuModule.cs @@ -4,6 +4,7 @@ using GreyMagic; using System.Collections.Generic; using static PostNamazu.Common.I18n; +using System.Threading; namespace PostNamazu.Actions { @@ -128,6 +129,34 @@ protected string GetLocalizedString(string key, params object[] args) internal class IgnoredException : Exception { internal IgnoredException(string msg) : base(msg) { } } + + public static void ExecuteWithLock(Action action) + { + bool lockTaken = false; + try + { + Monitor.Enter(Memory.Executor.AssemblyLock, ref lockTaken); + action(); + } + finally + { + if (lockTaken) Monitor.Exit(Memory.Executor.AssemblyLock); + } + } + + public static T ExecuteWithLock(Func function) + { + bool lockTaken = false; + try + { + Monitor.Enter(Memory.Executor.AssemblyLock, ref lockTaken); + return function(); + } + finally + { + if (lockTaken) Monitor.Exit(Memory.Executor.AssemblyLock); + } + } } } diff --git a/PostNamazu/Common/SigScanner.cs b/PostNamazu/Common/SigScanner.cs index 058e053..76825cb 100644 --- a/PostNamazu/Common/SigScanner.cs +++ b/PostNamazu/Common/SigScanner.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Runtime.InteropServices; namespace PostNamazu.Common @@ -375,33 +376,27 @@ public SigScanner(Process process) { } - public T ScanText(string pattern, Func visitor, string name = null) { - var results = FindPattern(pattern); - if (results.Count > 1) - { - throw new ArgumentException(I18n.Translate( - "SigScanner/ResultMultiple", - "扫描{0}时匹配到 {1} 处内存签名,无法确定唯一位置。", - name == null ? "" : $" {name} ", - results.Count - )); - } - if (results.Count == 0) - { - throw new ArgumentException(I18n.Translate( - "SigScanner/ResultNone", - "扫描{0}时未匹配到所需的内存签名。", - name == null ? "" : $" {name} " - )); - } - return visitor(results[0]); + var result = ScanText(pattern, name); + return visitor(result); } + /// + /// 从内存中扫描指定的内存签名,返回唯一匹配的地址,否则报错。

+ /// + /// ? 或 ?? 表示普通通配符,如:
+ /// 48 89 5C 24 ?? ...

+ /// + /// * 或 ** 表示相对寻址通配符,如果使用,则仅能有连续四个,如:
+ /// 48 8D 0D * * * * 4C 8B 85 ...
+ /// E8 * * * * 48 83 C4 ? E9 ? ? ? ? ...
+ /// 相对寻址计算方式为 * * * * 后的地址 + 这四字节对应的 int 偏移量。

+ ///
public IntPtr ScanText(string pattern, string name = null) { - var results = FindPattern(pattern); + (var bytes, int? relAddressingOffset) = HexToBytes(pattern); + var results = FindPattern(bytes); if (results.Count > 1) { throw new ArgumentException(I18n.Translate( @@ -419,24 +414,25 @@ public IntPtr ScanText(string pattern, string name = null) name == null ? "" : $" {name} " )); } - var scanRet = results[0]; - var insnByte = ReadByte(scanRet); - if (insnByte == 0xE8 || insnByte == 0xE9) - return ReadCallSig(scanRet); - return scanRet; - } - private IntPtr ReadCallSig(IntPtr sigLocation) { - var jumpOffset = ReadInt32(IntPtr.Add(sigLocation, 1)); - return IntPtr.Add(sigLocation, 5 + jumpOffset); + IntPtr patternPtr = results[0]; + if (relAddressingOffset.HasValue) // 指定相对寻址 + { + IntPtr starPtr = patternPtr + relAddressingOffset.Value; // 第一个 * 的地址 + patternPtr = starPtr + 4 + ReadInt32(starPtr); + } +#if DEBUG + PostNamazu.Plugin.PluginUI.Log($"[Scanner] {name ?? ""} ({pattern}) @ {patternPtr} (jump={relAddressingOffset?.ToString() ?? "null"})"); +#endif + return patternPtr; } - - public List FindPattern(string pattern) { - var results = Find(HexToBytes(pattern)); - for (int i = 0; i < results.Count; i++) { + public List FindPattern(List pattern) + { + var results = Find(pattern); + for (int i = 0; i < results.Count; i++) + { results[i] = _baseAddress + (int)results[i]; - } return results; } @@ -455,7 +451,7 @@ List Find(List pattern) { bool ByteMatch(byte[] bytes, int start, List pattern) { for (int i = start, j = 0; j < pattern.Count; i++, j++) { - if (pattern[j] == -1) + if (pattern[j] < 0) continue; if (bytes[i] != pattern[j]) @@ -464,29 +460,32 @@ bool ByteMatch(byte[] bytes, int start, List pattern) { return true; } - List HexToBytes(string hex) { - List bytes = new List(); - - for (int i = 0; i < hex.Length - 1;) { - if (hex[i] == '?') { - if (hex[i + 1] == '?') - i++; - i++; - bytes.Add(-1); - continue; - } - if (hex[i] == ' ') { - i++; - continue; + (List, int?) HexToBytes(string hex) + { + List bytes = hex.Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(s => + { + return s switch + { + "*" or "**" => -2, + "?" or "??" => -1, + _ => byte.Parse(s, NumberStyles.AllowHexSpecifier), + }; + }).ToList(); + + int? jump = null; // relative addressing + var idx = bytes.IndexOf(-2); + if (idx >= 0) + { + jump = idx; + if (jump > bytes.Count - 4 || + bytes.Skip(jump.Value).Take(4).Any(b => b != -2) || // these 4 digits must be * + bytes.Skip(jump.Value + 4).Any(b => b == -2)) // no more * after these 4 digits + { + throw new FormatException(I18n.Translate("SigScanner/RelAddressingFormatError", + "相对寻址 ({0}) 的内存签名必须含四个连续星号通配符(* * * *),且无额外星号。", hex)); } - - string byteString = hex.Substring(i, 2); - var _byte = byte.Parse(byteString, NumberStyles.AllowHexSpecifier); - bytes.Add(_byte); - i += 2; } - - return bytes; + return (bytes, jump); } /// diff --git a/PostNamazu/PostNamazu.cs b/PostNamazu/PostNamazu.cs index 76f8ce1..57bf9e6 100644 --- a/PostNamazu/PostNamazu.cs +++ b/PostNamazu/PostNamazu.cs @@ -277,7 +277,7 @@ private bool GetOffsets() } try { - _entrancePtr = SigScanner.ScanText("E9 ?? ?? ?? ?? 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 83 B9"); + _entrancePtr = SigScanner.ScanText("E9 * * * * 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 48 83 B9"); return true; } catch (ArgumentException) { @@ -367,37 +367,27 @@ public IntPtr FrameworkPtrPtr { get { - if (_frameworkPtrPtr == IntPtr.Zero) + if (_frameworkPtrPtr != IntPtr.Zero) + { + return _frameworkPtrPtr; + } + try // 7.0 CN { - IntPtr sigStart; int offset; - try - { // 7.0 CN - sigStart = SigScanner.ScanText("49 8B C4 48 8B 0D ?? ?? ?? ?? 48 8D 15 ?? ?? ?? ?? 48 89 05 ?? ?? ?? ??"); - offset = 20; - } - catch - { - try - { // 7.0 global - sigStart = SigScanner.ScanText("49 8B DC 48 89 1D ?? ?? ?? ??"); - offset = 6; - } - catch - { - PluginUI.Log(I18n.Translate("PostNamazu/XivFrameworkNotFound", "未找到 Framework 的内存签名,部分功能无法使用,可能需要更新插件版本。")); - return IntPtr.Zero; - } - } - try - { - _frameworkPtrPtr = sigStart + (offset + 4) + Memory.Read(sigStart + offset); - } - catch - { - throw new Exception(I18n.Translate("PostNamazu/ReadMemoryFail", "初始化时读取内存失败,可能是卫月等插件所致。")); - } + _frameworkPtrPtr = SigScanner.ScanText("49 8B C4 48 8B 0D ? ? ? ? 48 8D 15 ? ? ? ? 48 89 05 * * * *", nameof(_frameworkPtrPtr)); + return _frameworkPtrPtr; + } + catch { } + try // 7.0 global + { + _frameworkPtrPtr = SigScanner.ScanText("49 8B DC 48 89 1D * * * *", nameof(_frameworkPtrPtr)); + return _frameworkPtrPtr; + } + catch (Exception ex) + { + PluginUI.Log(I18n.Translate("PostNamazu/XivFrameworkNotFound", + "未找到 Framework 的内存签名,部分功能无法使用,可能需要更新插件版本。错误:{0}", ex.Message)); + return IntPtr.Zero; } - return _frameworkPtrPtr; } }