Skip to content

Commit 1c32ec8

Browse files
committed
feat: 支持JS类型信息导出C# Binding
1 parent 2caa6ee commit 1c32ec8

5 files changed

Lines changed: 337 additions & 6 deletions

File tree

BetterGenshinImpact/BetterGenshinImpact.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
<PackageReference Include="Serilog.Sinks.RichTextBoxEx.Wpf" Version="1.1.0.1" />
8787
<PackageReference Include="System.Drawing.Common" Version="10.0.0" />
8888
<PackageReference Include="System.IO.Hashing" Version="9.0.4" />
89+
<PackageReference Include="System.Reflection.Metadata" Version="10.0.2" />
8990
<PackageReference Include="TorchSharp" Version="0.105.0" />
9091
<PackageReference Include="Vanara.PInvoke.NtDll" Version="4.1.3" />
9192
<PackageReference Include="Vanara.PInvoke.SHCore" Version="4.1.3" />
@@ -100,6 +101,9 @@
100101
<PackageReference Include="YoloSharp" Version="6.0.3" />
101102
</ItemGroup>
102103

104+
<PropertyGroup Condition=" '$(Configuration)' == 'Debug'">
105+
</PropertyGroup>
106+
103107
<ItemGroup Condition=" '$(Configuration)' == 'Debug'">
104108
</ItemGroup>
105109

BetterGenshinImpact/Core/Script/Dependence/Genshin.cs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using BetterGenshinImpact.GameTask;
1+
using BetterGenshinImpact.GameTask;
22
using BetterGenshinImpact.GameTask.AutoTrackPath;
33
using System.Threading.Tasks;
44
using BetterGenshinImpact.GameTask.Common.Job;
@@ -21,7 +21,9 @@ namespace BetterGenshinImpact.Core.Script.Dependence;
2121

