Skip to content

Commit

Permalink
ADB: 大规模重构,使用Win32黑科技让主程序启动的ADB服务器能够在主程序关闭时被系统关闭
Browse files Browse the repository at this point in the history
  • Loading branch information
Rcmcpe committed Nov 12, 2020
1 parent f09d65f commit 25389fd
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 33 deletions.
1 change: 1 addition & 0 deletions Auto Arknights CLI/Auto Arknights CLI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<SelfContained>false</SelfContained>
<PublishReadyToRun>true</PublishReadyToRun>
<PublishDir>artifact\</PublishDir>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)'=='Release'">
Expand Down
19 changes: 19 additions & 0 deletions Auto Arknights CLI/app.manifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>

<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--To support job objects -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
</application>
</compatibility>
</assembly>
107 changes: 79 additions & 28 deletions Auto Arknights Core/Adb.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,65 +15,116 @@ public AdbException(string? message) : base(message) { }

public sealed class Adb
{
private static readonly string[] FailSigns =
public Adb(string executable, string targetSerial)
{
"cannot connect", "no device", "no emulators", "device unauthorized", "device still", "device offline"
};
Executable = executable;
TargetSerial = targetSerial;

public Adb(string executable) => Executable = executable;
if (Process.GetProcessesByName("adb").Any()) return;
ExecuteCore("start-server", out _);

public string? Target { get; set; }
public string Executable { get; set; }

public void Connect(string target)
{
Target = target;
ExecuteCore($"connect {target}");
var job = new Job();
job.AddProcess(Process.GetProcessesByName("adb")[0].Handle);
}

public string Executable { get; set; }
public string TargetSerial { get; set; }

public void Click(Point point)
{
ExecuteCore($"shell input tap {point.X} {point.Y}");
Execute($"shell input tap {point.X} {point.Y}");
}

public Mat GetScreenshot()
{
Mat result = Cv2.ImDecode(ExecuteCore("exec-out screencap -p", 5 * 1024 * 1024), ImreadModes.Color);
Mat result = Cv2.ImDecode(ExecuteOutBytes("exec-out screencap -p", 5 * 1024 * 1024), ImreadModes.Color);
if (result.Empty()) throw new AdbException("未接收到有效数据");

return result;
}

public string Execute(string parameter) => Encoding.UTF8.GetString(ExecuteCore(parameter));
public void Execute(string parameter)
{
ExecuteCore(parameter, out string stdErr);
if (!string.IsNullOrWhiteSpace(stdErr)) throw new AdbException($"ADB错误 StdErr: {stdErr}");
}

public string ExecuteOut(string parameter, int bufferSize) =>
Encoding.UTF8.GetString(ExecuteOutBytes(parameter, bufferSize));

private byte[] ExecuteCore(string parameter, int bufferSize = 1024)
public byte[] ExecuteOutBytes(string parameter, int bufferSize)
{
Log.That(parameter, Log.Level.Debug, "ADB");

EnsureConnected();
ExecuteCore(parameter, out byte[] stdOutBytes, bufferSize, out string stdErr);
if (!string.IsNullOrWhiteSpace(stdErr)) throw new AdbException($"ADB错误 StdErr: {stdErr}");

return stdOutBytes;
}

private void EnsureConnected()
{
for (var timesTried = 0; timesTried < 2; timesTried++)
{
ExecuteCore("get-state", out string state, out _);
if (state == "device") return;

if (timesTried == 1) ExecuteCore("kill-server", out _);
Log.That($"正在连接到 {TargetSerial}", Log.Level.Debug, "ADB");
ExecuteCore($"connect {TargetSerial}", out _);
}

throw new AdbException($"无法连接到 {TargetSerial}");
}

private static byte[] ReadToEnd(Stream stream, int bufferSize)
{
byte[] buffer = new byte[bufferSize];

int read;
var totalLen = 0;
while ((read = stream.Read(buffer, totalLen, bufferSize - totalLen)) > 0) totalLen += read;

Array.Resize(ref buffer, totalLen);
return buffer;
}

private void ExecuteCore(string parameter, out string stdErr)
{
using var process = new Process
{
StartInfo = new ProcessStartInfo(Executable, $"-s {Target} {parameter}")
StartInfo = new ProcessStartInfo(Executable, parameter)
{
CreateNoWindow = true, RedirectStandardOutput = true
CreateNoWindow = true, RedirectStandardError = true
}
};

process.Start();

byte[] buffer = new byte[bufferSize];
Stream stdOut = process.StandardOutput.BaseStream;
stdErr = process.StandardError.ReadToEnd().Trim();
}

int read;
var totalLen = 0;
while ((read = stdOut.Read(buffer, totalLen, bufferSize - totalLen)) > 0) totalLen += read;
private void ExecuteCore(string parameter, out string stdOut, out string stdErr)
{
ExecuteCore(parameter, out byte[] stdOutBytes, 1024, out stdErr);
stdOut = Encoding.UTF8.GetString(stdOutBytes).Trim();
}

Array.Resize(ref buffer, totalLen);
private void ExecuteCore(string parameter, out byte[] stdOut, int stdOutBufferSize, out string stdErr)
{
using var process = new Process
{
StartInfo = new ProcessStartInfo(Executable, parameter)
{
CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true
}
};

// 检查ADB连接状态, 但是不在收到截图之类较大的数据的时候检查
if (totalLen > 200) return buffer;
string result = Encoding.UTF8.GetString(buffer);
if (FailSigns.Any(failSign => result.Contains(failSign))) throw new AdbException("不能在未连接的情况下使用ADB");
return buffer;
process.Start();

stdOut = ReadToEnd(process.StandardOutput.BaseStream, stdOutBufferSize);
stdErr = process.StandardError.ReadToEnd().Trim();
}
}
}
6 changes: 1 addition & 5 deletions Auto Arknights Core/Device.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ public sealed class Device : IDisposable
private readonly Assets _assets = new();
private readonly ImageRegister _register = new("Assets/Cache");

