1 /*---------------------------------------------------------------------------*
2 
3   Copyright 2010-2012 Nintendo.  All rights reserved.
4 
5   These coded instructions, statements, and computer programs contain
6   proprietary information of Nintendo of America Inc. and/or Nintendo
7   Company Ltd., and are protected by Federal copyright law.  They may
8   not be disclosed to third parties or copied or duplicated in any form,
9   in whole or in part, without the prior written consent of Nintendo.
10 
11  *---------------------------------------------------------------------------*/
12 
13 using System;
14 using System.Collections.Generic;
15 using System.Diagnostics;
16 using System.Globalization;
17 using System.IO;
18 using System.Net;
19 using System.Net.NetworkInformation;
20 using System.Net.Sockets;
21 using System.Runtime.InteropServices;
22 using System.Text;
23 using System.Threading;
24 using CafeX;
25 
26 namespace Nintendo.SDSG
27 {
28 
29     public class KeyboardInputProcessorBase
30     {
31         protected static readonly string DK_CONSOLE_EXPIRE_MESSAGE = "cattoucan: Demand enabling input because {0} seconds have elapsed.";
32         protected enum FileType { Unknown, Disk, Char, Pipe };
33         protected enum StdHandle { Stdin = -10, Stdout = -11, Stderr = -12 };
34         protected const uint KEY_EVENT = 0x0001; // Event contains key event record
35         protected const uint MOUSE_EVENT = 0x0002; // Event contains mouse event record
36         protected const uint WINDOW_BUFFER_SIZE_EVENT = 0x0004; // Event contains window change event record
37         protected const uint MENU_EVENT = 0x0008; // Event contains menu event record
38         protected const uint FOCUS_EVENT = 0x0010; // event contains focus change
39 
40         protected const uint ENABLE_ECHO_INPUT = 0x0004;
41         protected const uint ENABLE_LINE_INPUT = 0x0002;
42         protected const uint ENABLE_PROCESSED_INPUT = 0x0001;
43         protected const int STD_INPUT_HANDLE = -10;
44         protected const int KEY_POOLING_WAIT_MSECONDS = 20;
45 
46         protected static int THREAD_JOIN_TIMEOUT_MSECONDS = 1000;
47         protected static int DK_INPUT_BUFFER_MAX_SIZE = 1024 * 4; // max 1024 Unicode chars
48         protected static double DK_READY_TIMEOUT = 8.0;
49         protected Stream input_stream;
50         protected Thread input_thread;
51         protected EventWaitHandle wait_handle = new EventWaitHandle(false, EventResetMode.AutoReset);
52         protected volatile bool bAborted = false;
53         protected volatile bool enableDKInput = false;
54         protected StringBuilder DKinputBuffer = new StringBuilder();
55         protected NetworkStream stream;
56         protected byte[] dk_input_buffer = new byte[DK_INPUT_BUFFER_MAX_SIZE];
57         protected int dk_input_count = 0;
58         protected DateTime startTime = DateTime.Now;
59         protected Encoding targetEncoding = null;
60         protected Encoding utf8Encoding = new UTF8Encoding();
61         protected Encoding unicodeEncoding = new System.Text.UnicodeEncoding();
62 
63 
64 		protected byte[] UP_ARROW_BYTES = new byte[] { 0x1B, 0x5B, 0x41 };
65 		protected byte[] DOWN_ARROW_BYTES = new byte[] { 0x1B, 0x5B, 0x42 };
66 		protected byte[] RIGHT_ARROW_BYTES = new byte[] { 0x1B, 0x5B, 0x43 };
67 		protected byte[] LEFT_ARROW_BYTES = new byte[] { 0x1B, 0x5B, 0x44 };
68 		protected byte[] BACKSPACE_BYTES = new byte[] { 0x7F };
69 		protected byte[] ESCAPE_BYTES = new byte[] { 0x1B };
70 		protected byte[] END_BYTES = new byte[] { 0x1B, 0x5B, 0x34, 0x7E };
71 		protected byte[] HOME_BYTES = new byte[] { 0x1B, 0x5B, 0x31, 0x7E };
72 		protected byte[] DELETE_BYTES = new byte[] { 0x1B, 0x5B, 0x33, 0x7E };
73 		protected byte[] INSERT_BYTES = new byte[] { 0x1B, 0x5B, 0x32, 0x7E };
74 		protected byte[] PG_UP_BYTES = new byte[] { 0x1B, 0x5B, 0x35, 0x7E };
75 		protected byte[] PG_DOWN_BYTES = new byte[] { 0x1B, 0x5B, 0x36, 0x7E };
76 
77         // Function keys - - request from SDSG.
78         protected byte[] F1_BYTES = new byte[] { 0x1B, 0x5B, 0x5B, 0x41 };
79         protected byte[] F2_BYTES = new byte[] { 0x1B, 0x5B, 0x5B, 0x42 };
80         protected byte[] F3_BYTES = new byte[] { 0x1B, 0x5B, 0x5B, 0x43 };
81         protected byte[] F4_BYTES = new byte[] { 0x1B, 0x5B, 0x5B, 0x44 };
82         protected byte[] F5_BYTES = new byte[] { 0x1B, 0x5B, 0x5B, 0x45 };
83         protected byte[] F6_BYTES = new byte[] { 0x1B, 0x5B, 0x31, 0x37, 0x7E };
84         protected byte[] F7_BYTES = new byte[] { 0x1B, 0x5B, 0x31, 0x38, 0x7E };
85         protected byte[] F8_BYTES = new byte[] { 0x1B, 0x5B, 0x31, 0x39, 0x7E };
86         protected byte[] F9_BYTES = new byte[] { 0x1B, 0x5B, 0x32, 0x30, 0x7E };
87         protected byte[] F10_BYTES = new byte[] { 0x1B, 0x5B, 0x32, 0x31, 0x7E };
88         protected byte[] F11_BYTES = new byte[] { 0x1B, 0x5B, 0x32, 0x33, 0x7E };
89         protected byte[] F12_BYTES = new byte[] { 0x1B, 0x5B, 0x32, 0x34, 0x7E };
90 
91 
92         #region P-Invoke
93 
94         [StructLayout(LayoutKind.Explicit)]
95         public struct INPUT_RECORD
96         {
97             [FieldOffset(0)]
98             public ushort EventType;
99             [FieldOffset(4)]
100             public KEY_EVENT_RECORD KeyEvent;
101             [FieldOffset(4)]
102             public MOUSE_EVENT_RECORD MouseEvent;
103             [FieldOffset(4)]
104             public WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
105             [FieldOffset(4)]
106             public MENU_EVENT_RECORD MenuEvent;
107             [FieldOffset(4)]
108             public FOCUS_EVENT_RECORD FocusEvent;
109         };
110 
111         [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Unicode)]
112         public struct KEY_EVENT_RECORD
113         {
114             [FieldOffset(0), MarshalAs(UnmanagedType.Bool)]
115             public bool bKeyDown;
116             [FieldOffset(4), MarshalAs(UnmanagedType.U2)]
117             public ushort wRepeatCount;
118             [FieldOffset(6), MarshalAs(UnmanagedType.U2)]
119             //public VirtualKeys wVirtualKeyCode;
120             public ushort wVirtualKeyCode;
121             [FieldOffset(8), MarshalAs(UnmanagedType.U2)]
122             public ushort wVirtualScanCode;
123             [FieldOffset(10)]
124             public char UnicodeChar;
125             [FieldOffset(12), MarshalAs(UnmanagedType.U4)]
126             //public ControlKeyState dwControlKeyState;
127             public uint dwControlKeyState;
128         }
129 
130         [StructLayout(LayoutKind.Sequential)]
131         public struct COORD
132         {
133 
134             public short X;
135             public short Y;
136 
137         }
138 
139         public struct SMALL_RECT
140         {
141 
142             public short Left;
143             public short Top;
144             public short Right;
145             public short Bottom;
146 
147         }
148         [StructLayout(LayoutKind.Sequential)]
149         public struct MOUSE_EVENT_RECORD
150         {
151             public COORD dwMousePosition;
152             public uint dwButtonState;
153             public uint dwControlKeyState;
154             public uint dwEventFlags;
155         }
156 
157         public struct WINDOW_BUFFER_SIZE_RECORD
158         {
159             public COORD dwSize;
160 
WINDOW_BUFFER_SIZE_RECORDNintendo.SDSG.KeyboardInputProcessorBase.WINDOW_BUFFER_SIZE_RECORD161             public WINDOW_BUFFER_SIZE_RECORD(short x, short y)
162             {
163                 dwSize = new COORD();
164                 dwSize.X = x;
165                 dwSize.Y = y;
166             }
167         }
168 
169         [StructLayout(LayoutKind.Sequential)]
170         public struct MENU_EVENT_RECORD
171         {
172             public uint dwCommandId;
173         }
174 
175         [StructLayout(LayoutKind.Sequential)]
176         public struct FOCUS_EVENT_RECORD
177         {
178             public uint bSetFocus;
179         }
180 
181         //CHAR_INFO struct, which was a union in the old days
182         // so we want to use LayoutKind.Explicit to mimic it as closely
183         // as we can
184         [StructLayout(LayoutKind.Explicit)]
185         public struct CHAR_INFO
186         {
187             [FieldOffset(0)]
188             public char UnicodeChar;
189             [FieldOffset(0)]
190             public char AsciiChar;
191             [FieldOffset(2)] //2 bytes seems to work properly
192             public UInt16 Attributes;
193         }
194 
195         [StructLayout(LayoutKind.Sequential)]
196         public struct CONSOLE_CURSOR_INFO
197         {
198             public uint Size;
199             public bool Visible;
200         }
201 
202         [StructLayout(LayoutKind.Sequential)]
203         public struct CONSOLE_HISTORY_INFO
204         {
205             public ushort cbSize;
206             public ushort HistoryBufferSize;
207             public ushort NumberOfHistoryBuffers;
208             public uint dwFlags;
209         }
210 
211         [StructLayout(LayoutKind.Sequential)]
212         public struct CONSOLE_SELECTION_INFO
213         {
214             public uint Flags;
215             public COORD SelectionAnchor;
216             public SMALL_RECT Selection;
217 
218             public const uint CONSOLE_MOUSE_DOWN = 0x0008; // Mouse is down
219             public const uint CONSOLE_MOUSE_SELECTION = 0x0004; //Selecting with the mouse
220             public const uint CONSOLE_NO_SELECTION = 0x0000; //No selection
221             public const uint CONSOLE_SELECTION_IN_PROGRESS = 0x0001; //Selection has begun
222             public const uint CONSOLE_SELECTION_NOT_EMPTY = 0x0002; //Selection rectangle is not empty
223         }
224 
225         public enum CtrlTypes : uint
226         {
227             CTRL_C_EVENT = 0,
228             CTRL_BREAK_EVENT,
229             CTRL_CLOSE_EVENT,
230             CTRL_LOGOFF_EVENT = 5,
231             CTRL_SHUTDOWN_EVENT
232         }
233 
234         public enum VirtualKeys : ushort
235         {
236             LeftButton = 0x01,
237             RightButton = 0x02,
238             Cancel = 0x03,
239             MiddleButton = 0x04,
240             ExtraButton1 = 0x05,
241             ExtraButton2 = 0x06,
242             Back = 0x08,
243             Tab = 0x09,
244             Clear = 0x0C,
245             Return = 0x0D,
246             Shift = 0x10,
247             Control = 0x11,
248             Menu = 0x12,
249             Pause = 0x13,
250             Kana = 0x15,
251             Hangeul = 0x15,
252             Hangul = 0x15,
253             Junja = 0x17,
254             Final = 0x18,
255             Hanja = 0x19,
256             Kanji = 0x19,
257             Escape = 0x1B,
258             Convert = 0x1C,
259             NonConvert = 0x1D,
260             Accept = 0x1E,
261             ModeChange = 0x1F,
262             Space = 0x20,
263             Prior = 0x21,
264             Next = 0x22,
265             End = 0x23,
266             Home = 0x24,
267             Left = 0x25,
268             Up = 0x26,
269             Right = 0x27,
270             Down = 0x28,
271             Select = 0x29,
272             Print = 0x2A,
273             Execute = 0x2B,
274             Snapshot = 0x2C,
275             Insert = 0x2D,
276             Delete = 0x2E,
277             Help = 0x2F,
278             N0 = 0x30,
279             N1 = 0x31,
280             N2 = 0x32,
281             N3 = 0x33,
282             N4 = 0x34,
283             N5 = 0x35,
284             N6 = 0x36,
285             N7 = 0x37,
286             N8 = 0x38,
287             N9 = 0x39,
288             A = 0x41,
289             B = 0x42,
290             C = 0x43,
291             D = 0x44,
292             E = 0x45,
293             F = 0x46,
294             G = 0x47,
295             H = 0x48,
296             I = 0x49,
297             J = 0x4A,
298             K = 0x4B,
299             L = 0x4C,
300             M = 0x4D,
301             N = 0x4E,
302             O = 0x4F,
303             P = 0x50,
304             Q = 0x51,
305             R = 0x52,
306             S = 0x53,
307             T = 0x54,
308             U = 0x55,
309             V = 0x56,
310             W = 0x57,
311             X = 0x58,
312             Y = 0x59,
313             Z = 0x5A,
314             LeftWindows = 0x5B,
315             RightWindows = 0x5C,
316             Application = 0x5D,
317             Sleep = 0x5F,
318             Numpad0 = 0x60,
319             Numpad1 = 0x61,
320             Numpad2 = 0x62,
321             Numpad3 = 0x63,
322             Numpad4 = 0x64,
323             Numpad5 = 0x65,
324             Numpad6 = 0x66,
325             Numpad7 = 0x67,
326             Numpad8 = 0x68,
327             Numpad9 = 0x69,
328             Multiply = 0x6A,
329             Add = 0x6B,
330             Separator = 0x6C,
331             Subtract = 0x6D,
332             Decimal = 0x6E,
333             Divide = 0x6F,
334             F1 = 0x70,
335             F2 = 0x71,
336             F3 = 0x72,
337             F4 = 0x73,
338             F5 = 0x74,
339             F6 = 0x75,
340             F7 = 0x76,
341             F8 = 0x77,
342             F9 = 0x78,
343             F10 = 0x79,
344             F11 = 0x7A,
345             F12 = 0x7B,
346             F13 = 0x7C,
347             F14 = 0x7D,
348             F15 = 0x7E,
349             F16 = 0x7F,
350             F17 = 0x80,
351             F18 = 0x81,
352             F19 = 0x82,
353             F20 = 0x83,
354             F21 = 0x84,
355             F22 = 0x85,
356             F23 = 0x86,
357             F24 = 0x87,
358             NumLock = 0x90,
359             ScrollLock = 0x91,
360             NEC_Equal = 0x92,
361             Fujitsu_Jisho = 0x92,
362             Fujitsu_Masshou = 0x93,
363             Fujitsu_Touroku = 0x94,
364             Fujitsu_Loya = 0x95,
365             Fujitsu_Roya = 0x96,
366             LeftShift = 0xA0,
367             RightShift = 0xA1,
368             LeftControl = 0xA2,
369             RightControl = 0xA3,
370             LeftMenu = 0xA4,
371             RightMenu = 0xA5,
372             BrowserBack = 0xA6,
373             BrowserForward = 0xA7,
374             BrowserRefresh = 0xA8,
375             BrowserStop = 0xA9,
376             BrowserSearch = 0xAA,
377             BrowserFavorites = 0xAB,
378             BrowserHome = 0xAC,
379             VolumeMute = 0xAD,
380             VolumeDown = 0xAE,
381             VolumeUp = 0xAF,
382             MediaNextTrack = 0xB0,
383             MediaPrevTrack = 0xB1,
384             MediaStop = 0xB2,
385             MediaPlayPause = 0xB3,
386             LaunchMail = 0xB4,
387             LaunchMediaSelect = 0xB5,
388             LaunchApplication1 = 0xB6,
389             LaunchApplication2 = 0xB7,
390             OEM1 = 0xBA,
391             OEMPlus = 0xBB,
392             OEMComma = 0xBC,
393             OEMMinus = 0xBD,
394             OEMPeriod = 0xBE,
395             OEM2 = 0xBF,
396             OEM3 = 0xC0,
397             OEM4 = 0xDB,
398             OEM5 = 0xDC,
399             OEM6 = 0xDD,
400             OEM7 = 0xDE,
401             OEM8 = 0xDF,
402             OEMAX = 0xE1,
403             OEM102 = 0xE2,
404             ICOHelp = 0xE3,
405             ICO00 = 0xE4,
406             ProcessKey = 0xE5,
407             ICOClear = 0xE6,
408             Packet = 0xE7,
409             OEMReset = 0xE9,
410             OEMJump = 0xEA,
411             OEMPA1 = 0xEB,
412             OEMPA2 = 0xEC,
413             OEMPA3 = 0xED,
414             OEMWSCtrl = 0xEE,
415             OEMCUSel = 0xEF,
416             OEMATTN = 0xF0,
417             OEMFinish = 0xF1,
418             OEMCopy = 0xF2,
419             OEMAuto = 0xF3,
420             OEMENLW = 0xF4,
421             OEMBackTab = 0xF5,
422             ATTN = 0xF6,
423             CRSel = 0xF7,
424             EXSel = 0xF8,
425             EREOF = 0xF9,
426             Play = 0xFA,
427             Zoom = 0xFB,
428             Noname = 0xFC,
429             PA1 = 0xFD,
430             OEMClear = 0xFE
431         }
432         [DllImport("kernel32.dll")]
GetFileType(IntPtr hdl)433         protected static extern FileType GetFileType(IntPtr hdl);
434         [DllImport("kernel32.dll")]
GetStdHandle(StdHandle std)435         protected static extern IntPtr GetStdHandle(StdHandle std);
436         [DllImport("Kernel32.dll")]
SetConsoleMode(IntPtr hConsoleHandle, uint dwMode)437         protected static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode);
438         [DllImport("Kernel32.dll")]
GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode)439         protected static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode);
440         [DllImport("Kernel32.dll")]
GetLastError()441         protected static extern uint GetLastError();
442         [DllImport("kernel32.dll")]
GetNumberOfConsoleInputEvents(IntPtr hConsoleInput, out uint lpcNumberOfEvents)443         protected static extern bool GetNumberOfConsoleInputEvents(IntPtr hConsoleInput, out uint lpcNumberOfEvents);
444         [DllImport("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
ReadConsoleInput(IntPtr hConsoleInput, [Out] INPUT_RECORD[] lpBuffer, uint nLength, out uint lpNumberOfEventsRead)445         protected static extern bool ReadConsoleInput(IntPtr hConsoleInput, [Out] INPUT_RECORD[] lpBuffer, uint nLength, out uint lpNumberOfEventsRead);
446 
447         #endregion
448 
449 
KeyboardInputProcessorBase(Encoding targetEncoding)450         public KeyboardInputProcessorBase(Encoding targetEncoding)
451         {
452             if (targetEncoding == null)
453             {
454                 throw new ArgumentNullException("targetEncoding");
455             }
456 
457             this.targetEncoding = targetEncoding;
458         }
459 
Abort()460         public void Abort()
461         {
462             this.bAborted = true;
463             wait_handle.Set();
464 
465             // Wait for the input_thread to finish
466             try
467             {
468                 if (input_thread != null)
469                 {
470                     bool success = input_thread.Join(THREAD_JOIN_TIMEOUT_MSECONDS);
471                     if (!success)
472                     {
473                         this.input_thread.Abort();
474                     }
475                 }
476             }
477             catch (ThreadStateException)
478             {
479                 // Thread had already ended.
480             }
481             catch (ThreadInterruptedException)
482             {
483                 // Main application thread (the one calling Abort) is being killed.
484             }
485             catch (ThreadAbortException)
486             {
487                 // Exception when calling abort.
488             }
489         }
490 
EnableDKInput()491         public void EnableDKInput()
492         {
493             if (this.enableDKInput == true)
494             {
495                 // Nothing to do. This is the case when the DK Console message came after the internal timeout to
496                 // enable dk console expired, which set the variable already.
497                 return;
498             }
499 
500             lock (this.dk_input_buffer.SyncRoot)
501             {
502                 if (this.enableDKInput == false)
503                 {
504                     if (this.dk_input_count > 0)
505                     {
506                         // Flush buffer
507                         FlushDKBuffer();
508                     }
509 
510                     // Disabling buffering
511                     this.enableDKInput = true;
512                 }
513             }
514         }
515 
BufferOrDiscardDKInput(ref byte[] buffer, int bytesToBuffer)516         protected void BufferOrDiscardDKInput(ref byte[] buffer, int bytesToBuffer)
517         {
518             lock (this.dk_input_buffer.SyncRoot)
519             {
520                 if (this.enableDKInput == false)
521                 {
522                     // Check if the time has expired
523                     if (this.startTime.AddSeconds(DK_READY_TIMEOUT).CompareTo(DateTime.Now) < 0)
524                     {
525                         // Flush buffer
526                         Console.WriteLine(DK_CONSOLE_EXPIRE_MESSAGE, DK_READY_TIMEOUT);
527                         this.FlushDKBuffer();
528                         this.sendBuffer(ref buffer, 0, bytesToBuffer);
529                         this.enableDKInput = true;
530                     }
531                     else
532                     {
533                         // Only keep buffering if there is space
534                         if (this.dk_input_count + bytesToBuffer >= DK_INPUT_BUFFER_MAX_SIZE)
535                         {
536                             Console.WriteLine("cattoucan : input '{0}' discarded!", buffer);
537                         }
538                         else
539                         {
540                             // there is space, keep buffering
541                             buffer.CopyTo(this.dk_input_buffer, this.dk_input_count);
542                             this.dk_input_count += bytesToBuffer;
543                         }
544                     }
545                 }
546             }
547         }
548 
FlushDKBuffer()549         protected void FlushDKBuffer()
550         {
551             // Check if there is something to flush.
552             if (this.dk_input_count > 0)
553             {
554                 try
555                 {
556                     this.sendBuffer(ref this.dk_input_buffer, 0, this.dk_input_count);
557                 }
558                 catch (IOException)
559                 {
560                     // Network must be shutting down
561                 }
562 
563                 // Clean up the buffer
564                 Array.Clear(this.dk_input_buffer, 0, this.dk_input_count);
565                 this.dk_input_count = 0;
566             }
567         }
568 
sendBuffer(ref byte[] byteBuffer, int start, int size)569         protected void sendBuffer(ref byte[] byteBuffer, int start, int size)
570         {
571             //Replace '\13' for '\10'
572             for (int counter = start; counter < (start + size); ++counter)
573             {
574                 if (byteBuffer[counter] == 13)
575                 {
576                     byteBuffer[counter] = 10;
577                 }
578             }
579 
580             stream.Write(byteBuffer, start, size);
581         }
582     }
583 
584     public class InputThread : KeyboardInputProcessorBase
585     {
586         TcpClient tcp_client;
587         int bytes_read;
588 
InputThread(TcpClient tcp)589         public InputThread(TcpClient tcp)
590             : base(Encoding.Default)
591         {
592             tcp_client = tcp;
593             stream = tcp.GetStream();
594             input_stream = null;
595             input_thread = null;
596         }
597 
InputThread(TcpClient tcp, Stream input, Encoding targetEncoding)598         public InputThread(TcpClient tcp, Stream input, Encoding targetEncoding)
599             : base(targetEncoding)
600         {
601             tcp_client = tcp;
602             input_stream = input;
603             stream = tcp.GetStream();
604             input_thread = new Thread(input_thread_proc);
605             wait_handle = new EventWaitHandle(false, EventResetMode.AutoReset);
606             input_thread.Start();
607         }
608 
read_callback(IAsyncResult ar)609         private void read_callback(IAsyncResult ar)
610         {
611             try
612             {
613                 bytes_read = input_stream.EndRead(ar);
614             }
615             catch (IOException)
616             {
617                 Thread.Sleep(100);
618                 bytes_read = 0;
619             }
620 
621             wait_handle.Set();
622         }
623 
input_thread_proc()624         private void input_thread_proc()
625         {
626             byte[] buffer = new byte[256];
627             this.startTime = DateTime.Now;
628             while (!this.bAborted)
629             {
630                 try
631                 {
632                     input_stream.BeginRead(buffer, 0, buffer.Length, read_callback, null);
633                     wait_handle.WaitOne();
634 
635                     if ((this.bytes_read != 0) && (!this.bAborted))
636                     {
637                         //IMPORTANT  Do not encode to this stream. Just send the bytes directly.
638                         if (this.enableDKInput == true)
639                         {
640                             this.sendBuffer(ref buffer, 0, this.bytes_read);
641                         }
642                         else // not enabled yet, should keep buffering
643                         {
644                             lock (this.dk_input_buffer.SyncRoot)
645                             {
646                                 if (this.enableDKInput == false)
647                                 {
648                                     // Check if the time has expired
649                                     if (startTime.AddSeconds(DK_READY_TIMEOUT).CompareTo(DateTime.Now) < 0)
650                                     {
651                                         // Flush buffer
652                                         Console.WriteLine(DK_CONSOLE_EXPIRE_MESSAGE, DK_READY_TIMEOUT);
653                                         this.FlushDKBuffer();
654                                         this.sendBuffer(ref buffer, 0, this.bytes_read);
655                                         this.enableDKInput = true;
656                                     }
657                                     else
658                                     {
659                                         // Only keep buffering if there is space
660                                         this.BufferOrDiscardDKInput(ref buffer, this.bytes_read);
661                                     }
662                                 } // enableDKInput == false (check inside lock)
663                             } // lock
664                         } // enableDKInput == false
665 
666                         Array.Clear(buffer, 0, this.bytes_read);
667                     }// has bytes and not aborted
668                 }
669                 catch (ThreadAbortException)
670                 {
671                     return;
672                 }
673                 catch (IOException)
674                 {
675                     return;
676                 }
677             } // while not aborted
678         }
679     }
680 
681     public class ConsoleInputNative : KeyboardInputProcessorBase
682     {
ConsoleInputNative(TcpClient tcp, Encoding targetEncoding)683         public ConsoleInputNative(TcpClient tcp, Encoding targetEncoding)
684             : base(targetEncoding)
685         {
686             stream = tcp.GetStream();
687             input_thread = new Thread(input_thread_proc);
688             input_thread.Start();
689         }
690 
input_thread_proc()691         private void input_thread_proc()
692         {
693             IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
694             IntPtr handle = GetStdHandle(StdHandle.Stdin);
695             uint mode;
696 
697             if (handle == IntPtr.Zero || handle == INVALID_HANDLE_VALUE)
698             {
699                 Console.WriteLine("cafex : unable to initialize keyboard input!");
700                 return;
701             }
702 
703             if (!GetConsoleMode(handle, out mode))
704             {
705                 Console.WriteLine("cafex : unable to initialize keyboard input!");
706                 return;
707             }
708 
709             mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT);
710 
711             if (!SetConsoleMode(handle, mode))
712             {
713                 Console.WriteLine("cafex : unable to initialize keyboard input!");
714                 return;
715             }
716 
717             uint numEvents = 0;
718             uint numEventsRead = 0;
719             this.startTime = DateTime.Now;
720 
721             while (!bAborted)
722             {
723                 GetNumberOfConsoleInputEvents(handle, out numEvents);
724                 if (numEvents != 0)
725                 {
726                     INPUT_RECORD[] eventBuffer = new INPUT_RECORD[128]; ;
727                     ReadConsoleInput(handle, eventBuffer, 128, out numEventsRead);
728 
729                     for (int eventCounter = 0; eventCounter < numEventsRead; ++eventCounter)
730                     {
731                         INPUT_RECORD eventReceived = eventBuffer[eventCounter];
732                         {
733                             if (eventReceived.EventType == KEY_EVENT &&
734                                 eventReceived.KeyEvent.bKeyDown)
735                             {
736                                 byte[] byteBuffer = null;
737                                 bool handled = false;
738 
739                                 // Check for arrows first
740                                 switch (eventReceived.KeyEvent.wVirtualKeyCode)
741                                 {
742                                     case (ushort)VirtualKeys.Escape:
743                                         byteBuffer = ESCAPE_BYTES;
744                                         handled = true;
745                                         break;
746                                     case (ushort)VirtualKeys.Up:
747                                         byteBuffer = UP_ARROW_BYTES;
748                                         handled = true;
749                                         break;
750                                     case (ushort)VirtualKeys.Down:
751                                         byteBuffer = DOWN_ARROW_BYTES;
752                                         handled = true;
753                                         break;
754                                     case (ushort)VirtualKeys.Left:
755                                         byteBuffer = LEFT_ARROW_BYTES;
756                                         handled = true;
757                                         break;
758                                     case (ushort)VirtualKeys.Right:
759                                         byteBuffer = RIGHT_ARROW_BYTES;
760                                         handled = true;
761                                         break;
762                                     case (ushort)VirtualKeys.Home:
763                                         byteBuffer = HOME_BYTES;
764                                         handled = true;
765                                         break;
766                                     case (ushort)VirtualKeys.End:
767                                         byteBuffer = END_BYTES;
768                                         handled = true;
769                                         break;
770                                     case (ushort)VirtualKeys.Insert:
771                                         byteBuffer = INSERT_BYTES;
772                                         handled = true;
773                                         break;
774                                     case (ushort)VirtualKeys.Delete:
775                                         byteBuffer = DELETE_BYTES;
776                                         handled = true;
777                                         break;
778                                     case (ushort)VirtualKeys.Prior:
779                                         byteBuffer = PG_UP_BYTES;
780                                         handled = true;
781                                         break;
782                                     case (ushort)VirtualKeys.Next:
783                                         byteBuffer = PG_DOWN_BYTES;
784                                         handled = true;
785                                         break;
786 									case (ushort)VirtualKeys.F1:
787 										byteBuffer = F1_BYTES;
788 										handled = true;
789 										break;
790 									case (ushort)VirtualKeys.F2:
791 										byteBuffer = F2_BYTES;
792 										handled = true;
793 										break;
794 									case (ushort)VirtualKeys.F3:
795 										byteBuffer = F3_BYTES;
796 										handled = true;
797 										break;
798 									case (ushort)VirtualKeys.F4:
799 										byteBuffer = F4_BYTES;
800 										handled = true;
801 										break;
802 									case (ushort)VirtualKeys.F5:
803 										byteBuffer = F5_BYTES;
804 										handled = true;
805 										break;
806 									case (ushort)VirtualKeys.F6:
807 										byteBuffer = F6_BYTES;
808 										handled = true;
809 										break;
810 									case (ushort)VirtualKeys.F7:
811 										byteBuffer = F7_BYTES;
812 										handled = true;
813 										break;
814 									case (ushort)VirtualKeys.F8:
815 										byteBuffer = F8_BYTES;
816 										handled = true;
817 										break;
818 									case (ushort)VirtualKeys.F9:
819 										byteBuffer = F9_BYTES;
820 										handled = true;
821 										break;
822 									case (ushort)VirtualKeys.F10:
823 										byteBuffer = F10_BYTES;
824 										handled = true;
825 										break;
826 									case (ushort)VirtualKeys.F11:
827 										byteBuffer = F11_BYTES;
828 										handled = true;
829 										break;
830 									case (ushort)VirtualKeys.F12:
831 										byteBuffer = F12_BYTES;
832 										handled = true;
833 										break;
834 								}
835 
836                                 // Check for Unicode chars then
837                                 if (!handled && eventReceived.KeyEvent.UnicodeChar != 0)
838                                 {
839                                     // prepare the data.
840                                     byteBuffer = Encoding.Convert(this.unicodeEncoding, this.utf8Encoding, this.unicodeEncoding.GetBytes(new char[] { eventReceived.KeyEvent.UnicodeChar }));
841                                 }
842 
843                                 if (byteBuffer != null && byteBuffer.Length > 0)
844                                 {
845                                     if (this.enableDKInput)
846                                     {
847                                         // Can send right now
848                                         this.sendBuffer(ref byteBuffer, 0, byteBuffer.Length);
849                                     }
850                                     else
851                                     {
852                                         // Need to buffer
853                                         this.BufferOrDiscardDKInput(ref byteBuffer, byteBuffer.Length);
854                                     }
855                                 }
856                             }
857                         }
858                     }
859                 }
860 
861                 Thread.Sleep(KEY_POOLING_WAIT_MSECONDS);
862             }
863 
864         }
865     }
866 
867     public class CatToucan
868     {
869         private const double DEBUG_PORT_CONNECTION_TIMEOUT = 30.0; // 30s
870 
871         private Stream input_stream = null;
872         private bool exclusive_match = false;
873         private TcpClient tcp = null;
874         private Semaphore wait_handle = new Semaphore(0, 2);
875         private int bytes_read = 0;
876         private IPEndPoint m_portcombo_to_listen_to = new IPEndPoint(IPAddress.Loopback, 6001);
877         private StringBuilder m_string_builder = new StringBuilder();
878         private KeyboardInputProcessorBase input_thread = null;
879         private Encoding utf8Encoding = new UTF8Encoding();
880         private List<KeyValuePair<string, ManualResetEvent>> matchNotify = new List<KeyValuePair<string, ManualResetEvent>>();
881         private List<KeyValuePair<string, Func<string, bool>>> matchCallback = new List<KeyValuePair<string, Func<string, bool>>>();
882         private ParserState currentState = ParserState.NormalParsing;
883 
884         public static class Terms
885         {
886             public static string IOP_START = "USB Trace: Activating root hubs";
887             public static string PPC_START = "PPC Init";
888             public static string PPC_SHUTDOWN_COMPLETED = "***PPC SHUTDOWN COMPLETED***";
889             public static string VPAD_INIT_END = "VPAD Init end";
890             public static string PPC_FAST_CYCLE = "***PPC FAST CYCLE***";
891             public static string DEVICE_REMOTE_COMMANDS = "Device remote commands:";
892             public static string KI_PIREPARE_TITLE_ASYNC = "DIAG:Issued KiPrepareTitleAsync";
893             public static string SYSTEM_BUSY = "System busy, try again";
894             public static string DK_READY_MESSAGE = "[+-* DK is ready for console input *-+]";
895             public static string MENU_SOURCE = "source -p -q /vol/content";
896             public static string COS_LAUNCH = "cos launch 0x";
897         }
898 
899         public class ExitReason
900         {
901             public enum Reason
902             {
903                 StoppedByUser,
904                 StoppedDueToDroppedConnection,
905                 StoppedByApplicationExit,
906                 StoppedByWaitingForDebugger,
907                 StoppedByAlternateTermination,
908                 NoExit
909             }
910             public Reason reason;
911             public Int32 AppExitCode;
912 
ExitReason(Reason r ,Int32 app_exit_code)913             public ExitReason(Reason r ,Int32 app_exit_code)
914             {
915                 reason = r;
916                 AppExitCode = app_exit_code;
917             }
918         }
919 
920         /// <summary>
921         /// Uses the default portcombo of localhost:6001
922         /// </summary>
923 
CatToucan(int port)924         public CatToucan(int port) :
925             this(new IPEndPoint(IPAddress.Loopback, port))
926         {
927         }
928 
929         /// <summary>
930         /// Uses the specified portcombo.
931         /// </summary>
CatToucan(IPEndPoint character_stream_server_location)932         public CatToucan(IPEndPoint character_stream_server_location)
933         {
934             if (character_stream_server_location == null)
935             {
936                 m_portcombo_to_listen_to = new IPEndPoint(IPAddress.Loopback, 6001);
937             }
938             else
939             {
940                 m_portcombo_to_listen_to = character_stream_server_location;
941             }
942         }
943 
944         public bool ExclusiveMatch
945         {
946             get
947             {
948                 return exclusive_match;
949             }
950 
951             set
952             {
953                 exclusive_match = value;
954             }
955         }
956 
957         public int Port
958         {
959             get
960             {
961                 return m_portcombo_to_listen_to.Port;
962             }
963         }
964 
~CatToucan()965         ~CatToucan()
966         {
967             wait_handle.Close();
968             wait_handle = null;
969         }
970 
971         /// <summary>
972         /// Contains the application return value, if any.
973         /// </summary>
974         public int? ReturnCodeThatWeSnooped { get; private set; }
975 
976         /// <summary>
977         /// Adds an event that will be set if 'match' string is found in the output stream.
978         /// </summary>
979         /// <param name="kvp">The string and event key-value pair to add to the list</param>
AddNotification(KeyValuePair<string, ManualResetEvent> kvp)980         public void AddNotification(KeyValuePair<string, ManualResetEvent> kvp)
981         {
982             matchNotify.Add(kvp);
983         }
984 
985         /// <summary>
986         /// Adds a callback that will be executed if 'match' string is found in the output stream.
987         /// </summary>
988         /// <param name="kvp">The string and callback key-value pair to add to the list</param>
AddNotification(KeyValuePair<string, Func<string, bool>> kvp)989         public void AddNotification(KeyValuePair<string, Func<string, bool>> kvp)
990         {
991             int already = matchCallback.IndexOf(kvp);
992             if(already >= 0)
993             {
994 #if DEBUG
995                 CafeX.Log.WriteLine("Overriding existing handler for '{0}'!", kvp.Key);
996 #endif
997                 matchCallback.RemoveAt(already);
998             }
999             matchCallback.Add(kvp);
1000         }
1001 
1002         /// <summary>
1003         /// Clear all of the string match events
1004         /// </summary>
ClearNotifications()1005         public void ClearNotifications()
1006         {
1007             matchNotify.Clear();
1008             matchCallback.Clear();
1009         }
1010 
read_callback(IAsyncResult ar)1011         private void read_callback(IAsyncResult ar)
1012         {
1013             try
1014             {
1015                 try
1016                 {
1017                     int read = input_stream.EndRead(ar);
1018                     lock (wait_handle)
1019                     {
1020                         if (bytes_read >= 0)
1021                         {
1022                             bytes_read = read;
1023                         }
1024                     }
1025                 }
1026                 catch (IOException)
1027                 {
1028                     lock (wait_handle)
1029                     {
1030                         if (bytes_read > 0)
1031                         {
1032                             bytes_read = 0;
1033                         }
1034                     }
1035 
1036                     if (bytes_read < 0)
1037                     {
1038                         return;
1039                     }
1040 
1041                     Thread.Sleep(100);
1042                 }
1043                 wait_handle.Release();
1044             }
1045             catch (ObjectDisposedException)
1046             {
1047             }
1048         }
1049 
StopRelay()1050         public void StopRelay()
1051         {
1052             lock (wait_handle)
1053             {
1054                 bytes_read = -1;
1055             }
1056             wait_handle.Release();
1057         }
1058 
1059         /// <summary>
1060         /// Monitors the portcombo and relays the data to the specified output stream.
1061         /// </summary>
RelaySerialCharacters(TextWriter output_stream, bool timeout)1062         public ExitReason RelaySerialCharacters(TextWriter output_stream, bool timeout)
1063         {
1064             // Uncomment this line to prevent a timeout.
1065             // timeout = false;
1066 
1067             tcp = new TcpClient();
1068 
1069             // connection loop
1070             {
1071 
1072                 DateTime now = DateTime.Now;
1073                 DateTime give_up = now.AddSeconds(DEBUG_PORT_CONNECTION_TIMEOUT);
1074 
1075                 while (tcp.Connected == false)
1076                 {
1077                     try
1078                     {
1079                         tcp.Connect(m_portcombo_to_listen_to);
1080                     }
1081                     catch
1082                     {
1083                         if (DateTime.Now > give_up)
1084                         {
1085                             return new ExitReason(ExitReason.Reason.StoppedDueToDroppedConnection,0);
1086                         }
1087                     }
1088                 }
1089             }
1090 
1091             if (Program.no_console)
1092             {
1093                 // Don't use the console stdin because we told not to
1094                 input_thread = (KeyboardInputProcessorBase)(new InputThread(tcp));
1095             }
1096             else if (CafeX.Program.IsInputRedirected)
1097             {
1098                 // Can't use the ReadKeys stream because the console output is being redirected
1099                 input_thread = (KeyboardInputProcessorBase)(new InputThread(tcp, Console.OpenStandardInput(), Console.InputEncoding));
1100             }
1101             else
1102             {
1103                 // Can use the ReadKeys enabled stream
1104                 input_thread = (KeyboardInputProcessorBase)(new ConsoleInputNative(tcp, Console.InputEncoding));
1105             }
1106 
1107             const int k_max_bytes_to_read = 128;
1108             byte[] holding_pool = new byte[k_max_bytes_to_read];
1109 
1110             this.input_stream = tcp.GetStream();
1111 
1112             ExitReason reason_for_remotely_initiated_stop = new ExitReason(ExitReason.Reason.NoExit,0);
1113 
1114             int read;
1115             while ((read = bytes_read) >= 0 && tcp.Connected)
1116             {
1117                 try
1118                 {
1119                     input_stream.BeginRead(holding_pool, 0, k_max_bytes_to_read, read_callback, null);
1120                 }
1121                 catch (IOException)
1122                 {
1123                     break;
1124                 }
1125 
1126                 wait_handle.WaitOne();
1127 
1128                 read = bytes_read;
1129                 if (read == 0)
1130                 {
1131                     Thread.Sleep(100);
1132                     continue;
1133                 }
1134 
1135                 if (read < 0)
1136                 {
1137                     break;
1138                 }
1139 
1140                 reason_for_remotely_initiated_stop = consume_raw_bytes(holding_pool, read, output_stream, exclusive_match);
1141                 if (reason_for_remotely_initiated_stop.reason != ExitReason.Reason.NoExit)
1142                 {
1143 #if DEBUG
1144                     CafeX.Log.WriteLine("#############  CLOSING TCP CONNECTION WITH CONSOLE ON PORT " + m_portcombo_to_listen_to.Port.ToString() + "#############");
1145 #endif
1146                     break;
1147                 }
1148 
1149                 // Clear buffer for the next read
1150                 Array.Clear(holding_pool, 0, holding_pool.Length);
1151             }
1152 
1153             input_thread.Abort();
1154             input_stream.Close();
1155             tcp.Close();
1156 
1157             if (read < 0)
1158             {
1159                 reason_for_remotely_initiated_stop.reason = ExitReason.Reason.StoppedByAlternateTermination;
1160                 reason_for_remotely_initiated_stop.AppExitCode = 0;
1161             }
1162             else if (reason_for_remotely_initiated_stop.reason == ExitReason.Reason.NoExit)
1163             {
1164                 reason_for_remotely_initiated_stop.reason = ExitReason.Reason.StoppedDueToDroppedConnection;
1165                 reason_for_remotely_initiated_stop.AppExitCode = 0;
1166             }
1167 
1168             return reason_for_remotely_initiated_stop;
1169         }
1170 
EnableDKConsoleInput()1171         private void EnableDKConsoleInput()
1172         {
1173             if (input_thread != null)
1174             {
1175                 input_thread.EnableDKInput();
1176             }
1177         }
1178 
1179         private string[] cafeEventList = new string[]
1180         {
1181             Terms.IOP_START,                       //IOP Start
1182             Terms.PPC_START,                       //PPC Start
1183             Terms.PPC_SHUTDOWN_COMPLETED,          //PPC Shutdown
1184             Terms.VPAD_INIT_END,                   //String to find the PID
1185             Terms.PPC_FAST_CYCLE,                  //Used only for Fast Relaunch
1186         };
1187         private static DateTime evtProfile;
1188         private static int cafeProfile = -1;
1189         private const int thousand = 1000;
1190         private const int million = 1000000;
1191 
TrimSpecial(string input, char seperator)1192         private string TrimSpecial(string input, char seperator)
1193         {
1194             if (!string.IsNullOrEmpty(input))
1195             {
1196                 StringBuilder sb = new StringBuilder();
1197                 foreach (char c in input)
1198                 {
1199                     if (c != seperator)
1200                     {
1201                         sb.Append(c);
1202                     }
1203                 }
1204                 return sb.ToString();
1205             }
1206             return null;
1207         }
1208 
UpdateEventTimes(string newTime)1209         private void UpdateEventTimes(string newTime)
1210         {
1211             int temp = Convert.ToInt32(newTime);
1212 
1213             int diff = temp - CatToucan.cafeProfile;
1214             CatToucan.cafeProfile = temp;
1215 
1216             int sec = 0;
1217             int msec = (diff % million) / thousand;
1218             if (diff > million)
1219             {
1220                 sec = (int)(diff / million);
1221             }
1222 
1223             TimeSpan ts = new TimeSpan(0, 0, 0, sec, msec);
1224             CatToucan.evtProfile = CatToucan.evtProfile.Add(ts);
1225         }
1226 
HandleCafeEvents(string cafeEvent)1227         private bool HandleCafeEvents(string cafeEvent)
1228         {
1229             bool isValid = false;
1230             if (Program.CAFE_PROFILE.value != null && Program.CAFE_PROFILE.value == "1")
1231             {
1232                 int i = -1;
1233 
1234                 foreach (string str in cafeEventList)
1235                 {
1236                     i++;
1237                     if (cafeEvent.Contains(str))
1238                     {
1239                         isValid = true;
1240                         break;
1241                     }
1242                 }
1243 
1244                 if (isValid == true)
1245                 {
1246                     switch (i)
1247                     {
1248                         case 0:    //IOP Start
1249                         case 4:    //Fast Relaunch
1250                             {
1251                                 //Log the current PC Time
1252                                 CatToucan.evtProfile = DateTime.Now;  //Log the current PC Time
1253 
1254                                 if (i == 0)
1255                                 {
1256                                     //Log the current CAFE time
1257                                     string temp = cafeEvent.Substring(0, 12);
1258                                     temp = this.TrimSpecial(temp, ':');
1259                                     CatToucan.cafeProfile = Convert.ToInt32(temp) * thousand;
1260                                     CafeXEventtLog.Instance.WriteToEventLog(522, CatToucan.evtProfile, EventStatus.BEGIN, EventProcess.IOP, "IOP STARTED: " + CatToucan.cafeProfile);
1261                                 }
1262                                 else
1263                                 {
1264                                     CatToucan.cafeProfile = 0;
1265                                     CafeXEventtLog.Instance.WriteToEventLog(582, CatToucan.evtProfile, EventStatus.BEGIN, EventProcess.IOP, "***PPC FAST CYCLE***: " + CatToucan.cafeProfile);
1266                                 }
1267                             }
1268                             break;
1269                         case 1:    //PPC Start
1270                             {
1271                                 //If the value is more than 0, then it means system started as cold boot
1272                                 //Update the timings
1273                                 if (CatToucan.cafeProfile > 0)
1274                                 {
1275                                     string temp = cafeEvent.Substring(cafeEvent.LastIndexOf("/") + 1);
1276                                     temp = temp.Substring(0, temp.LastIndexOf(")"));
1277                                     UpdateEventTimes(temp);
1278                                     CafeXEventtLog.Instance.WriteToEventLog(532, CatToucan.evtProfile, EventStatus.BEGIN, EventProcess.PPC, "PPC STARTED: " + CatToucan.cafeProfile);
1279                                 }
1280                                 else
1281                                 {
1282                                     CafeXEventtLog.Instance.WriteToEventLog(532, DateTime.Now, EventStatus.BEGIN, EventProcess.PPC, "PPC STARTED");
1283                                 }
1284                             }
1285                             break;
1286                         case 2:    //PPC Shutdown
1287                             CafeXEventtLog.Instance.WriteToEventLog(512, DateTime.Now, EventStatus.END, EventProcess.PPC, "***PPC SHUTDOWN COMPLETED***");
1288                             break;
1289                         case 3:    //App Start/Launch
1290                             {
1291                                 string pidStr = cafeEvent.Substring(cafeEvent.LastIndexOf('=') + 1).Trim();
1292                                 int currentPID = Convert.ToInt32(pidStr);
1293                                 if (currentPID == 2 || currentPID == 15)
1294                                 {
1295                                     string temp = cafeEvent.Substring(cafeEvent.LastIndexOf("/") + 1);
1296                                     temp = temp.Substring(0, temp.LastIndexOf(")"));
1297                                     UpdateEventTimes(temp);
1298                                     CafeXEventtLog.Instance.WriteToEventLog(542, DateTime.Now, EventStatus.BEGIN, EventProcess.TITLE, "Title Launched: " + CatToucan.cafeProfile);
1299                                 }
1300                             }
1301                             break;
1302                     }
1303                 }
1304             }
1305             return isValid;
1306         }
1307 
1308         //states to dictate how consume_raw_bytes will parse data
1309         private enum ParserState : uint
1310         {
1311             NormalParsing,
1312             EscapeSequenceParsing,
1313             CommandSequenceParsing,
1314             PerformanceTimerParsing_Boot1
1315         };
1316 
consume_raw_bytes(byte[] holding_pool, int holding_bytes, TextWriter output_stream, bool exclusive_match)1317         private ExitReason consume_raw_bytes(byte[] holding_pool, int holding_bytes, TextWriter output_stream, bool exclusive_match)
1318         {
1319             byte[] outBytes = null;
1320             char[] outChars = null;
1321 
1322             ExitReason reason = new ExitReason(ExitReason.Reason.NoExit, 0);
1323 
1324             if (!String.IsNullOrEmpty(Program.CAFE_OUTPUT_ENCODING.value))
1325             {
1326                 outBytes = Encoding.Convert(Program.cafeOutEncoding, output_stream.Encoding, holding_pool, 0, holding_bytes);
1327             }
1328             else //user didn't specify a language, so we'll just assume UTF-8
1329             {
1330                 char[] utf8Chars = this.utf8Encoding.GetChars(holding_pool, 0, holding_bytes); //1)this.utf8Encoding.GetChars(holding_pool); then 2)output_stream.Encoding.GetChars(holding_pool);
1331                 byte[] utf8Bytes = this.utf8Encoding.GetBytes(utf8Chars);
1332 
1333                 outBytes = Encoding.Convert(this.utf8Encoding, output_stream.Encoding, utf8Bytes);
1334             }
1335 
1336             outChars = output_stream.Encoding.GetChars(outBytes);
1337 
1338             foreach (char next_char in outChars)
1339             {
1340                 if (currentState == ParserState.EscapeSequenceParsing)
1341                 {
1342                     if (next_char == '[') // user is issuing some other command
1343                     {
1344                         currentState = ParserState.CommandSequenceParsing;
1345                         continue;
1346                     }
1347                     else if (next_char == 'c') // user wants to clear the terminal
1348                     {
1349                         Console.Clear();
1350                         currentState = ParserState.NormalParsing;
1351                         continue;
1352                     }
1353                 }
1354                 else if (currentState == ParserState.CommandSequenceParsing)
1355                 {
1356                     switch (next_char)
1357                     {
1358                         case 'H':
1359                         {
1360                             Console.SetCursorPosition(0, 0);
1361                             currentState = ParserState.NormalParsing;
1362                             break;
1363                         }
1364                         default: //should never get here
1365                         {
1366                             currentState = ParserState.NormalParsing;
1367                             break;
1368                         }
1369                     }
1370                     continue;
1371                 }
1372 
1373                 UnicodeCategory x = char.GetUnicodeCategory(next_char);
1374 
1375                 // ignore most of the characters in this class.
1376                 if (x == UnicodeCategory.Control)
1377                 {
1378 
1379                     //If the parser has hit a newline character, print what we've consumed so far.
1380                     //Then check to see if we need to do something further based on the output
1381                     if (next_char == '\n' || next_char == '\r')
1382                     {
1383                         string thing = m_string_builder.ToString();
1384                         output_stream.WriteLine();
1385 
1386 #if DEBUG
1387                         CafeX.Log.WriteLineRaw("Line: " + thing);
1388 #endif
1389 
1390                         // Clears string buffer used for storage before parsing
1391                         m_string_builder = new StringBuilder();
1392 
1393 #if TIMER
1394                         //We want to time up until we stop seeing BOOT1 messages.
1395                         if (thing.StartsWith("BOOT1:"))
1396                         {
1397                             currentState = ParserState.PerformanceTimerParsing_Boot1;
1398                             CafeX.Program.CafeXSetupTime.Report();
1399                         }
1400 
1401                         if (currentState == ParserState.PerformanceTimerParsing_Boot1 && !thing.StartsWith("BOOT1:"))
1402                         {
1403                             CafeX.Program.CafeXSetupTime.Report();
1404                             currentState = ParserState.NormalParsing;
1405                         }
1406 
1407                         if (thing.StartsWith(Terms.VPAD_INIT_END))
1408                         {
1409                             if (thing.Contains("PID=15") ||
1410                                (CafeX.Program.command == "on" && !CafeX.Program.IsBooting && thing.Contains("PID=2")))
1411                             {
1412                                 CafeX.Program.CafeXSetupTime.Stop();
1413                             }
1414                             else
1415                             {
1416                                 CafeX.Program.CafeXSetupTime.Report();
1417                             }
1418                         }
1419 #endif
1420                         if (cattoucan.sync != SyncStage.None)
1421                         {
1422                             if (cattoucan.sync == SyncStage.TitleSoft)
1423                             {
1424                                 if (thing.Contains(Terms.KI_PIREPARE_TITLE_ASYNC))
1425                                 {
1426                                     cattoucan.SignalTitleEvent(SyncStage.None);
1427                                 }
1428                                 else
1429                                 {
1430                                     cattoucan.SignalTitleEvent(SyncStage.TitleSoft);
1431                                 }
1432                             }
1433                             else if (cattoucan.sync == SyncStage.TitleFast)
1434                             {
1435                                 if (thing.Contains(Terms.COS_LAUNCH))
1436                                 {
1437                                     cattoucan.SignalTitleEvent(SyncStage.None);
1438                                 }
1439                                 else if (thing.Contains(Terms.SYSTEM_BUSY))
1440                                 {
1441                                     cattoucan.SignalTitleEvent(SyncStage.Busy);
1442                                 }
1443                                 else
1444                                 {
1445                                     cattoucan.SignalTitleEvent(SyncStage.TitleFast);
1446                                 }
1447                             }
1448                             else if (cattoucan.sync == SyncStage.Boot)
1449                             {
1450                                 if (thing.Contains(Terms.DEVICE_REMOTE_COMMANDS))
1451                                 {
1452                                     cattoucan.SignalBootEvent();
1453                                 }
1454                             }
1455                             else if (cattoucan.sync == SyncStage.Menu)
1456                             {
1457                                 if (thing.Contains(Terms.MENU_SOURCE))
1458                                 {
1459                                     cattoucan.SignalMenuEvent();
1460                                 }
1461                             }
1462                         }
1463                         else
1464                         {
1465                             // check the line for the magic strings:
1466                             // [+-*APPLICATION EXITED %d*-+]
1467                             // [+-*APPLICATION EXITED*-+]
1468                             // [+-*WAITING FOR GDB*-+]
1469                             // [+-*WAITING FOR DEBUGGER*-+]
1470                             if (!exclusive_match && thing.Contains("[+-*APPLICATION EXITED"))
1471                             {
1472                                 Int32 app_exit_code = 0;
1473                                 app_exit_code = Int32.Parse(System.Text.RegularExpressions.Regex.Match(thing, @"\-*?\d+").Value);
1474 
1475                                 reason = new ExitReason(ExitReason.Reason.StoppedByApplicationExit, app_exit_code);
1476 #if DEBUG
1477                                 CafeX.Log.WriteLine("CatToucan found 'APPLICATION EXITED' message and will exit with this reason.");
1478 #endif
1479                                 output_stream.WriteLine();
1480                                 return reason;
1481                             }
1482                             else if (thing.Contains("[+-*WAITING FOR DEBUGGER*-+]"))
1483                             {
1484                                 reason = new ExitReason(ExitReason.Reason.StoppedByWaitingForDebugger, 42);
1485 #if DEBUG
1486                                 CafeX.Log.WriteLine("CatToucan found 'WAITING FOR DEBUGGER' message and will exit with this reason.");
1487 #endif
1488                                 output_stream.WriteLine();
1489                                 return reason;
1490                             }
1491                             else if (thing.Contains("[+-*WAITING FOR GDB*-+]"))
1492                             {
1493                                 reason = new ExitReason(ExitReason.Reason.StoppedByWaitingForDebugger, 0);
1494 #if DEBUG
1495                                 CafeX.Log.WriteLine("CatToucan found 'WAITING FOR DEBUGGER' message and will exit with this reason.");
1496 #endif
1497                                 output_stream.WriteLine();
1498                                 return reason;
1499                             }
1500                         }
1501 
1502                         if (thing.Contains(Terms.DK_READY_MESSAGE))
1503                         {
1504                             // enable keyboard input
1505                             this.EnableDKConsoleInput();
1506                         }
1507                         else if (!HandleCafeEvents(thing) && !string.IsNullOrEmpty(CafeX.Program.CATTOUCAN_TERM.value) &&
1508                                   thing.Contains(CafeX.Program.CATTOUCAN_TERM.value))
1509                         {
1510                             output_stream.WriteLine("\nPattern Matched '" + CafeX.Program.CATTOUCAN_TERM.value);
1511                             return new ExitReason(ExitReason.Reason.StoppedByAlternateTermination, 0);
1512                         }
1513                         else
1514                         {
1515                             // Check the notification strings
1516                             foreach (KeyValuePair<string, ManualResetEvent> kvp in matchNotify)
1517                             {
1518                                 // See if we have any matches
1519                                 if (thing.IndexOf(kvp.Key, 0, thing.Length, StringComparison.CurrentCultureIgnoreCase) >= 0)
1520                                 {
1521                                     // Set the manual event
1522 #if DEBUG
1523                                     Console.WriteLine("    Found key '{0}'...setting handle {1}", kvp.Key, kvp.Value.Handle);
1524 #endif
1525                                     kvp.Value.Set();
1526                                 }
1527                             }
1528 
1529                             // Check the callback strings
1530                             foreach (KeyValuePair<string, Func<string, bool>> kvp in matchCallback)
1531                             {
1532                                 // See if we have any matches
1533                                 if (thing.IndexOf(kvp.Key, 0, thing.Length, StringComparison.CurrentCultureIgnoreCase) >= 0)
1534                                 {
1535                                     // Run the callback
1536 #if DEBUG
1537                                     Console.WriteLine("    Found key '{0}'...calling {1}({2})", kvp.Key, kvp.Value.Method.Name, thing);
1538 #endif
1539                                     if (!kvp.Value.Invoke(thing))
1540                                     {
1541                                         output_stream.WriteLine("Callback {0}({1}) requested termination.", kvp.Value.Method.Name, thing);
1542                                         return new ExitReason(ExitReason.Reason.StoppedByAlternateTermination, 0);
1543                                     }
1544                                 }
1545                             }
1546                         }
1547                     }
1548                     else if (next_char == '\t')
1549                     {
1550                         m_string_builder.Append(next_char);
1551                         output_stream.Write(next_char);
1552                     }
1553                     else if (next_char == '\b')
1554                     {
1555                         m_string_builder.Append(next_char);
1556                         output_stream.Write(next_char);
1557                         Console.Write(" \b");
1558                     }
1559                     else if (next_char == '\u001b' )
1560                     {
1561                         //if we read the escape character, we'll start checking the next few bytes
1562                         //to determine what special command we have to do
1563                         currentState = ParserState.EscapeSequenceParsing;
1564                     }
1565                 }
1566                 else
1567                 {
1568                     output_stream.Write(next_char);
1569                     m_string_builder.Append(next_char);
1570                 }
1571             }
1572 
1573             //
1574 
1575             return reason;
1576         }
1577 
1578         #region Helper APIs - Static Public
ParseStrings(string ipaddr, string port_num, out string error)1579         public static IPEndPoint ParseStrings(string ipaddr, string port_num, out string error)
1580         {
1581             IPAddress actual_ip_addr;
1582             int actual_port;
1583             bool could_parse_addr = IPAddress.TryParse(ipaddr, out actual_ip_addr);
1584             bool could_parse_port = int.TryParse(port_num, out actual_port);
1585 
1586 
1587             if (!could_parse_addr)
1588             {
1589                 error = "Couldn't parse IP address.";
1590                 return (null);
1591             }
1592 
1593             if (!could_parse_port)
1594             {
1595                 error = "Couldn't parse port number.";
1596                 return (null);
1597             }
1598 
1599 
1600             error = string.Empty;
1601             IPEndPoint rc = new IPEndPoint(actual_ip_addr, actual_port);
1602             return (rc);
1603         }
1604 
1605         #endregion
1606     }
1607 }
1608