feat: add send connector preflight probes
This commit is contained in:
848
native/ISphereWinHelper/SendConnectorPreflight.cs
Normal file
848
native/ISphereWinHelper/SendConnectorPreflight.cs
Normal 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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user