public Device(string adbPath, string adbRemote)
{
_adb = new Adb(adbPath);
_adb.Connect(adbRemote);
}
public Device(string exePath, string targetSerial) => _adb = new Adb(exePath, targetSerial);

public void Dispose()
{
Expand Down
90 changes: 90 additions & 0 deletions Auto Arknights Core/Job.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

// ReSharper disable FieldCanBeMadeReadOnly.Global

namespace REVUnit.AutoArknights.Core
{
/// <summary>
/// 用来关闭ADB的黑科技。
/// </summary>
public class Job
{
private readonly IntPtr _handle;

public Job()
{
_handle = CreateJobObject(IntPtr.Zero, Guid.NewGuid().ToString());

var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION { LimitFlags = 0x2000 };
var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION { BasicLimitInformation = info };

int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);

try
{
Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);
if (!SetInformationJobObject(_handle, 9, extendedInfoPtr,
(uint) length))
throw new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
Marshal.FreeHGlobal(extendedInfoPtr);
}
}

public void AddProcess(IntPtr handle)
{
AssignProcessToJobObject(_handle, handle);
}

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string? name);

[DllImport("kernel32.dll")]
private static extern bool SetInformationJobObject(IntPtr job, int infoType,
IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
}

[StructLayout(LayoutKind.Sequential)]
internal struct IO_COUNTERS
{
public ulong ReadOperationCount;
public ulong WriteOperationCount;
public ulong OtherOperationCount;
public ulong ReadTransferCount;
public ulong WriteTransferCount;
public ulong OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
public long PerProcessUserTimeLimit;
public long PerJobUserTimeLimit;
public uint LimitFlags;
public UIntPtr MinimumWorkingSetSize;
public UIntPtr MaximumWorkingSetSize;
public uint ActiveProcessLimit;
public UIntPtr Affinity;
public uint PriorityClass;
public uint SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
internal struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
public IO_COUNTERS IoInfo;
public UIntPtr ProcessMemoryLimit;
public UIntPtr JobMemoryLimit;
public UIntPtr PeakProcessMemoryUsed;
public UIntPtr PeakJobMemoryUsed;
}
}

0 comments on commit 25389fd

Please sign in to comment.