Files

375 lines
14 KiB
C#
Raw Permalink Normal View History

2024-03-03 00:27:52 +01:00
/*
* MIT License
*
* Copyright (c) 2024 The DuoWOA authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
using System;
using System.Collections.Generic;
2024-03-03 15:13:51 +01:00
using System.ComponentModel;
2024-03-03 20:52:24 +01:00
using System.Diagnostics;
2024-03-03 00:27:52 +01:00
using System.Linq;
using System.Text;
2024-03-03 11:08:16 +01:00
using System.Text.RegularExpressions;
2024-03-03 15:13:51 +01:00
using System.Threading;
2024-03-03 00:27:52 +01:00
namespace AndroidDebugBridge
{
public partial class AndroidDebugBridgeTransport
{
public void Shell()
{
if (!IsConnected)
{
throw new Exception("Cannot open a shell with no accepted connection!");
}
2024-03-03 00:27:52 +01:00
using AndroidDebugBridgeStream stream = OpenStream("shell,v2,TERM=xterm-256color,pty:");
stream.DataReceived += (object? sender, byte[] incomingMessage) =>
{
2024-03-03 10:32:03 +01:00
// 0: STDIN
// 1: STDOUT
// 2: STDERR
// 3: EXIT
// 4: Close STDIN
// 5: Window Size Change
2024-03-03 11:08:16 +01:00
Regex escapeSequences = new(@"(\x9D|\x1B\]).+(\x07|\x9c)|\x1b [F-Nf-n]|\x1b#[3-8]|\x1b%[@Gg]|\x1b[()*+][A-Za-z0-9=`<>]|\x1b[()*+]\""[>4?]|\x1b[()*+]%[0-6=]|\x1b[()*+]&[4-5]|\x1b[-.\/][ABFHLM]|\x1b[6-9Fcl-o=>\|\}~]|(\x9f|\x1b_).+\x9c|(\x90|\x1bP).+\x9c|(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]|(\x9e|\x1b\^).+\x9c|\x1b[DEHMNOVWXYZ78]");
2024-03-03 00:27:52 +01:00
byte messageType = incomingMessage[0];
2024-03-03 10:32:03 +01:00
if (messageType == 0)
2024-03-03 00:27:52 +01:00
{
2024-03-03 10:32:03 +01:00
// Can't do much here, STDIN is not writable ofc...
}
else if (messageType == 1)
{
uint packetArgument = BitConverter.ToUInt32(incomingMessage[1..5]);
2024-03-03 11:08:16 +01:00
string output = Encoding.UTF8.GetString(incomingMessage[5..]);
MatchCollection matches = escapeSequences.Matches(output);
int[] escapeSequenceIndices = matches.SelectMany(x =>
{
List<int> indices = new();
for (int i = x.Index; i < i + x.Length; i++)
{
indices.Add(i);
}
return indices;
}).ToArray();
2024-03-03 10:32:03 +01:00
for (int i = 0; i < output.Length; i++)
{
2024-03-03 11:08:16 +01:00
if (escapeSequenceIndices.Contains(i))
{
// Ignore for now...
continue;
}
2024-03-03 10:32:03 +01:00
char ch = output[i];
if (ch == 0x01)
{
2024-03-03 11:08:16 +01:00
string escapeArgument = output[(i + 1)..(i + 5)];
2024-03-03 10:32:03 +01:00
i += 4;
}
else
{
Console.Out.Write(ch);
}
}
(sender as AndroidDebugBridgeStream)?.SendAcknowledgement();
}
else if (messageType == 2)
{
uint packetArgument = BitConverter.ToUInt32(incomingMessage[1..5]);
2024-03-03 11:08:16 +01:00
string output = Encoding.UTF8.GetString(incomingMessage[5..]);
MatchCollection matches = escapeSequences.Matches(output);
int[] escapeSequenceIndices = matches.SelectMany(x =>
{
List<int> indices = new();
for (int i = x.Index; i < i + x.Length; i++)
{
indices.Add(i);
}
return indices;
}).ToArray();
2024-03-03 10:32:03 +01:00
for (int i = 0; i < output.Length; i++)
{
2024-03-03 11:08:16 +01:00
if (escapeSequenceIndices.Contains(i))
{
// Ignore for now...
continue;
}
2024-03-03 10:32:03 +01:00
char ch = output[i];
if (ch == 0x01)
{
2024-03-03 11:08:16 +01:00
string escapeArgument = output[(i + 1)..(i + 5)];
2024-03-03 10:32:03 +01:00
i += 4;
}
else
{
Console.Error.Write(ch);
}
}
2024-03-03 00:27:52 +01:00
(sender as AndroidDebugBridgeStream)?.SendAcknowledgement();
}
else if (messageType == 3)
{
// Close notification.
}
2024-03-03 10:32:03 +01:00
else if (messageType == 4)
{
// Close STDIN
}
else if (messageType == 5)
{
// Window size change
}
2024-03-03 00:27:52 +01:00
else
{
// Unknown message type
}
};
// Configure terminal size
2024-03-03 10:32:03 +01:00
string terminalConfiguration = $"{Console.WindowHeight}x{Console.WindowWidth},0x0";
2024-03-03 00:27:52 +01:00
byte[] ConfigurationRes = Encoding.UTF8.GetBytes($"{terminalConfiguration}\0");
byte[] StringLength = BitConverter.GetBytes(ConfigurationRes.Length);
List<byte> consoleConfigurationData = ConfigurationRes.ToList();
consoleConfigurationData.Insert(0, 0x05);
consoleConfigurationData.InsertRange(1, StringLength);
stream.Write(consoleConfigurationData.ToArray());
2024-03-03 15:13:51 +01:00
BackgroundWorker consoleInputWorker = new()
2024-03-03 00:27:52 +01:00
{
2024-03-03 15:13:51 +01:00
WorkerSupportsCancellation = true
};
consoleInputWorker.DoWork += (object? sender, DoWorkEventArgs e) =>
{
ConsoleKeyInfo readKey = Console.ReadKey(true);
2024-03-03 20:52:24 +01:00
if (!stream.IsClosed && readKey.KeyChar != '\0')
2024-03-03 00:27:52 +01:00
{
2024-03-03 15:13:51 +01:00
byte[] buffer = Encoding.UTF8.GetBytes(readKey.KeyChar.ToString());
byte[] StringLength = BitConverter.GetBytes(buffer.Length);
2024-03-03 00:27:52 +01:00
2024-03-03 15:13:51 +01:00
List<byte> consoleInputData = buffer.ToList();
consoleInputData.Insert(0, 0x00);
consoleInputData.InsertRange(1, StringLength);
2024-03-03 00:27:52 +01:00
2024-03-03 15:13:51 +01:00
stream.Write(consoleInputData.ToArray());
2024-03-03 00:27:52 +01:00
}
2024-03-03 15:13:51 +01:00
};
consoleInputWorker.RunWorkerCompleted += (object? sender, RunWorkerCompletedEventArgs e) =>
{
2024-03-03 20:52:24 +01:00
if (!stream.IsClosed)
2024-03-03 15:13:51 +01:00
{
consoleInputWorker.RunWorkerAsync();
}
};
2024-03-03 00:27:52 +01:00
stream.DataClosed += (object? sender, EventArgs args) =>
{
2024-03-03 15:13:51 +01:00
consoleInputWorker.CancelAsync();
2024-03-03 00:27:52 +01:00
};
2024-03-03 15:13:51 +01:00
consoleInputWorker.RunWorkerAsync();
2024-03-03 20:52:24 +01:00
Debug.WriteLine("Entering shell closed Loop!");
2024-03-03 15:13:51 +01:00
// Wait til the stream is closed
2024-03-03 20:52:24 +01:00
while (!stream.IsClosed)
2024-03-03 15:13:51 +01:00
{
Thread.Sleep(100);
}
2024-03-03 20:52:24 +01:00
Debug.WriteLine("Leaving shell closed Loop!");
}
2024-03-03 22:17:12 +01:00
private object ShellLock = new();
2024-03-03 20:52:24 +01:00
public string Shell(string command)
{
if (!IsConnected)
{
throw new Exception("Cannot open a shell with a specific command with no accepted connection!");
}
2024-03-03 22:17:12 +01:00
lock (ShellLock)
2024-03-03 20:52:24 +01:00
{
2024-03-03 22:17:12 +01:00
string ConsoleOutputString = string.Empty;
using AndroidDebugBridgeStream stream = OpenStream($"shell,v2,TERM=xterm-256color,pty:{command}");
2024-03-03 20:52:24 +01:00
2024-03-03 22:17:12 +01:00
stream.DataReceived += (object? sender, byte[] incomingMessage) =>
2024-03-03 20:52:24 +01:00
{
2024-03-03 22:17:12 +01:00
// 0: STDIN
// 1: STDOUT
// 2: STDERR
// 3: EXIT
// 4: Close STDIN
// 5: Window Size Change
2024-03-03 20:52:24 +01:00
2024-03-03 22:17:12 +01:00
Regex escapeSequences = new(@"(\x9D|\x1B\]).+(\x07|\x9c)|\x1b [F-Nf-n]|\x1b#[3-8]|\x1b%[@Gg]|\x1b[()*+][A-Za-z0-9=`<>]|\x1b[()*+]\""[>4?]|\x1b[()*+]%[0-6=]|\x1b[()*+]&[4-5]|\x1b[-.\/][ABFHLM]|\x1b[6-9Fcl-o=>\|\}~]|(\x9f|\x1b_).+\x9c|(\x90|\x1bP).+\x9c|(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]|(\x9e|\x1b\^).+\x9c|\x1b[DEHMNOVWXYZ78]");
2024-03-03 20:52:24 +01:00
2024-03-03 22:17:12 +01:00
byte messageType = incomingMessage[0];
if (messageType == 0)
2024-03-03 20:52:24 +01:00
{
2024-03-03 22:17:12 +01:00
// Can't do much here, STDIN is not writable ofc...
2024-03-03 20:52:24 +01:00
}
2024-03-03 22:17:12 +01:00
else if (messageType == 1)
2024-03-03 20:52:24 +01:00
{
2024-03-03 22:17:12 +01:00
uint packetArgument = BitConverter.ToUInt32(incomingMessage[1..5]);
string output = Encoding.UTF8.GetString(incomingMessage[5..]);
2024-03-03 20:52:24 +01:00
2024-03-03 22:17:12 +01:00
MatchCollection matches = escapeSequences.Matches(output);
int[] escapeSequenceIndices = matches.SelectMany(x =>
2024-03-03 20:52:24 +01:00
{
2024-03-03 22:17:12 +01:00
List<int> indices = new();
for (int i = x.Index; i < i + x.Length; i++)
{
indices.Add(i);
}
return indices;
}).ToArray();
for (int i = 0; i < output.Length; i++)
{
if (escapeSequenceIndices.Contains(i))
{
// Ignore for now...
continue;
}
char ch = output[i];
if (ch == 0x01)
{
string escapeArgument = output[(i + 1)..(i + 5)];
i += 4;
}
else
{
ConsoleOutputString += ch;
}
2024-03-03 20:52:24 +01:00
}
2024-03-03 22:17:12 +01:00
(sender as AndroidDebugBridgeStream)?.SendAcknowledgement();
2024-03-03 20:52:24 +01:00
}
2024-03-03 22:17:12 +01:00
else if (messageType == 2)
{
uint packetArgument = BitConverter.ToUInt32(incomingMessage[1..5]);
string output = Encoding.UTF8.GetString(incomingMessage[5..]);
2024-03-03 20:52:24 +01:00
2024-03-03 22:17:12 +01:00
MatchCollection matches = escapeSequences.Matches(output);
int[] escapeSequenceIndices = matches.SelectMany(x =>
{
List<int> indices = new();
for (int i = x.Index; i < i + x.Length; i++)
{
indices.Add(i);
}
return indices;
}).ToArray();
for (int i = 0; i < output.Length; i++)
{
if (escapeSequenceIndices.Contains(i))
{
// Ignore for now...
continue;
}
char ch = output[i];
if (ch == 0x01)
{
string escapeArgument = output[(i + 1)..(i + 5)];
i += 4;
}
else
{
ConsoleOutputString += ch;
}
}
(sender as AndroidDebugBridgeStream)?.SendAcknowledgement();
}
else if (messageType == 3)
{
// Close notification.
}
else if (messageType == 4)
{
// Close STDIN
}
else if (messageType == 5)
{
// Window size change
}
else
{
// Unknown message type
}
};
// Configure terminal size
string terminalConfiguration = $"{500}x{500},0x0";
byte[] ConfigurationRes = Encoding.UTF8.GetBytes($"{terminalConfiguration}\0");
byte[] StringLength = BitConverter.GetBytes(ConfigurationRes.Length);
List<byte> consoleConfigurationData = ConfigurationRes.ToList();
consoleConfigurationData.Insert(0, 0x05);
consoleConfigurationData.InsertRange(1, StringLength);
stream.Write(consoleConfigurationData.ToArray());
Debug.WriteLine("Entering shell/cmd closed Loop!");
// Wait til the stream is closed
while (!stream.IsClosed)
2024-03-03 20:52:24 +01:00
{
2024-03-03 22:17:12 +01:00
Thread.Sleep(100);
2024-03-03 20:52:24 +01:00
}
2024-06-23 10:54:02 +02:00
if (stream.IsFaulted)
{
throw stream.ReceivedException ?? new Exception("Stream caught an unknown exception!");
}
2024-03-03 22:17:12 +01:00
Debug.WriteLine("Leaving shell/cmd closed Loop!");
2024-03-03 20:52:24 +01:00
2024-03-03 22:17:12 +01:00
return ConsoleOutputString;
2024-03-03 20:52:24 +01:00
}
2024-03-03 00:27:52 +01:00
}
}
}