2222
public class Genshin
2323
{
24-
private RECT captureAreaRect = TaskContext.Instance().SystemInfo.CaptureAreaRect;
24+
// ?? default 供 JS 类型兼容(TypeHelper 创建空实例时 SystemInfo 可能为 null)
25+
private RECT captureAreaRect = TaskContext.Instance().SystemInfo?.CaptureAreaRect
26+
?? default;
2527

2628
/// <summary>
2729
/// 游戏宽度
@@ -36,7 +38,9 @@ public class Genshin
3638
/// <summary>
3739
/// 游戏窗口大小相比1080P的缩放比例
3840
/// </summary>
39-
public double ScaleTo1080PRatio { get; } = TaskContext.Instance().SystemInfo.ScaleTo1080PRatio;
41+
// ?? 1 供 JS 类型兼容(TypeHelper 创建空实例时 SystemInfo 可能为 null)
42+
public double ScaleTo1080PRatio { get; } = TaskContext.Instance().SystemInfo?.ScaleTo1080PRatio
43+
?? 1;
4044

4145
/// <summary>
4246
/// 系统屏幕的DPI缩放比例
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
using Microsoft.Extensions.Logging;
2+
using Microsoft.ClearScript;
3+
using System;
4+
using System.Reflection;
5+
using System.Reflection.Metadata;
6+
using System.Reflection.Metadata.Ecma335;
7+
using System.Reflection.PortableExecutable;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
11+
namespace BetterGenshinImpact.Core.Script.Dependence;
12+
13+
public class TypeHelper
14+
{
15+
private HostFunctions hostFunctions = new HostFunctions();
16+
public TypeHelper(IScriptEngine engine)
17+
{
18+
}
19+
private readonly ILogger<TypeHelper> _logger = App.GetLogger<TypeHelper>();
20+
21+
public bool isTypeObj(object obj)
22+
{
23+
if (obj == null) return false;
24+
return hostFunctions.isTypeObj(obj);
25+
}
26+
27+
private Type getRealType(object obj)
28+
{
29+
var asm = typeof(HostItemFlags).Assembly;
30+
31+
// public Type[] Types { get; }
32+
var hostType = asm.GetType("Microsoft.ClearScript.HostType", true);
33+
if (hostType == null) throw new InvalidOperationException("HostType type not found");
34+
var typesProp = hostType.GetProperty("Types", BindingFlags.Public | BindingFlags.Instance);
35+
if (typesProp == null) throw new InvalidOperationException("HostType.Types property not found");
36+
var types = (Type[]?)typesProp.GetValue(obj);
37+
if (types == null || types.Length == 0) throw new InvalidOperationException("HostType.Types is null or empty");
38+
if (types.Length > 1) throw new InvalidOperationException("HostType.Types has multiple types, cannot determine which one to create");
39+
var type = types[0];
40+
return type;
41+
}
42+
43+
/// <summary>
44+
/// 泛型重载:ClearScript 在传入 HostType 参数时会将其解释为泛型类型参数,
45+
/// 即 JS 调用 TypeHelper.GetNullInstance(Mat) 会被解析为 GetNullInstance&lt;Mat&gt;()。
46+
/// </summary>
47+
public object GetNullInstance<T>()
48+
{
49+
return GetNullInstanceFromType(typeof(T));
50+
}
51+
52+
public object GetNullInstance(object obj)
53+
{
54+
// obj是HostType,通过反射获取真实Type,然后创建一个nullwrapper
55+
return GetNullInstanceFromType(getRealType(obj));
56+
}
57+
58+
private object GetNullInstanceFromType(Type type)
59+
{
60+
// 通过反射创建一个 null wrapper
61+
var asm = typeof(HostItemFlags).Assembly;
62+
// _logger.LogDebug($"[Script] 创建类型 {type.FullName} 的 null HostObject");
63+
64+
// private static HostObject GetNullWrapper(Type type)
65+
var hostObject = asm.GetType("Microsoft.ClearScript.HostObject", true);
66+
if (hostObject == null) throw new InvalidOperationException("HostObject type not found");
67+
var getNullWrapperMethod = hostObject.GetMethod("GetNullWrapper", BindingFlags.NonPublic | BindingFlags.Static);
68+
if (getNullWrapperMethod == null) throw new InvalidOperationException("HostObject.GetNullWrapper method not found");
69+
var instance = getNullWrapperMethod.Invoke(null, [type]);
70+
if (instance == null) throw new InvalidOperationException("HostObject.GetNullWrapper returned null");
71+
72+
return instance;
73+
}
74+
75+
public Type GetType(object obj)
76+
{
77+
if (obj == null) throw new ArgumentNullException(nameof(obj));
78+
_logger.LogDebug($"[Script] 获取对象 {obj} 的类型 {obj?.GetType().FullName}");
79+
return obj.GetType();
80+
}
81+
82+
private Dictionary<string, object> dumpMethodInfo(MethodInfo methodInfo, MetadataReader? pdbReader = null)
83+
{
84+
var parameters = methodInfo.GetParameters();
85+
var paramStrs = new List<string>();
86+
foreach (var param in parameters)
87+
{
88+
var paramStr = $"{param.ParameterType.Name} {param.Name}";
89+
if (param.IsOptional)
90+
{
91+
paramStr += $" = {param.DefaultValue ?? "null"}";
92+
}
93+
paramStrs.Add(paramStr);
94+
}
95+
var definition = $"{methodInfo.Name}({string.Join(", ", paramStrs)})";
96+
_logger.LogDebug($"[Script] 方法定义: {definition}");
97+
var definitionDict = new Dictionary<string, object>
98+
{
99+
{ "name", methodInfo.Name },
100+
{ "definition", definition },
101+
{ "parameterTypes", parameters.AsEnumerable().Select(p => p.ParameterType.FullName) },
102+
};
103+
104+
// 尝试从 PDB 获取源码位置
105+
if (pdbReader != null)
106+
{
107+
try
108+
{
109+
var methodHandle = MetadataTokens.MethodDefinitionHandle(methodInfo.MetadataToken);
110+
var methodDebugInfo = pdbReader.GetMethodDebugInformation(methodHandle);
111+
if (!methodDebugInfo.SequencePointsBlob.IsNil)
112+
{
113+
foreach (var sp in methodDebugInfo.GetSequencePoints())
114+
{
115+
if (!sp.Document.IsNil && !sp.IsHidden)
116+
{
117+
var document = pdbReader.GetDocument(sp.Document);
118+
var filePath = pdbReader.GetString(document.Name);
119+
definitionDict["sourceFile"] = filePath;
120+
definitionDict["sourceLine"] = sp.StartLine;
121+
break;
122+
}
123+
}
124+
}
125+
}
126+
catch (Exception ex)
127+
{
128+
_logger.LogDebug($"[Script] 无法获取方法 {methodInfo.Name} 的源码位置: {ex.Message}");
129+
}
130+
}
131+
132+
return definitionDict;
133+
}
134+
135+
public string GetMethodDefinition(object method)
136+
{
137+
_logger.LogDebug($"[Script] 获取方法 {method} 的定义");
138+
var asm = typeof(HostItemFlags).Assembly;
139+
var hostMethod = asm.GetType("Microsoft.ClearScript.HostMethod", true);
140+
if (hostMethod == null) throw new InvalidOperationException("HostMethod type not found");
141+
// private readonly HostItem target;
142+
var targetField = hostMethod.GetField("target", BindingFlags.NonPublic | BindingFlags.Instance);
143+
if (targetField == null) throw new InvalidOperationException("HostMethod.target field not found");
144+
var target = targetField.GetValue(method);
145+
if (target == null) throw new InvalidOperationException("HostMethod.target is null");
146+
var targetType = target.GetType();
147+
148+
// assert target type is HostItem
149+
var hostItem = asm.GetType("Microsoft.ClearScript.HostItem", true);
150+
if (hostItem == null) throw new InvalidOperationException("HostItem type not found");
151+
if (!hostItem.IsAssignableFrom(targetType))
152+
{
153+
throw new InvalidOperationException($"HostMethod.target is not a HostItem, but {targetType.FullName}");
154+
}
155+
156+
// private readonly string name;
157+
var nameField = hostMethod.GetField("name", BindingFlags.NonPublic | BindingFlags.Instance);
158+
if (nameField == null) throw new InvalidOperationException("HostItem.name field not found");
159+
var name = (string?)nameField.GetValue(method);
160+
if (name == null) throw new InvalidOperationException("HostItem.name is null");
161+
162+
_logger.LogDebug($"[Script] 获取方法定义 {target} {name}");
163+
164+
// public HostTarget Target { get; }
165+
var targetProp = hostItem.GetProperty("Target", BindingFlags.Public | BindingFlags.Instance);
166+
if (targetProp == null) throw new InvalidOperationException("HostItem.Target property not found");
167+
var targetValue = targetProp.GetValue(target); // should be hostType
168+
_logger.LogDebug($"[Script] 方法目标类型 {targetValue} {targetValue.GetType().FullName}");
169+
170+
// if type is HostType, get the real Type
171+
if (isTypeObj(targetValue))
172+
{
173+
return GetMethodDefinitionForType(targetValue, name);
174+
}
175+
else
176+
{
177+
throw new InvalidOperationException($"HostItem.Target is not a HostType, but {targetValue?.GetType().FullName}");
178+
}
179+
}
180+
181+
private string dumpMethodDefinitionForType(Type type, string methodName)
182+
{
183+
_logger.LogDebug($"[Script] 获取类型 {type.FullName} 的方法 {methodName} 定义");
184+
var matchedMethods = new List<MethodInfo>();
185+
foreach (var methodInfo in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static))
186+
{
187+
if (methodInfo.Name.Equals(methodName, StringComparison.OrdinalIgnoreCase))
188+
{
189+
matchedMethods.Add(methodInfo);
190+
}
191+
}
192+
if (matchedMethods.Count == 0)
193+
{
194+
throw new InvalidOperationException($"类型 {type.FullName} 没有找到方法 {methodName}");
195+
}
196+
197+
// 尝试打开 Portable PDB 获取源码位置
198+
System.IO.FileStream? fs = null;
199+
PEReader? peReader = null;
200+
MetadataReaderProvider? pdbProvider = null;
201+
MetadataReader? pdbReader = null;
202+
203+
try
204+
{
205+
var location = type.Assembly.Location;
206+
if (!string.IsNullOrEmpty(location) && System.IO.File.Exists(location))
207+
{
208+
fs = System.IO.File.OpenRead(location);
209+
peReader = new PEReader(fs);
210+
211+
// 优先尝试嵌入式 Portable PDB
212+
foreach (var entry in peReader.ReadDebugDirectory())
213+
{
214+
if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)
215+
{
216+
pdbProvider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry);
217+
break;
218+
}
219+
}
220+
221+
// 其次尝试关联的 .pdb 文件
222+
if (pdbProvider == null)
223+
{
224+
peReader.TryOpenAssociatedPortablePdb(location, System.IO.File.OpenRead, out pdbProvider, out _);
225+
}
226+
227+
pdbReader = pdbProvider?.GetMetadataReader();
228+
}
229+
}
230+
catch (Exception ex)
231+
{
232+
_logger.LogDebug($"[Script] 无法打开 PDB 以读取源码位置: {ex.Message}");
233+
}
234+
235+
try
236+
{
237+
// convert method info to string function definition
238+
var definitions = new List<Dictionary<string, object>>();
239+
foreach (var methodInfo in matchedMethods)
240+
{
241+
definitions.Add(dumpMethodInfo(methodInfo, pdbReader));
242+
}
243+
// return json string
244+
return System.Text.Json.JsonSerializer.Serialize(definitions);
245+
}
246+
finally
247+
{
248+
pdbProvider?.Dispose();
249+
peReader?.Dispose();
250+
fs?.Dispose();
251+
}
252+
}
253+
254+
public string GetMethodDefinitionForType(object typeObj, string methodName)
255+
{
256+
if (!isTypeObj(typeObj))
257+
{
258+
throw new ArgumentException("参数不是类型对象", nameof(typeObj));
259+
}
260+
return dumpMethodDefinitionForType(getRealType(typeObj), methodName);
261+
}
262+
public string GetMethodDefinitionForType<T>(string methodName)
263+
{
264+
return dumpMethodDefinitionForType(typeof(T), methodName);
265+
}
266+
}

