From 33c060c65554ad95b27c160525f4667ae803a93c Mon Sep 17 00:00:00 2001 From: Alexey 'Cluster' Avdyukhin Date: Tue, 11 Apr 2017 23:38:06 +0300 Subject: Real Telnet --- CloverShell/ClovershellConnection.cs | 123 +++++++++++++++++++++++------------ CloverShell/ExecConnection.cs | 2 +- CloverShell/ShellConnection.cs | 54 +++++++++++++-- Program.cs | 6 +- 4 files changed, 135 insertions(+), 50 deletions(-) diff --git a/CloverShell/ClovershellConnection.cs b/CloverShell/ClovershellConnection.cs index 00c24c0..ae247c9 100644 --- a/CloverShell/ClovershellConnection.cs +++ b/CloverShell/ClovershellConnection.cs @@ -32,7 +32,7 @@ namespace com.clusterrr.clovershell byte[] lastPingResponse = null; DateTime lastAliveTime; public delegate void OnClovershellConnected(); - public event OnClovershellConnected OnConnected = delegate{}; + public event OnClovershellConnected OnConnected = delegate { }; internal enum ClovershellCommand { @@ -236,13 +236,14 @@ namespace com.clusterrr.clovershell epReader = device.OpenEndpointReader((ReadEndpointID)inEndp, 65536); epWriter = device.OpenEndpointWriter((WriteEndpointID)outEndp); Debug.WriteLine("clovershell connected"); - // Kill all other serrions and drop all output + // Kill all other sessions and drop all output killAll(); var body = new byte[65536]; int len; while (epReader.Read(body, 50, out len) == ErrorCode.Ok) ; epReader.ReadBufferSize = 65536; epReader.DataReceived += epReader_DataReceived; + epReader.ReadThreadPriority = ThreadPriority.AboveNormal; epReader.DataReceivedEnabled = true; lastAliveTime = DateTime.Now; online = true; @@ -250,7 +251,7 @@ namespace com.clusterrr.clovershell while (device.mUsbRegistry.IsAlive) { Thread.Sleep(100); - if ((IdleTime.TotalSeconds >= 5) && (Ping() < 0)) + if ((IdleTime.TotalSeconds >= 10) && (Ping() < 0)) throw new ClovershellException("no answer from device"); } break; @@ -261,7 +262,7 @@ namespace com.clusterrr.clovershell } catch (ClovershellException ex) { - Debug.WriteLine(ex.Message); + Debug.WriteLine(ex.Message + ex.StackTrace); break; } } @@ -301,19 +302,41 @@ namespace com.clusterrr.clovershell if (!online) throw new ClovershellException("no clovershell connection, make sure your NES Mini connected, turned on and clovershell mod installed"); } + public void Disconnect() + { + try + { + if (device != null) + device.Close(); + } + catch { } + } + void epReader_DataReceived(object sender, EndpointDataEventArgs e) { - var cmd = (ClovershellCommand)e.Buffer[0]; - var arg = e.Buffer[1]; - var len = e.Buffer[2] | (e.Buffer[3] * 0x100); - proceedPacket(cmd, arg, e.Buffer, 4, len); +#if VERY_DEBUG + Debug.WriteLine("<-[CLV] " + BitConverter.ToString(e.Buffer, 0, e.Count)); +#endif + int pos = 0; + int count = e.Count; + while (count > 0) + { + var cmd = (ClovershellCommand)e.Buffer[pos]; + var arg = e.Buffer[pos + 1]; + var len = e.Buffer[pos + 2] | (e.Buffer[pos + 3] * 0x100); + proceedPacket(cmd, arg, e.Buffer, pos + 4, len); + count -= len + 4; + pos += len + 4; + } } void proceedPacket(ClovershellCommand cmd, byte arg, byte[] data, int pos, int len) { if (len < 0) len = data.Length; - //Debug.WriteLine(string.Format("cmd={0}, arg={1:X2}, len={2}", cmd, arg, len)); +#if VERY_DEBUG + Debug.WriteLine(string.Format("<-[CLV] cmd={0}, arg={1:X2}, len={2}, data={3}", cmd, arg, len, BitConverter.ToString(data, pos, len))); +#endif lastAliveTime = DateTime.Now; switch (cmd) { @@ -368,42 +391,52 @@ namespace com.clusterrr.clovershell throw new ClovershellException("kill all exec: write error"); } - internal void writeUsb(ClovershellCommand cmd, byte arg, byte[] data = null, int l = -1) + internal void writeUsb(ClovershellCommand cmd, byte arg, byte[] data = null, int pos = 0, int l = -1) { - if (!online) throw new ClovershellException("NES Mini is offline"); - var len = (l >= 0) ? l : ((data != null) ? data.Length : 0); - var buff = new byte[len + 4]; - buff[0] = (byte)cmd; - buff[1] = arg; - buff[2] = (byte)(len & 0xFF); - buff[3] = (byte)((len >> 8) & 0xFF); - if (data != null) - Array.Copy(data, 0, buff, 4, len); - int tLen = 0; - int pos = 0; - len += 4; - int repeats = 0; - while (pos < len) + if (!IsOnline) throw new ClovershellException("NES Mini is offline"); + if (epWriter == null) return; + lock (epWriter) { - var res = epWriter.Write(buff, pos, len, 1000, out tLen); - pos += tLen; - len -= tLen; - if (res != ErrorCode.Ok) + var len = (l >= 0) ? l : ((data != null) ? (data.Length - pos) : 0); +#if VERY_DEBUG + Debug.WriteLine(string.Format("->[CLV] cmd={0}, arg={1:X2}, len={2}, data={3}", cmd, arg, len, data != null ? BitConverter.ToString(data, pos, len) : "")); +#endif + var buff = new byte[len + 4]; + buff[0] = (byte)cmd; + buff[1] = arg; + buff[2] = (byte)(len & 0xFF); + buff[3] = (byte)((len >> 8) & 0xFF); + if (data != null) + Array.Copy(data, pos, buff, 4, len); + int tLen = 0; + pos = 0; + len += 4; + int repeats = 0; + while (pos < len) { - if (repeats >= 3) break; - repeats++; - Thread.Sleep(100); + var res = epWriter.Write(buff, pos, len, 1000, out tLen); +#if VERY_DEBUG + Debug.WriteLine("->[CLV] " + BitConverter.ToString(buff, pos, len)); +#endif + pos += tLen; + len -= tLen; + if (res != ErrorCode.Ok) + { + if (repeats >= 3) break; + repeats++; + Thread.Sleep(100); + } } + if (len > 0) + throw new ClovershellException("write error"); } - if (len > 0) - throw new ClovershellException("write error"); } void shellListenerThreadLoop(object o) { + var server = o as TcpListener; try { - var server = o as TcpListener; while (true) { while (!server.Pending()) Thread.Sleep(100); @@ -419,7 +452,7 @@ namespace com.clusterrr.clovershell { Thread.Sleep(50); t++; - if (t >= 20) + if (t >= 50) throw new ClovershellException("shell request timeout"); } } @@ -429,8 +462,10 @@ namespace com.clusterrr.clovershell } catch (ClovershellException ex) { + Debug.WriteLine(ex.Message + ex.StackTrace); + if (connection.socket.Connected) + connection.socket.Send(Encoding.ASCII.GetBytes("Error: " + ex.Message)); connection.Dispose(); - Debug.WriteLine("Error: " + ex.Message + ex.StackTrace); } } } @@ -440,7 +475,11 @@ namespace com.clusterrr.clovershell } catch (ClovershellException ex) { - Debug.WriteLine(ex.Message); + Debug.WriteLine(ex.Message + ex.StackTrace); + } + finally + { + server.Stop(); } shellEnabled = false; } @@ -467,7 +506,7 @@ namespace com.clusterrr.clovershell { try { - var connection = (from c in pendingExecConnections where c.command == command select c).First(); + var connection = (from c in pendingExecConnections where c.command == command select c).Last(); pendingExecConnections.Remove(connection); //Debug.WriteLine("Executing: " + command); connection.id = arg; @@ -560,6 +599,7 @@ namespace com.clusterrr.clovershell } public int Ping() { + if (!IsOnline) throw new ClovershellException("NES Mini is offline"); var rnd = new Random(); var data = new byte[4]; rnd.NextBytes(data); @@ -580,14 +620,13 @@ namespace com.clusterrr.clovershell { var stdOut = new MemoryStream(); Execute(command, null, stdOut, null, timeout, throwOnNonZero); - var buff = new byte[stdOut.Length]; - stdOut.Seek(0, SeekOrigin.Begin); - stdOut.Read(buff, 0, buff.Length); + var buff = stdOut.ToArray(); return Encoding.UTF8.GetString(buff).Trim(); } public int Execute(string command, Stream stdin = null, Stream stdout = null, Stream stderr = null, int timeout = 0, bool throwOnNonZero = false) { + if (!IsOnline) throw new ClovershellException("NES Mini is offline"); if (throwOnNonZero && stderr == null) stderr = new MemoryStream(); using (var c = new ExecConnection(this, command, stdin, stdout, stderr)) @@ -601,7 +640,7 @@ namespace com.clusterrr.clovershell { Thread.Sleep(50); t++; - if (t >= 20) + if (t >= 50) throw new ClovershellException("exec request timeout"); } while (!c.finished) diff --git a/CloverShell/ExecConnection.cs b/CloverShell/ExecConnection.cs index 913d07b..2e68281 100644 --- a/CloverShell/ExecConnection.cs +++ b/CloverShell/ExecConnection.cs @@ -56,7 +56,7 @@ namespace com.clusterrr.clovershell { l = stdin.Read(buffer, 0, buffer.Length); if (l > 0) - connection.writeUsb(ClovershellConnection.ClovershellCommand.CMD_EXEC_STDIN, (byte)id, buffer, l); + connection.writeUsb(ClovershellConnection.ClovershellCommand.CMD_EXEC_STDIN, (byte)id, buffer, 0, l); else break; LastDataTime = DateTime.Now; diff --git a/CloverShell/ShellConnection.cs b/CloverShell/ShellConnection.cs index c1be436..abd4eba 100644 --- a/CloverShell/ShellConnection.cs +++ b/CloverShell/ShellConnection.cs @@ -11,7 +11,7 @@ namespace com.clusterrr.clovershell internal class ShellConnection : IDisposable { public readonly ClovershellConnection connection; - Socket socket; + internal Socket socket; internal int id; internal Thread shellConnectionThread; @@ -20,6 +20,9 @@ namespace com.clusterrr.clovershell this.connection = connection; this.socket = socket; id = -1; + socket.Send(new byte[] { 0xFF, 0xFD, 0x03 }); // Do Suppress Go Ahead + socket.Send(new byte[] { 0xFF, 0xFB, 0x03 }); // Will Suppress Go Ahead + socket.Send(new byte[] { 0xFF, 0xFB, 0x01 }); // Will Echo } internal void shellConnectionLoop() @@ -31,7 +34,47 @@ namespace com.clusterrr.clovershell { var l = socket.Receive(buff); if (l > 0) - connection.writeUsb(ClovershellConnection.ClovershellCommand.CMD_SHELL_IN, (byte)id, buff, l); + { + int start = 0; + int pos = 0; + do + { + if ((pos + 1 < l) && (buff[pos] == '\r') && (buff[pos + 1] == '\n')) // New line? + { + // Hey, dot not send \r\n! I'll cut it to \n + buff[pos] = (byte)'\n'; + connection.writeUsb(ClovershellConnection.ClovershellCommand.CMD_SHELL_IN, (byte)id, buff, start, pos - start + 1); + pos += 2; + start = pos; + } + else if ((pos + 1 < l) && (buff[pos] == 0xFF)) // Telnet command? + { + if (buff[pos + 1] == 0xFF) // Or just 0xFF... + { + connection.writeUsb(ClovershellConnection.ClovershellCommand.CMD_SHELL_IN, (byte)id, buff, start, pos - start + 1); + pos += 2; + start = pos; + } + else if (pos + 2 < l) + { + if (pos - start > 0) + connection.writeUsb(ClovershellConnection.ClovershellCommand.CMD_SHELL_IN, (byte)id, buff, start, pos - start); + var cmd = buff[pos + 1]; // Telnet command code + var opt = buff[pos + 2]; // Telnet option code +#if VERY_DEBUG + Debug.WriteLine(string.Format("Telnet command: CMD={0:X2} ARG={1:X2}", cmd, opt)); +#endif + pos += 3; + start = pos; + } + } + else pos++; // No, moving to next character + if ((pos == l) && (l - start > 0)) // End of packet + { + connection.writeUsb(ClovershellConnection.ClovershellCommand.CMD_SHELL_IN, (byte)id, buff, start, l - start); + } + } while (pos < l); + } else break; } @@ -39,8 +82,11 @@ namespace com.clusterrr.clovershell catch (ThreadAbortException) { } - catch (ClovershellException) + catch (ClovershellException ex) { + Debug.WriteLine(ex.Message + ex.StackTrace); + if (socket.Connected) + socket.Send(Encoding.ASCII.GetBytes("Error: " + ex.Message)); } finally { @@ -50,7 +96,7 @@ namespace com.clusterrr.clovershell socket.Close(); connection.shellConnections[id] = null; } - + public void Dispose() { if (shellConnectionThread != null) diff --git a/Program.cs b/Program.cs index 72782a3..a5fca4c 100644 --- a/Program.cs +++ b/Program.cs @@ -44,8 +44,8 @@ namespace com.clusterrr.clovershell nes.ShellPort = ushort.Parse(args[1]); nes.ShellEnabled = true; nes.AutoReconnect = true; - Console.WriteLine("Started shell server on port {0}.", nes.ShellPort); - Console.WriteLine("Connect to it using terminal client (raw mode, no local echo)."); + Console.WriteLine("Started shell server on telnet://127.0.0.1:{0}.", nes.ShellPort); + Console.WriteLine("Connect to it using telnet client."); Console.WriteLine("Press ENTER to stop."); Console.ReadLine(); result = 0; @@ -90,7 +90,7 @@ namespace com.clusterrr.clovershell else stderr = Console.OpenStandardError(); var s = DateTime.Now; result = nes.Execute(args[1], stdin, stdout, stderr); - Console.Error.WriteLine("Done in {0}ms. Exit code: {1}", (int)(DateTime.Now - s).TotalMilliseconds, result); + //Console.Error.WriteLine("Done in {0}ms. Exit code: {1}", (int)(DateTime.Now - s).TotalMilliseconds, result); break; case "pull": if (args.Length < 2) -- cgit v1.2.3