849 lines
32 KiB
C#
849 lines
32 KiB
C#
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 "";
|
|
}
|
|
}
|
|
}
|
|
}
|