BetterGenshinImpact/Core/Script/EngineExtend.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using BetterGenshinImpact.Core.Script.Dependence;
44
using BetterGenshinImpact.Core.Script.Dependence.Model;
@@ -73,7 +73,16 @@ public static void InitHost(IScriptEngine engine, string workDir, string[]? sear
7373
engine.AddHostType("AutoDomainParam", typeof(AutoDomainParam));
7474
engine.AddHostType("AutoFightParam", typeof(AutoFightParam));
7575
//鼠标回调
76-
engine.AddHostType("KeyMouseHook", typeof(KeyMouseHook));
76+
engine.AddHostType("KeyMouseHook", typeof(KeyMouseHook));
77+
78+
#if DEBUG
79+
// 供 JS 类型兼容:添加 ClearScript 辅助类,用于 JsTypeDef checker 反射导出类型信息
80+
engine.AllowReflection = true; // 允许反射
81+
engine.AddHostObject("HostFunctions", new HostFunctions());
82+
// 用于辅助JsTypeDef创建空对象导出JS类型信息
83+
engine.AddHostObject("TypeHelper", new TypeHelper(engine));
84+
#endif
85+
7786
// 添加C#的类型
7887
engine.AddHostType(typeof(Task));
7988

0 commit comments

Comments
 (0)