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