feat: add send connector preflight probes

This commit is contained in:
zhaoyilun
2026-07-10 13:37:08 +08:00
parent b2ce7b0e36
commit 1f99681348
11 changed files with 1307 additions and 25 deletions

View File

@@ -47,17 +47,23 @@ namespace ISphereWinHelper
Dictionary<string, object> filesystem = LiveProbeInventory.CollectFilesystem(runtime);
WriteJson(Path.Combine(outputDir, "filesystem_inventory.json"), filesystem);
Dictionary<string, object> sendEntrypoints = SendConnectorPreflight.ProbeSendEntrypoints(new Dictionary<string, object>());
WriteJson(Path.Combine(outputDir, "send_entrypoints_preflight.json"), sendEntrypoints);
string uiaDir = Path.Combine(outputDir, "uia");
Directory.CreateDirectory(uiaDir);
List<Dictionary<string, object>> uiaSummary = DumpTargetWindowUia(runtime, windows, uiaDir);
WriteJson(Path.Combine(outputDir, "uia_summary.json"), uiaSummary);
List<Dictionary<string, object>> sendUiaPreflight = ProbeTargetWindowSendControls(runtime, windows);
WriteJson(Path.Combine(outputDir, "send_uia_controls_preflight.json"), sendUiaPreflight);
Dictionary<string, object> feasibility = LiveProbeInventory.SummarizeFeasibility(runtime, filesystem, network);
WriteJson(Path.Combine(outputDir, "feasibility_summary.json"), feasibility);
WriteText(Path.Combine(outputDir, "NEXT_STEP.txt"),
"把整个目录压缩后发回即可。\r\n" +
"重点文件probe_client_runtime.json、process_details.json、network_inventory.json、filesystem_inventory.json、services_registry_shortcuts.json、scan_windows.json、uia_summary.json、uia/*.json、feasibility_summary.json\r\n");
"重点文件probe_client_runtime.json、send_entrypoints_preflight.json、send_uia_controls_preflight.json、process_details.json、network_inventory.json、filesystem_inventory.json、services_registry_shortcuts.json、scan_windows.json、uia_summary.json、uia/*.json、feasibility_summary.json\r\n");
Console.WriteLine("OK");
Console.WriteLine("已生成采集目录:");
@@ -77,6 +83,50 @@ namespace ISphereWinHelper
}
}
private static List<Dictionary<string, object>> ProbeTargetWindowSendControls(Dictionary<string, object> runtime, Dictionary<string, object> windows)
{
var targetPids = TargetPids(runtime);
var summary = new List<Dictionary<string, object>>();
object rawWindows;
if (!windows.TryGetValue("windows", out rawWindows))
{
return summary;
}
var windowList = rawWindows as List<Dictionary<string, object>>;
if (windowList == null)
{
return summary;
}
foreach (Dictionary<string, object> window in windowList)
{
int pid = GetInt(window, "pid");
if (!targetPids.Contains(pid))
{
continue;
}
string hwnd = GetString(window, "hwnd");
string title = GetString(window, "title");
Dictionary<string, object> probe = SendConnectorPreflight.ProbeSendUiaControls("live-probe-send-uia", "probe_send_uia_controls", new Dictionary<string, object>
{
{ "hwnd", hwnd },
{ "max_depth", 8 },
{ "max_children", 180 }
});
summary.Add(new Dictionary<string, object>
{
{ "pid", pid },
{ "hwnd", hwnd },
{ "title_present", !string.IsNullOrEmpty(title) },
{ "ok", GetBool(probe, "ok") },
{ "probe", probe }
});
}
return summary;
}
private static List<Dictionary<string, object>> DumpTargetWindowUia(Dictionary<string, object> runtime, Dictionary<string, object> windows, string uiaDir)
{
var targetPids = TargetPids(runtime);
@@ -154,7 +204,7 @@ namespace ISphereWinHelper
return new Dictionary<string, object>
{
{ "recorder_name", "ISphereLiveProbeRecorder" },
{ "recorder_version", "0.2.0" },
{ "recorder_version", "0.3.0" },
{ "helper_protocol", HelperProtocol.Protocol },
{ "run_id", runId },
{ "timestamp_local", DateTime.Now.ToString("o") },
@@ -183,7 +233,7 @@ namespace ISphereWinHelper
"5. 双击 ISphereLiveProbeRecorder.exe。\r\n" +
"6. 程序会在 probe-output 下生成 isphere-live-probe-时间戳 目录。\r\n" +
"7. 把整个目录压缩后发回。\r\n\r\n" +
"采集内容:运行进程、模块、命令行摘要、窗口、目标窗口 UIA 控件树、安装目录关键文件、配置/日志/数据库候选、注册表卸载项、快捷方式、服务、本地 TCP 连接、命名管道。\r\n" +
"采集内容:运行进程、模块、命令行摘要、窗口、目标窗口 UIA 控件树、发送入口元数据预检、发送控件/离线状态预检、安装目录关键文件、配置/日志/数据库候选、注册表卸载项、快捷方式、服务、本地 TCP 连接、命名管道。\r\n" +
"不会执行发送消息、发送文件、上传文件、点击、输入、注入、hook、修改客户端数据。\r\n";
}

View File

@@ -47,6 +47,12 @@ namespace ISphereWinHelper
case "probe_client_runtime":
response = HelperProtocol.Success(requestId, op, RuntimeProbe.ProbeClientRuntime(opArgs));
break;
case "probe_send_entrypoints":
response = HelperProtocol.Success(requestId, op, SendConnectorPreflight.ProbeSendEntrypoints(opArgs));
break;
case "probe_send_uia_controls":
response = SendConnectorPreflight.ProbeSendUiaControls(requestId, op, opArgs);
break;
default:
response = HelperProtocol.Failure(requestId, op, "UNSUPPORTED_OP", "unsupported op: " + op);
break;
@@ -82,7 +88,7 @@ namespace ISphereWinHelper
return new Dictionary<string, object>
{
{ "helper_name", "ISphereWinHelper" },
{ "helper_version", "0.3.0" },
{ "helper_version", "0.4.0" },
{ "protocol", HelperProtocol.Protocol },
{ "runtime", ".NET Framework " + Environment.Version }
};

View File

@@ -0,0 +1,848 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Automation;
namespace ISphereWinHelper
{
internal static class SendConnectorPreflight
{
private sealed class EntrypointSpec
{
public readonly string Id;
public readonly string Capability;
public readonly string AssemblyName;
public readonly string TypeName;
public readonly string MethodName;
public readonly bool Required;
public EntrypointSpec(string id, string capability, string assemblyName, string typeName, string methodName, bool required)
{
Id = id;
Capability = capability;
AssemblyName = assemblyName;
TypeName = typeName;
MethodName = methodName;
Required = required;
}
}
private sealed class UiaMatch
{
public string Path = "";
public string ControlType = "";
public string AutomationId = "";
public string ClassName = "";
public string SafeName = "";
public bool IsEnabled;
public bool IsOffscreen;
public Dictionary<string, object> Bounds = new Dictionary<string, object>();
public Dictionary<string, object> ToDictionary()
{
return new Dictionary<string, object>
{
{ "path", Path },
{ "control_type", ControlType },
{ "automation_id", AutomationId },
{ "class_name", ClassName },
{ "safe_name", SafeName },
{ "is_enabled", IsEnabled },
{ "is_offscreen", IsOffscreen },
{ "bounds", Bounds }
};
}
}
private static readonly EntrypointSpec[] DefaultEntrypoints = new[]
{
new EntrypointSpec(
"text_high_level_by_jid",
"send_message",
"IMPlatformClient.exe",
"IMPP.Client.Core.Plugins.Manager.AppContextManager",
"SendTxtMessageByJid",
true),
new EntrypointSpec(
"text_message_scheduler",
"send_message",
"IMPlatformClient.exe",
"IMPP.Client.Core.MessageEngine.MessageScheduling",
"SendChatMessage",
true),
new EntrypointSpec(
"text_wire_chat_send",
"send_message",
"smack.dll",
"com.vision.smack.Chat",
"SendMessage",
true),
new EntrypointSpec(
"file_service_upload",
"send_file",
"IMPP.Service.dll",
"IMPP.Service.BLL.FileTransfer",
"FileUpload",
true),
new EntrypointSpec(
"file_service_upload_async",
"send_file",
"IMPP.Service.dll",
"IMPP.Service.BLL.FileTransfer",
"AsynFileUpload",
false),
new EntrypointSpec(
"file_offline_send",
"send_file",
"IMPlatformClient.exe",
"IMPP.Client.OffLineFileSend",
"sendFile",
true),
new EntrypointSpec(
"file_wire_message",
"send_file",
"smack.dll",
"com.vision.smack.Chat",
"sendFileMessage",
false)
};
public static Dictionary<string, object> ProbeSendEntrypoints(Dictionary<string, object> args)
{
var installDirs = CandidateInstallDirs(args);
var assemblyPaths = ResolveAssemblyPaths(installDirs);
var assemblySummaries = SummarizeAssemblies(assemblyPaths);
var entrypoints = new List<Dictionary<string, object>>();
int requiredCount = 0;
int requiredAvailable = 0;
int availableCount = 0;
ResolveEventHandler resolveHandler = delegate(object sender, ResolveEventArgs eventArgs)
{
return ResolveReflectionOnlyAssembly(eventArgs, installDirs);
};
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveHandler;
try
{
foreach (EntrypointSpec spec in DefaultEntrypoints)
{
if (spec.Required)
{
requiredCount++;
}
Dictionary<string, object> result = ProbeEntrypoint(spec, assemblyPaths);
bool available = GetBool(result, "available");
if (available)
{
availableCount++;
if (spec.Required)
{
requiredAvailable++;
}
}
entrypoints.Add(result);
}
}
finally
{
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveHandler;
}
return new Dictionary<string, object>
{
{ "probe_mode", "read_only_send_entrypoint_metadata" },
{ "safety", SafetyData() },
{ "install_dirs_checked", installDirs },
{ "assemblies", assemblySummaries },
{ "entrypoints", entrypoints },
{ "summary", new Dictionary<string, object>
{
{ "entrypoint_count", entrypoints.Count },
{ "available_count", availableCount },
{ "required_count", requiredCount },
{ "required_available_count", requiredAvailable },
{ "all_required_available", requiredCount > 0 && requiredAvailable == requiredCount },
{ "b_route_entrypoint_preflight", requiredCount > 0 && requiredAvailable == requiredCount ? "pass" : "incomplete" }
}
}
};
}
public static Dictionary<string, object> ProbeSendUiaControls(string requestId, string op, Dictionary<string, object> args)
{
string hwndText = HelperProtocol.GetString(args, "hwnd", "");
IntPtr hwnd;
if (!WindowScanner.TryParseHwnd(hwndText, out hwnd))
{
return HelperProtocol.Failure(requestId, op, "WINDOW_NOT_FOUND", "invalid or empty hwnd");
}
int maxDepth = HelperProtocol.GetInt(args, "max_depth", 8);
if (maxDepth < 0) maxDepth = 0;
if (maxDepth > 20) maxDepth = 20;
int maxChildren = HelperProtocol.GetInt(args, "max_children", 200);
if (maxChildren < 1) maxChildren = 1;
if (maxChildren > 1000) maxChildren = 1000;
try
{
AutomationElement root = AutomationElement.FromHandle(hwnd);
if (root == null)
{
return HelperProtocol.Failure(requestId, op, "WINDOW_NOT_FOUND", "no UI Automation element for hwnd");
}
Dictionary<string, object> data = ClassifyUiaTree(hwnd, root, maxDepth, maxChildren);
return HelperProtocol.Success(requestId, op, data);
}
catch (Exception ex)
{
return HelperProtocol.Failure(requestId, op, "UIA_SEND_PREFLIGHT_FAILED", ex.Message);
}
}
private static Dictionary<string, object> ClassifyUiaTree(IntPtr hwnd, AutomationElement root, int maxDepth, int maxChildren)
{
UiaMatch searchEdit = null;
UiaMatch receiveDocument = null;
UiaMatch sendEditor = null;
UiaMatch sendButton = null;
UiaMatch fileMenu = null;
UiaMatch offlineBlocker = null;
int visited = 0;
bool truncated = false;
Traverse(root, "root", 0, maxDepth, maxChildren, ref visited, ref truncated, delegate(AutomationElement element, string path)
{
string automationId = SafeString(delegate { return element.Current.AutomationId; });
string name = SafeString(delegate { return element.Current.Name; });
string value = SafeValue(element);
string controlType = SafeControlType(element);
string combined = (automationId + " " + name + " " + value).ToLowerInvariant();
if (searchEdit == null &&
(StringEquals(automationId, "skinAlphaTxt") ||
name.IndexOf("按帐号、姓名、拼音、标签查询联系人", StringComparison.OrdinalIgnoreCase) >= 0))
{
searchEdit = MatchFrom(element, path, "按帐号、姓名、拼音、标签查询联系人");
}
if (receiveDocument == null && StringEquals(automationId, "rtbRecvMessage"))
{
receiveDocument = MatchFrom(element, path, "");
}
if (sendEditor == null && StringEquals(automationId, "rtbSendMessage"))
{
sendEditor = MatchFrom(element, path, "");
}
if (sendButton == null &&
(StringEquals(automationId, "btnSend") || name.IndexOf("发送(S)", StringComparison.OrdinalIgnoreCase) >= 0))
{
sendButton = MatchFrom(element, path, "发送(S)");
}
if (fileMenu == null &&
(StringEquals(automationId, "btnFile") ||
StringEquals(automationId, "btnSendFile") ||
name.IndexOf("发送文件", StringComparison.OrdinalIgnoreCase) >= 0))
{
fileMenu = MatchFrom(element, path, "发送文件");
}
if (offlineBlocker == null &&
(StringEquals(automationId, "offlineSendBlocker") ||
combined.IndexOf("离线状态", StringComparison.OrdinalIgnoreCase) >= 0 ||
combined.IndexOf("无法发送消息", StringComparison.OrdinalIgnoreCase) >= 0))
{
offlineBlocker = MatchFrom(element, path, "您已处于离线状态,无法发送消息,请上线后再次尝试!");
}
});
bool hasSearchEdit = searchEdit != null;
bool hasSendEditor = sendEditor != null;
bool hasSendButton = sendButton != null;
bool hasFileMenu = fileMenu != null;
bool hasOfflineBlocker = offlineBlocker != null;
bool sendButtonEnabled = sendButton != null && sendButton.IsEnabled && !sendButton.IsOffscreen;
string routeHint = hasOfflineBlocker ? "A_ROUTE_OFFLINE_BLOCKED" :
(hasSendEditor && hasSendButton ? "A_ROUTE_CONTROLS_PRESENT" : "A_ROUTE_INCOMPLETE");
return new Dictionary<string, object>
{
{ "probe_mode", "read_only_uia_send_control_classifier" },
{ "safety", SafetyData() },
{ "hwnd", WindowScanner.FormatHwnd(hwnd) },
{ "root", new Dictionary<string, object>
{
{ "automation_id", SafeString(delegate { return root.Current.AutomationId; }) },
{ "control_type", SafeControlType(root) },
{ "class_name", SafeString(delegate { return root.Current.ClassName; }) },
{ "framework_id", SafeString(delegate { return root.Current.FrameworkId; }) },
{ "name_present", !string.IsNullOrEmpty(SafeString(delegate { return root.Current.Name; })) }
}
},
{ "flags", new Dictionary<string, object>
{
{ "has_search_edit", hasSearchEdit },
{ "has_receive_document", receiveDocument != null },
{ "has_send_editor", hasSendEditor },
{ "has_send_button", hasSendButton },
{ "has_file_menu", hasFileMenu },
{ "offline_blocker_visible", hasOfflineBlocker },
{ "send_button_enabled", sendButtonEnabled },
{ "route_hint", routeHint },
{ "visited_nodes", visited },
{ "truncated", truncated }
}
},
{ "controls", new Dictionary<string, object>
{
{ "search_edit", MatchOrNull(searchEdit) },
{ "receive_document", MatchOrNull(receiveDocument) },
{ "send_editor", MatchOrNull(sendEditor) },
{ "send_button", MatchOrNull(sendButton) },
{ "file_menu", MatchOrNull(fileMenu) },
{ "offline_blocker", MatchOrNull(offlineBlocker) }
}
}
};
}
private delegate void ElementVisitor(AutomationElement element, string path);
private static void Traverse(AutomationElement element, string path, int depth, int maxDepth, int maxChildren, ref int visited, ref bool truncated, ElementVisitor visitor)
{
if (element == null)
{
return;
}
visited++;
visitor(element, path);
if (depth >= maxDepth)
{
return;
}
AutomationElementCollection children;
try
{
children = element.FindAll(TreeScope.Children, Condition.TrueCondition);
}
catch
{
return;
}
int limit = Math.Min(children.Count, maxChildren);
if (children.Count > limit)
{
truncated = true;
}
for (int i = 0; i < limit; i++)
{
Traverse(children[i], path + "/" + i.ToString(CultureInfo.InvariantCulture), depth + 1, maxDepth, maxChildren, ref visited, ref truncated, visitor);
}
}
private static UiaMatch MatchFrom(AutomationElement element, string path, string safeName)
{
return new UiaMatch
{
Path = path,
ControlType = SafeControlType(element),
AutomationId = SafeString(delegate { return element.Current.AutomationId; }),
ClassName = SafeString(delegate { return element.Current.ClassName; }),
SafeName = safeName,
IsEnabled = SafeBool(delegate { return element.Current.IsEnabled; }),
IsOffscreen = SafeBool(delegate { return element.Current.IsOffscreen; }),
Bounds = SafeBounds(element)
};
}
private static object MatchOrNull(UiaMatch match)
{
if (match == null)
{
return null;
}
return match.ToDictionary();
}
private static Dictionary<string, object> ProbeEntrypoint(EntrypointSpec spec, Dictionary<string, string> assemblyPaths)
{
string assemblyPath = "";
assemblyPaths.TryGetValue(spec.AssemblyName, out assemblyPath);
bool assemblyFound = !string.IsNullOrEmpty(assemblyPath) && File.Exists(assemblyPath);
bool reflectionTypeFound = false;
bool reflectionMethodFound = false;
string reflectionError = "";
var signatures = new List<string>();
if (assemblyFound)
{
try
{
Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
Type targetType = FindType(assembly, spec.TypeName);
reflectionTypeFound = targetType != null;
if (targetType != null)
{
MethodInfo[] methods = targetType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
foreach (MethodInfo method in methods)
{
if (!StringEquals(method.Name, spec.MethodName))
{
continue;
}
reflectionMethodFound = true;
if (signatures.Count < 8)
{
signatures.Add(MethodSignature(method));
}
}
}
}
catch (Exception ex)
{
reflectionError = ex.GetType().Name + ": " + ex.Message;
}
}
bool stringTypeHint = false;
bool stringFullTypeHint = false;
bool stringShortTypeHint = false;
bool stringMethodHint = false;
string stringError = "";
if (assemblyFound)
{
try
{
byte[] bytes = File.ReadAllBytes(assemblyPath);
string ascii = Encoding.ASCII.GetString(bytes);
string unicode = Encoding.Unicode.GetString(bytes);
string shortTypeName = ShortTypeName(spec.TypeName);
stringFullTypeHint = ascii.IndexOf(spec.TypeName, StringComparison.OrdinalIgnoreCase) >= 0 ||
unicode.IndexOf(spec.TypeName, StringComparison.OrdinalIgnoreCase) >= 0 ||
ascii.IndexOf(spec.TypeName.Replace(".", "/"), StringComparison.OrdinalIgnoreCase) >= 0;
stringShortTypeHint = !string.IsNullOrEmpty(shortTypeName) &&
(ascii.IndexOf(shortTypeName, StringComparison.OrdinalIgnoreCase) >= 0 ||
unicode.IndexOf(shortTypeName, StringComparison.OrdinalIgnoreCase) >= 0);
stringTypeHint = stringFullTypeHint || stringShortTypeHint;
stringMethodHint = ascii.IndexOf(spec.MethodName, StringComparison.OrdinalIgnoreCase) >= 0 ||
unicode.IndexOf(spec.MethodName, StringComparison.OrdinalIgnoreCase) >= 0;
}
catch (Exception ex)
{
stringError = ex.GetType().Name + ": " + ex.Message;
}
}
bool available = assemblyFound && ((reflectionTypeFound && reflectionMethodFound) || (stringTypeHint && stringMethodHint));
return new Dictionary<string, object>
{
{ "id", spec.Id },
{ "capability", spec.Capability },
{ "assembly", spec.AssemblyName },
{ "assembly_found", assemblyFound },
{ "assembly_path_present", assemblyFound },
{ "type_name", spec.TypeName },
{ "method_name", spec.MethodName },
{ "required", spec.Required },
{ "reflection_type_found", reflectionTypeFound },
{ "reflection_method_found", reflectionMethodFound },
{ "reflection_error", reflectionError },
{ "string_type_hint", stringTypeHint },
{ "string_full_type_hint", stringFullTypeHint },
{ "string_short_type_hint", stringShortTypeHint },
{ "string_method_hint", stringMethodHint },
{ "string_scan_error", stringError },
{ "available", available },
{ "signatures", signatures }
};
}
private static Type FindType(Assembly assembly, string typeName)
{
try
{
Type exact = assembly.GetType(typeName, false, true);
if (exact != null)
{
return exact;
}
}
catch
{
}
try
{
foreach (Type type in assembly.GetTypes())
{
if (StringEquals(type.FullName, typeName) || StringEquals(type.Name, typeName))
{
return type;
}
}
}
catch (ReflectionTypeLoadException ex)
{
foreach (Type type in ex.Types)
{
if (type != null && (StringEquals(type.FullName, typeName) || StringEquals(type.Name, typeName)))
{
return type;
}
}
}
catch
{
}
return null;
}
private static string ShortTypeName(string typeName)
{
if (string.IsNullOrEmpty(typeName))
{
return "";
}
int dot = typeName.LastIndexOf('.');
int plus = typeName.LastIndexOf('+');
int slash = typeName.LastIndexOf('/');
int cut = Math.Max(dot, Math.Max(plus, slash));
return cut >= 0 && cut + 1 < typeName.Length ? typeName.Substring(cut + 1) : typeName;
}
private static string MethodSignature(MethodInfo method)
{
var sb = new StringBuilder();
sb.Append(method.IsStatic ? "static " : "instance ");
sb.Append(method.Name);
sb.Append("(");
ParameterInfo[] parameters = method.GetParameters();
for (int i = 0; i < parameters.Length; i++)
{
if (i > 0) sb.Append(", ");
sb.Append(SafeTypeName(parameters[i].ParameterType));
}
sb.Append(") -> ");
sb.Append(SafeTypeName(method.ReturnType));
return sb.ToString();
}
private static string SafeTypeName(Type type)
{
try
{
return type.FullName ?? type.Name;
}
catch
{
return "<unknown>";
}
}
private static Assembly ResolveReflectionOnlyAssembly(ResolveEventArgs args, List<string> installDirs)
{
try
{
string simpleName = new AssemblyName(args.Name).Name + ".dll";
foreach (string dir in installDirs)
{
string candidate = Path.Combine(dir, simpleName);
if (File.Exists(candidate))
{
return Assembly.ReflectionOnlyLoadFrom(candidate);
}
}
}
catch
{
}
return null;
}
private static List<string> CandidateInstallDirs(Dictionary<string, object> args)
{
var dirs = new List<string>();
string explicitDir = HelperProtocol.GetString(args, "install_dir", "");
AddDir(dirs, explicitDir);
foreach (string item in GetStringList(args, "install_dirs"))
{
AddDir(dirs, item);
}
foreach (Process process in Process.GetProcessesByName("IMPlatformClient"))
{
using (process)
{
try
{
if (process.MainModule != null)
{
AddDir(dirs, Path.GetDirectoryName(process.MainModule.FileName));
}
}
catch
{
}
}
}
AddDir(dirs, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Impp"));
AddDir(dirs, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Impp"));
return dirs;
}
private static void AddDir(List<string> dirs, string dir)
{
if (string.IsNullOrWhiteSpace(dir))
{
return;
}
try
{
string full = Path.GetFullPath(dir);
foreach (string existing in dirs)
{
if (StringEquals(existing, full))
{
return;
}
}
dirs.Add(full);
}
catch
{
}
}
private static Dictionary<string, string> ResolveAssemblyPaths(List<string> dirs)
{
var paths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (EntrypointSpec spec in DefaultEntrypoints)
{
if (!paths.ContainsKey(spec.AssemblyName))
{
paths.Add(spec.AssemblyName, "");
}
}
foreach (string dir in dirs)
{
foreach (EntrypointSpec spec in DefaultEntrypoints)
{
if (!string.IsNullOrEmpty(paths[spec.AssemblyName]))
{
continue;
}
string candidate = Path.Combine(dir, spec.AssemblyName);
if (File.Exists(candidate))
{
paths[spec.AssemblyName] = candidate;
}
}
}
return paths;
}
private static List<Dictionary<string, object>> SummarizeAssemblies(Dictionary<string, string> paths)
{
var list = new List<Dictionary<string, object>>();
foreach (KeyValuePair<string, string> item in paths)
{
bool exists = !string.IsNullOrEmpty(item.Value) && File.Exists(item.Value);
string version = "";
long length = 0;
string sha256 = "";
if (exists)
{
try
{
var info = new FileInfo(item.Value);
length = info.Length;
FileVersionInfo vi = FileVersionInfo.GetVersionInfo(item.Value);
version = vi == null ? "" : (vi.FileVersion ?? "");
sha256 = Sha256(item.Value);
}
catch
{
}
}
list.Add(new Dictionary<string, object>
{
{ "name", item.Key },
{ "found", exists },
{ "path_present", exists },
{ "length", length },
{ "file_version", version },
{ "sha256", sha256 }
});
}
return list;
}
private static Dictionary<string, object> SafetyData()
{
return new Dictionary<string, object>
{
{ "sent_message", false },
{ "sent_file", false },
{ "uploaded_file", false },
{ "clicked_ui", false },
{ "typed_text", false },
{ "captured_network", false },
{ "attached_hook", false },
{ "modified_client_data", false }
};
}
private static List<string> GetStringList(Dictionary<string, object> source, string key)
{
var values = new List<string>();
object raw;
if (source != null && source.TryGetValue(key, out raw) && raw != null)
{
object[] rawArray = raw as object[];
if (rawArray != null)
{
foreach (object item in rawArray)
{
string value = Convert.ToString(item);
if (!string.IsNullOrWhiteSpace(value))
{
values.Add(value.Trim());
}
}
return values;
}
string single = Convert.ToString(raw);
if (!string.IsNullOrWhiteSpace(single))
{
foreach (string part in single.Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries))
{
string value = part.Trim();
if (!string.IsNullOrWhiteSpace(value))
{
values.Add(value);
}
}
}
}
return values;
}
private delegate string StringGetter();
private delegate bool BoolGetter();
private static string SafeString(StringGetter getter)
{
try { return getter() ?? ""; }
catch { return ""; }
}
private static bool SafeBool(BoolGetter getter)
{
try { return getter(); }
catch { return false; }
}
private static string SafeControlType(AutomationElement element)
{
try
{
string name = element.Current.ControlType.ProgrammaticName ?? "";
return name.StartsWith("ControlType.", StringComparison.Ordinal) ? name.Substring("ControlType.".Length) : name;
}
catch
{
return "";
}
}
private static Dictionary<string, object> SafeBounds(AutomationElement element)
{
try
{
System.Windows.Rect rect = element.Current.BoundingRectangle;
return new Dictionary<string, object>
{
{ "x", rect.X },
{ "y", rect.Y },
{ "width", rect.Width },
{ "height", rect.Height }
};
}
catch
{
return new Dictionary<string, object>
{
{ "x", 0 },
{ "y", 0 },
{ "width", 0 },
{ "height", 0 }
};
}
}
private static string SafeValue(AutomationElement element)
{
try
{
object pattern;
if (element.TryGetCurrentPattern(ValuePattern.Pattern, out pattern))
{
return ((ValuePattern)pattern).Current.Value ?? "";
}
}
catch
{
}
return "";
}
private static bool StringEquals(string left, string right)
{
return string.Equals(left ?? "", right ?? "", StringComparison.OrdinalIgnoreCase);
}
private static bool GetBool(Dictionary<string, object> source, string key)
{
object value;
if (source.TryGetValue(key, out value) && value != null)
{
try { return Convert.ToBoolean(value); } catch { }
}
return false;
}
private static string Sha256(string path)
{
try
{
using (SHA256 sha = SHA256.Create())
using (FileStream stream = File.OpenRead(path))
{
byte[] hash = sha.ComputeHash(stream);
var sb = new StringBuilder(hash.Length * 2);
foreach (byte b in hash)
{
sb.Append(b.ToString("x2", CultureInfo.InvariantCulture));
}
return sb.ToString();
}
}
catch
{
return "";
}
}
}
}