1 /*---------------------------------------------------------------------------*
2 
3   Copyright (C) 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 using System.Collections.Generic;
13 using System;
14 using System.IO;
15 using System.Runtime.InteropServices;
16 using System.Text;
17 using System.Globalization;
18 using System.Net;
19 
20 namespace CafeX
21 {
22     public enum CAFEX_ERROR
23     {
24         OK = 0,
25         UPDATE_BRINGUP_BOOT1_UPDATE = 0,
26         RUN_SOFT_LAUNCH_WITH_DISCRUN = 1,
27         ON_RECURSIVE_CALL = 1,
28         SETBOOTMODE_BAD_MODE = 1,
29         MASTERING_AOC_INVALID_PARAMETERS = 1,
30         RUN_RPX_DOESNT_EXIST = 2,
31         HOSTCHECKVERSION_NO_HOSTBRIDGE = 2,
32         HOSTCHECKVERSION_FSEMUL_FAILED = 2,
33         HOSTCHECKVERSION_OLD_VERSIONS = 2,
34         BOOTMODEDETECT_FSEMUL_FAILED = 2,
35         MASTERING_DOWNLOAD_MASTERPART_NOT_FOUND = 2,
36         MASTERING_DOWNLOAD_WUMA_NOT_FOUND = 2,
37         MASTERING_DISC_MASTER_ARCHIVE_NOT_PROVIDED = 3,
38         MASTERING_AOC_CONTENT_SECTION_NOT_FOUND = 3,
39         MASTERING_DOWNLOAD_INVALID_WUMA = 4,
40         MASTERING_AOC_TOO_MANY_CONTENT_DIRS = 4,
41         MASTERING_DISC_INVALID_MASTER_ARCHIVE = 3,
42         MASTERING_DOWNLOAD_INVALID_OUTPUT_BASE_DIR = 5,
43         RUN_HOSTSTOP_RVAL_NON_ZERO = 5,
44         RUN_NO_RPX_SPECIFIED = 6,
45         RUN_TITLE_ID_NOT_FOUND_IN_MLC = 7,
46         RUN_FASTLAUNCH_FAILED = 7,
47         MASTERING_DOWNLOAD_INVALID_TEMP_DIR = 7,
48         MASTERING_DISC_INVALID_TEMP_DIR = 7,
49         RUN_CANT_FIND_OS_DIRECTORY = 8,
50         RUN_OS_NOT_PRESENT = 8,
51         RUN_DASH_R_WITH_NO_PREVIOUS_LAUNCH_INFO = 9,
52         RUN_DASH_R_CANT_FIND_APPLICATION = 9,
53         RUN_DISCRUN_NAND_UNABLE_TO_FIND_APPLICATION = 9,
54         RUN_UPDATE_ARGLIST_FAILED = 11,
55         RUN_CHECKTITLEID_FAILED = 12,
56         RUN_UPDATE_APP_XML_FAILED = 12,
57         RUN_UPDATE_COS_XML_FAILED = 12,
58         RUN_UPDATE_META_XML_FAILED = 12,
59         RUN_UPDATE_SYSTEM_XML_FAILED = 12,
60         MASTERING_ERROR = 14,
61         RUN_BAD_DEBUGGER = 20,
62         RUN_BAD_DEBUGGER_PORT = 21,
63         RUN_TEST_MODE_WITH_NAND_BOOT = 25,
64         RUN_PCFSSERVER_SYNC_FAILED = 30,
65         BOOTRUN_MAKEBSF_FAILED = 30,
66         BOOTRUN_MAKEDLF_FAILED = 31,
67         CAFEDEVRUN_CAFEMAKEDLF_FAILED = 31,
68         CAFEDEVRUN_NO_IMAGE_FILE = 50,
69         CAFEDEVRUN_CANT_FIND_APP_XML = 53,
70         RUN_KILL_RESTART_FAILED = 54,
71         CHECKNAME_FAILED = 55,
72 
73 
74         // CafeX only
75         UNEXPECTED = -1,
76         NO_SDK_HOSTBRIDGE_ENV_VARS = -2,
77         BAD_COMMAND = -3,
78         BAD_ARGUMENT = -4,
79         NO_DEVKIT_VARS = -5,
80         NO_SESSIONMANAGER_DLL = -6,
81         RUN_SPACES_IN_CAFE_ROOT = -7,
82         RUN_COULDNT_PARSE_SDK_VERSION_HEADER = -8,
83         RUN_NO_SDK_VERSION_HEADER = -9,
84         RUN_BAD_H_ARGUMENT = -10,
85         RUN_INVALID_OPTION = -11,
86         RUN_NO_TEMP_VAR = -12,
87         BOOTRUN_NO_TEMP_VAR = -13,
88         RUN_NON_MULTI_DEBUGGER = -14,
89         INVALID_OPTION = -15,
90         SETBOOTMODE_FAILED = -16,
91         RUN_TITLE_ID_REQUIRED = -17,
92         SYNCTOOL_FAILED = -18,
93         RECOVERY_FAILED = -19,
94         CLEARDATA_FAILED = -20,
95         MIONURL_FAILED = -21,
96         IMAGEUPLOADER_FAILED = -22,
97         SYSLOG_LEVEL_SET_FAILED = -23,
98         UPDATE_FAILED = -24,
99         UPLOADED_IMAGE_OS_MISMATCH = -25,
100         RUN_LAUNCH_FAILED = -26,
101         MIONPS_ERROR = -27, //mionps will always return an error code of 17 on failure, so we must check the string associated with the error.
102         BOOT_SYNC_FAILED = -28,
103         TITLE_SYNC_FAILED = -29,
104         PATH_DOES_NOT_EXIST = -30,
105         CANNOT_UPDATE_IN_NAND_MODE = -31,
106 
107         // Unmapped errors - should return 2 until BASH scripts map them to other values.
108         RUN_MEMMAP_NOT_SUPPORTED = 2,
109         RUN_BAD_TITLE_ID = 2,
110         RUN_NO_CAFE_DATA_DIR = 2,
111         RUN_D_OPTION_USED = 2,
112         ON_DATA_DIR_MISSING = 2,
113     }
114 
115     internal class CafeXReturn
116     {
117         public CAFEX_ERROR error;
118         public int appExitCode;
119 
120         /// <summary>
121         /// Constructor for CAFEX_ERROR class.  Sets the Error to the user-specified error.
122         /// Exit code = 0.
123         /// </summary>
124         /// <param name="err"></param>
CafeXReturn(CAFEX_ERROR err)125         public CafeXReturn(CAFEX_ERROR err)
126         {
127             error = err;
128             appExitCode = 0;
129         }
130         /// <summary>
131         /// Constructor for CAFEX_ERROR class.  Sets the application error code
132         /// according to the app's actual exit code.  Error = OK.
133         /// </summary>
134         /// <param name="exitcode"></param>
CafeXReturn(int exitcode)135         public CafeXReturn(int exitcode)
136         {
137             error = CAFEX_ERROR.OK;
138             appExitCode = exitcode;
139         }
140 
141         /// <summary>
142         /// Constructor for CAFEX_ERROR class.
143         /// </summary>
144         /// <param name="err">The Error CafeX should return</param>
145         /// <param name="exitcode">The exit code of the application</param>
CafeXReturn(CAFEX_ERROR err, int exitcode)146         public CafeXReturn(CAFEX_ERROR err, int exitcode)
147         {
148             error = err;
149             appExitCode = exitcode;
150         }
151 
CafeXReturn()152         public CafeXReturn()
153         {
154             error = CAFEX_ERROR.UNEXPECTED;
155             appExitCode = -1;
156         }
157     }
158 
159     partial class Program
160     {
161 
162 #if TIMER
163         public static PerfTimer CafeXSetupTime = new PerfTimer("CafeXSetupTime");
164 #endif
165 
166         private enum TargetEncoding
167         {
168             ConsoleIn,
169             ConsoleOut,
170             CafeOut
171         }
172 
173         // This variable is set if cafex is called with '-q'.
174         internal static bool quiet_mode = false;
175         // This variable is set if we want to ignore the internal setting of quiet mode
176         internal static bool ignore_quiet_mode = false;
177         // this variable is set if cafex is called with -noinput
178         internal static bool no_console = false;
179         // The cafex command to execute.
180         internal static string command = string.Empty;
181 
182         // Global software and firmware versions
183         internal static int sw_ver_flat = 0;
184         internal static int fw_ver_flat = 0;
185 
186         internal static readonly uint RETRY_ATTEMPS = 3;
187 
188         internal const int DEVKIT_HELP_REQUEST_TIMEOUT = 5000;
189         internal const int DEVKIT_HELP_RESPONSE_TIMEOUT = 5000;
190 
191         internal const int COSDEBUG_FASTLAUNCH_RETRIES = 3;
192         internal const int COSDEBUG_FASTLAUNCH_TIMEOUT = 10000; // 10 seconds
193         internal const int COSDEBUG_FASTLAUNCH_DELAY   = 2000;  // 2 seconds
194 
195         internal const int COSDEBUG_KILLRESTART_RETRIES = 1;
196         internal const int COSDEBUG_KILLRESTART_TIMEOUT = 1000; // 1 second
197         internal const int COSDEBUG_KILLRESTART_DELAY   = 1000; // 1 second
198 
199         internal const int KILLRESTART_SOFTLAUNCH_TIMEOUT = 10000; // 10 seconds
200 
201         internal static readonly TimeSpan RETRY_TIMESPAN = new TimeSpan(0, 0, 1);
202 
203         internal static readonly TimeSpan COS_REBOOT_DELAY = new TimeSpan(0, 0, 5);
204 
205         internal static readonly TimeSpan COS_READY_DELAY = new TimeSpan(0, 0, 12);
206 
207         static private string PERMISSION_ERROR_TEXT = "Check your permission to write to the file's directory, and that the file name provided does not conflict with an existing directory name.";
208 
209         static public Encoding cafeOutEncoding;
210 
211         /// <summary>
212         /// When true, indicates PCFS server is running in client mode for TCP connection with CAT-R.
213         /// </summary>
214         internal const int DEFAULT_PCFS_TCP_PORT = 5999;
215         static bool PcfsTcpOnly = false;
216         static IPEndPoint PcfsIpEndpoint = null;
217         static string DebugSerialPort;
218 
219 //        static private bool CALLED_FROM_CYGWIN = false;
220         /// <summary>
221         /// Cleans up input argument array by removing any empty ones, and
222         /// breaking up those that are not spaced out properly (eg. -t0x00...)
223         /// which makes the argument parsing logic more reliable.
224         /// </summary>
225         /// <param name="args">Input argument array of strings.</param>
226         /// <returns>New array of arguments.</returns>
CleanupArguments(string[] args)227         static private string[] CleanupArguments(string[] args)
228         {
229 #if DEBUG
230             Log.WriteLine("CleanEmptArguments started.");
231 #endif
232 
233             List<string> newList = new List<string>();
234             int rpxIndex = int.MaxValue;
235             bool rpxFound = false;
236 
237             for (int counter = 0; counter < args.Length; ++counter)
238             {
239                 string argument = args[counter];
240 
241                 if (argument != null)
242                 {
243                     bool breakUpParameter = false;
244                     bool notAfterDashA = false;
245 
246                     if (argument.Length > 2)
247                     {
248                         if (!rpxFound)
249                         {
250                             if (argument.ToLowerInvariant().EndsWith(".rpx") ||
251                                 argument.ToLowerInvariant().EndsWith(".elf"))
252                             {
253                                 rpxFound = true;
254                                 rpxIndex = counter;
255                             }
256                         }
257 
258                         if (counter < rpxIndex)
259                         {
260                             // Special Cases
261                             switch (argument.Substring(0, 2))
262                             {
263                                 case "-t":
264                                     notAfterDashA = (counter == 0) || (!(args[counter - 1].StartsWith("-a")));
265                                     if (notAfterDashA)
266                                     {
267                                         breakUpParameter = true;
268                                     }
269                                     break;
270                                 case "-w":
271                                     notAfterDashA = (counter == 0) || (!(args[counter - 1].StartsWith("-a")));
272                                     if (notAfterDashA)
273                                     {
274                                         breakUpParameter = true;
275                                     }
276                                     break;
277                                 default:
278                                     // Nothing to do : breakUpParameter = false
279                                     break;
280                             }
281                         }
282                     }
283 
284                     if (breakUpParameter)
285                     {
286                         // This is a case where the cafe argument and setting are not spaced out properly
287                         newList.Add(argument.Substring(0, 2));
288                         newList.Add(argument.Substring(2));
289                     }
290                     else
291                     {
292                         // Just add the argument - it is good!
293                         newList.Add(argument);
294                     }
295                 }
296             }
297 
298             return newList.ToArray();
299         }
300 
301         /// <summary>
302         /// Sets either the Console.In or Console.Out encoding using the encoder name or codepage provided.
303         /// </summary>
304         /// <param name="target">Console In or Console Out.</param>
305         /// <param name="encoderValue">String representing the codepage or encoding name.</param>
306         /// <returns>True if encoding was changed, false if not.</returns>
SetEncoding(TargetEncoding target, string encoderValue)307         static private bool SetEncoding(TargetEncoding target, string encoderValue)
308         {
309             if (string.IsNullOrEmpty(encoderValue))
310             {
311                 // It will use the default encoders.
312                 return false;
313             }
314 
315             int codePage = 0;
316             bool returnVal = false;
317             string defaultEncodingCodePage = Encoding.Default.WindowsCodePage.ToString();
318 
319             // could be a string as the name of the encoding
320             if (encoderValue.ToLowerInvariant().Equals("default"))
321             {
322                 encoderValue = defaultEncodingCodePage;
323             }
324 
325             //handle situation where user provides the number for the codepage
326             if (int.TryParse(encoderValue, out codePage))
327             {
328                 if (codePage > 0)
329                 {
330                     //It was given a valid codepage
331                     try
332                     {
333                         switch (target)
334                         {
335                             case TargetEncoding.ConsoleIn:
336                                 Console.InputEncoding = Encoding.GetEncoding(codePage);
337                                 returnVal = true;
338                                 break;
339                             case TargetEncoding.ConsoleOut:
340                                 Console.OutputEncoding = Encoding.GetEncoding(codePage);
341                                 returnVal = true;
342                                 break;
343                             case TargetEncoding.CafeOut:
344                                 Program.cafeOutEncoding = Encoding.GetEncoding(codePage);
345                                 returnVal = true;
346                                 break;
347                         }
348                     }
349                     catch (ArgumentOutOfRangeException)
350                     { }
351                     catch (ArgumentException)
352                     { }
353                     catch (NotSupportedException)
354                     { }
355                     catch (IOException)
356                     { }
357                     catch (System.Security.SecurityException)
358                     { }
359                 }
360             }
361             else //did they pass in the string representation of the encoding?
362             {
363                 try
364                 {
365                     switch (target)
366                     {
367                         case TargetEncoding.ConsoleIn:
368                             Console.InputEncoding = Encoding.GetEncoding(encoderValue);
369                             returnVal = true;
370                             break;
371                         case TargetEncoding.ConsoleOut:
372                             Console.OutputEncoding = Encoding.GetEncoding(encoderValue);
373                             returnVal = true;
374                             break;
375                         case TargetEncoding.CafeOut:
376                             Program.cafeOutEncoding = Encoding.GetEncoding(encoderValue);
377                             returnVal = true;
378                             break;
379                     }
380                 }
381                 catch (ArgumentException)
382                 { }
383                 catch (NotSupportedException)
384                 { }
385                 catch (IOException)
386                 { }
387                 catch (System.Security.SecurityException)
388                 { }
389             }
390 
391             if (!returnVal && !encoderValue.Equals(defaultEncodingCodePage))
392             {
393                 // Failed with the user request, trying to default
394                 Console.WriteLine("cafex : unable to set {0} encoding to {1}. Reverting to default {2}.", target.ToString(), encoderValue, defaultEncodingCodePage);
395                 return SetEncoding(target, defaultEncodingCodePage);
396             }
397 
398             return returnVal;
399         }
400 
401         private static HandlerRoutine cchr = null;
402 
ConsoleCtrlCheck(CtrlTypes ctrlType)403         private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
404         {
405             Console.WriteLine("\ncafex: terminated");
406             Environment.Exit(0);
407             return true;
408         }
409 
Main(string[] args)410         static int Main(string[] args)
411         {
412             Console.WriteLine("-------------------------- CAFEX NEW INSTANCE ----------------------------------");
413             Console.WriteLine("Main started. Command line = " + Environment.CommandLine);
414 #if TIMER
415             string logFilePath = "%CAFE_TEMP%\\%SESSION_PATH_PREFIX%caferun\\CafeXPerf.log";
416             Log.OpenLogFile(logFilePath);
417             CafeXSetupTime.Start();
418 #endif
419             cchr = new HandlerRoutine(ConsoleCtrlCheck);
420             SetConsoleCtrlHandler(cchr, true);
421             GC.KeepAlive(cchr);
422 
423 #if DEBUG
424             //System.Diagnostics.Debugger.Break();
425             string logFilePath = "%CAFE_TEMP%\\%SESSION_PATH_PREFIX%caferun\\CafeX.log";
426             Log.OpenLogFile(logFilePath);
427             Log.WriteLine("-------------------------- CAFEX NEW INSTANCE ----------------------------------");
428             Log.WriteLine("Main started. Command line = " + Environment.CommandLine);
429 #endif
430 
431             if(CAFE_PROFILE.value != null && CAFE_PROFILE.value == "1")
432             {
433                 Console.WriteLine("CafeX: Profiling is enabled!");
434                 CafeXEventtLog.Instance.WriteToEventLog(500, DateTime.Now, EventStatus.BEGIN, EventProcess.CAFEX, "CAFEX NEW INSTANCE");
435             }
436             StreamWriter output_writer = null;
437             TextWriter old_out = null;
438             TextWriter old_err = null;
439 
440             FileUtil.GatherEnvironment();
441 
442             // Setting default encoding variables
443             if (string.IsNullOrEmpty(CAFEX_STDIN_ENCODING.value))
444             {
445                 CAFEX_STDIN_ENCODING.value = "default";
446             }
447 
448             if (string.IsNullOrEmpty(CAFEX_STDOUT_ENCODING.value))
449             {
450                 // Standard out should be different depending on the country.
451                 int defaultEncodingCodePage = Encoding.Default.WindowsCodePage;
452                 switch(defaultEncodingCodePage)
453                 {
454                     case 932: //Japan - Shift_JIS
455                         CAFEX_STDOUT_ENCODING.value = "default";
456                         break;
457                     default:
458                     CAFEX_STDOUT_ENCODING.value = "utf-8";
459                         break;
460                 }
461             }
462 
463             try
464             {
465                 // Check if "cafex" was the only thing in the command line
466                 if (args == null || args.Length == 0)
467                 {
468                     PrintCafeXHelp();
469                     return new CafeXReturn(CAFEX_ERROR.OK).appExitCode;
470                 }
471 
472                 int arg_pos = 0;
473                 args = CleanupArguments(args);
474 
475                 // Handle the arguments to cafex in any order.
476                 // Valid options are -q and -o.
477                 while (true)
478                 {
479                     if (args.Length > arg_pos && args[arg_pos] == "-q")
480                     {
481                         quiet_mode = true;
482                         ++arg_pos;
483                     }
484                     // Format for -o is -o [filename], so need to make sure there are 2 more arguments.
485                     else if (args.Length > arg_pos + 1 && args[arg_pos] == "-o")
486                     {
487                         if (!ArgumentChecks.IsArgumentValidSetting(args, arg_pos + 1))
488                         {
489                             PrintCafeXHelp();
490                             return (int)CAFEX_ERROR.BAD_ARGUMENT;
491                         }
492 
493                         string output_file = PathConverter.Windowsify(args[arg_pos + 1]);
494                         if (!FileUtil.IsValidFile(output_file, false))
495                         {
496                             Console.WriteLine("Invalid file name!\n");
497                             PrintCafeXHelp();
498                             return (int) CAFEX_ERROR.BAD_ARGUMENT;
499                         }
500 
501                         arg_pos += 2;
502 
503                         FileStream fs = null;
504                         try
505                         {
506                             fs = new FileStream(output_file, FileMode.Create);
507                         }
508                         catch (System.UnauthorizedAccessException)
509                         {
510                             Console.WriteLine(PERMISSION_ERROR_TEXT);
511                             PrintCafeXHelp();
512                             return (int)CAFEX_ERROR.BAD_ARGUMENT;
513                         }
514                         catch (System.Security.SecurityException)
515                         {
516                             Console.WriteLine(PERMISSION_ERROR_TEXT);
517                             PrintCafeXHelp();
518                             return (int)CAFEX_ERROR.BAD_ARGUMENT;
519                         }
520 
521                         old_out = Console.Out;
522                         old_err = Console.Error;
523                         output_writer = new StreamWriter(fs);
524                         Console.SetOut(output_writer);
525                         Console.SetError(output_writer);
526                     }
527                     else if (args.Length > arg_pos + 1 && args[arg_pos] == "-I")
528                     {
529                         // Get the STDIN encoding
530                         if (!ArgumentChecks.IsArgumentValidSetting(args, arg_pos + 1))
531                         {
532                             PrintCafeXHelp();
533                             return (int)CAFEX_ERROR.BAD_ARGUMENT;
534                         }
535 
536                         CAFEX_STDIN_ENCODING.value = args[arg_pos + 1];
537                         arg_pos += 2;
538                     }
539                     else if (args.Length > arg_pos + 1 && args[arg_pos] == "-O")
540                     {
541                         if (!ArgumentChecks.IsArgumentValidSetting(args, arg_pos + 1))
542                         {
543                             PrintCafeXHelp();
544                             return (int)CAFEX_ERROR.BAD_ARGUMENT;
545                         }
546 
547                         CAFEX_STDOUT_ENCODING.value = args[arg_pos + 1];
548                         arg_pos += 2;
549                     }
550 #if PCFS_OVER_TCP // Block out PcfsTcpOnly being set to true if we're not on a build of CafeX.exe that enables PCFS over TCP.
551                     else if(args.Length > arg_pos + 1 && args[arg_pos] == "-pcfsonly")
552                     {
553                         if (!ArgumentChecks.IsArgumentValidSetting(args, arg_pos + 1))
554                         {
555                             PrintCafeXHelp();
556                             return (int)CAFEX_ERROR.BAD_ARGUMENT;
557                         }
558 
559                         PcfsTcpOnly = true;
560                         arg_pos++;
561 
562                         PcfsIpEndpoint = ParseIpEndPoint(args[arg_pos]);
563                         if (PcfsIpEndpoint == null)
564                         {
565                             PrintCafeXHelp();
566                             return (int)CAFEX_ERROR.BAD_ARGUMENT;
567                         }
568                         else
569                         {
570                             arg_pos++;
571                         }
572                     }
573 #endif
574                     else if(args.Length > arg_pos + 1 && args[arg_pos] == "-debugport")
575                     {
576                         if (!ArgumentChecks.IsArgumentValidSetting(args, arg_pos + 1))
577                         {
578                             PrintCafeXHelp();
579                             return (int)CAFEX_ERROR.BAD_ARGUMENT;
580                         }
581 
582                         DebugSerialPort = TryParseComPort(args[++arg_pos]);
583 
584                         if(string.IsNullOrEmpty(DebugSerialPort))
585                         {
586                             PrintCafeXHelp();
587                             return (int)CAFEX_ERROR.BAD_ARGUMENT;
588                         }
589                         arg_pos++;
590                     }
591                     else if (args[arg_pos] == "-noinput")
592                     {
593                         no_console = true;
594                         arg_pos++;
595                     }
596                     else
597                     {
598                         // If it's anything else then it's the command.
599                         break;
600                     }
601                 }
602 
603 
604                 // Get the command.
605                 if (args.Length > arg_pos)
606                 {
607                     command = args[arg_pos].ToLowerInvariant();
608                     ++arg_pos;
609                 }
610 
611                 CAFEX_ERROR ret = InitialEnvVarSetup();
612                 if (ret != CAFEX_ERROR.OK)
613                 {
614                     return (int)ret;
615                 }
616 
617                 ret = CheckCafeRootAndHostBridgeVars();
618                 if (ret != CAFEX_ERROR.OK)
619                 {
620                     return (int)ret;
621                 }
622 
623                 if (!string.IsNullOrEmpty(NO_CONSOLE.value) && NO_CONSOLE.value == "1")
624                 {
625                     // Disable console input if it was specified in the enviromnment
626                     no_console = true;
627                 }
628 
629                 // Sets the quiet mode if CAFE_CONSOLE is toucan and
630                 // "-q" parameter was not provided by caller.
631                 if (!quiet_mode && (CAFE_CONSOLE.value != "cattoucan"))
632                 {
633                     quiet_mode = true;
634                 }
635                 else if (CAFE_CONSOLE.value == "cattoucan")
636                 {
637                     // If explicitly setting console to cattoucan, it
638                     // should override even if the "-q" parameter was
639                     // set.  This solves validation.sh tests running
640                     // in softlaunch mode.
641                     quiet_mode = false;
642                 }
643 
644                 // Print the encoding
645                 if (!command.Equals("stop"))
646                 {
647                     // Set encodings
648                     if (CafeX.Program.IsInputRedirected)
649                     {
650                         Console.WriteLine("cafex : input is redirected. Using legacy keyboard input and not changing encodings.");
651                     }
652                     else
653                     {
654                         SetEncoding(TargetEncoding.ConsoleIn, CAFEX_STDIN_ENCODING.value);
655                         SetEncoding(TargetEncoding.ConsoleOut, CAFEX_STDOUT_ENCODING.value);
656 
657                         Console.WriteLine("cafex input/keyboard encoding code page: {0}", Console.InputEncoding.CodePage);
658                         Console.WriteLine("cafex output/screen  encoding code page: {0}", Console.OutputEncoding.CodePage);
659                     }
660 
661                     //always want to set this.
662                     SetEncoding(TargetEncoding.CafeOut, CAFE_OUTPUT_ENCODING.value);
663                     if (Program.cafeOutEncoding != null)
664                     {
665                         Console.WriteLine("cafe output/stream   encoding code page: {0}", Program.cafeOutEncoding.CodePage);
666                     }
667                 }
668 
669                 int args_to_copy = args.Length - arg_pos;
670                 string[] args_for_command = new string[args_to_copy];
671                 Array.Copy(args, arg_pos, args_for_command, 0, args_to_copy);
672 
673                 CafeXReturn exitCode = DoCommand(args_for_command, quiet_mode);
674 
675                 // Only output general CafeX help if user inputted a bad command.
676                 // If they inputted an invalid option to a valid command, then that command's method should output its own help text.
677                 if (exitCode.error == CAFEX_ERROR.BAD_COMMAND)
678                 {
679                     Console.WriteLine("cafex : Bad command.");
680                     PrintCafeXHelp();
681                 }
682 
683                 if (exitCode.error != CAFEX_ERROR.OK)
684                     return (int)exitCode.error;
685                 else
686                     return exitCode.appExitCode;
687             }
688             catch (Exception e)
689             {
690                 Console.OutputEncoding.GetEncoder().Reset();
691                 string errorFile = "cafexErrorLog_" + System.Diagnostics.Process.GetCurrentProcess().Id.ToString() + "_" +
692                                    DateTime.Now.ToString("yyyyMMddHHmm") + ".log";
693                 string errorLog = "CafeX error log\nDate:" + DateTime.Now + "\nError message:" +
694                                    e.Message + "\nStack trace:" + e.StackTrace + "\n";
695                 if (string.IsNullOrEmpty(CAFE_TEMP.value))
696                 {
697                     errorFile = Path.Combine(Directory.GetCurrentDirectory(), SESSION_PATH_PREFIX.value + errorFile);
698                 }
699                 else
700                 {
701                     errorFile = Path.Combine(CAFE_TEMP.value, SESSION_PATH_PREFIX.value + errorFile);
702                 }
703 
704                 try
705                 {
706                     System.IO.File.WriteAllText(errorFile, errorLog);
707                     Console.WriteLine("CafeX error log was saved as '{0}'", errorFile);
708                 }
709                 catch (IOException)
710                 {
711                     Console.WriteLine("Unable to write the file '{0}'", errorFile);
712                     Console.WriteLine("Writing log to console window:");
713                     Console.WriteLine(errorLog);
714                 }
715 
716                 Console.WriteLine("CafeX encountered an error executing the command.");
717                 Console.WriteLine("Please confirm this is a supported command and has the correct arguments.");
718                 Console.WriteLine("Error message: " + e.Message);
719 
720                 return (int)CAFEX_ERROR.UNEXPECTED;
721             }
722             finally
723             {
724                 // Re-set the original stdout and stderr.
725                 if (output_writer != null)
726                 {
727                     Console.SetOut(old_out);
728                     Console.SetError(old_err);
729                     output_writer.Close();
730                 }
731 #if DEBUG || TIMER
732                 Log.WriteLine("-------------------------- END OF CAFEX INSTANCE ----------------------------------");
733                 Log.CloseLogFile();
734 #endif
735                 if (CAFE_PROFILE.value != null && CAFE_PROFILE.value == "1")
736                 {
737                     CafeXEventtLog.Instance.WriteToEventLog(501, DateTime.Now, EventStatus.END, EventProcess.CAFEX, "END OF CAFEX INSTANCE");
738                 }
739 
740             }
741 
742         }
743 
744 
CheckCafeRootAndHostBridgeVars()745         internal static CAFEX_ERROR CheckCafeRootAndHostBridgeVars()
746         {
747 #if DEBUG
748             Log.WriteLine("CheckCafeRootAndHostBridgeVars started.");
749 #endif
750 
751             CAFEX_ERROR ret = CAFEX_ERROR.OK;
752 
753             if (CAFE_ROOT.value == null)
754             {
755                 Console.WriteLine("CafeX Error: CAFE_ROOT environment variable is not set.");
756                 ret = CAFEX_ERROR.NO_SDK_HOSTBRIDGE_ENV_VARS;
757             }
758             if (MION_BRIDGE_TOOLS.value == null && !PcfsTcpOnly)
759             {
760                 Console.WriteLine("CafeX Error: MION_BRIDGE_TOOLS environment variable is not set.");
761                 ret = CAFEX_ERROR.NO_SDK_HOSTBRIDGE_ENV_VARS;
762             }
763 
764             return ret;
765         }
766 
CheckDevkitVars()767         internal static CAFEX_ERROR CheckDevkitVars()
768         {
769 #if DEBUG
770             Log.WriteLine("CheckDevkitVars started. Retuns CAFEX_ERRO.OK if BRIDGE_CURRENT_NAME and BRIDGE_CURRENT_IP_ADDRESS are provided.");
771 #endif
772 
773             CAFEX_ERROR ret = CAFEX_ERROR.OK;
774 
775             if (!PcfsTcpOnly)
776             {
777                 if (string.IsNullOrEmpty(BRIDGE_CURRENT_NAME.value))
778                 {
779                     Console.WriteLine("CafeX Error: BRIDGE_CURRENT_NAME environment variable is not set.");
780                     ret = CAFEX_ERROR.NO_DEVKIT_VARS;
781                 }
782                 if (string.IsNullOrEmpty(BRIDGE_CURRENT_IP_ADDRESS.value))
783                 {
784                     Console.WriteLine("CafeX Error: BRIDGE_CURRENT_IP_ADDRESS environment variable is not set.");
785                     ret = CAFEX_ERROR.NO_DEVKIT_VARS;
786                 }
787             }
788 
789             return ret;
790         }
791 
ParseIpEndPoint(string value)792         private static IPEndPoint ParseIpEndPoint(string value)
793         {
794             string ipValue = string.Empty;
795             string portValue = string.Empty;
796             int portIndex = value.IndexOf(':');
797 
798             if (portIndex >= 0)
799             {
800                 ipValue = value.Substring(0, portIndex);
801                 portValue = value.Substring(portIndex + 1);
802             }
803             else
804             {
805                 ipValue = value;
806                 portValue = DEFAULT_PCFS_TCP_PORT.ToString();
807             }
808 
809             IPAddress ip;
810             int port;
811 
812             if (IPAddress.TryParse(ipValue, out ip) &&
813                 int.TryParse(portValue, out port))
814             {
815                 //the parsing will make sure no garbage was in the port string
816                 //now make sure the port is actually valid between 1 and 65536
817                 if(port <= 0 || port > ushort.MaxValue)
818                 {
819                     port = DEFAULT_PCFS_TCP_PORT;
820                 }
821 
822                 return new IPEndPoint(ip, port);
823             }
824             return null;
825         }
826 
827         /// <summary>
828         /// Attempts to parse the COM port number from the given string.  Strings looked for include
829         /// "COM 1", "1", and "COM1".
830         /// </summary>
831         /// <param name="value">String to parse.</param>
832         /// <returns><see cref="String.Empty"/> if the value cannot be parsed, otherwise an integer number string representing the port.</returns>
TryParseComPort(string value)833         private static string TryParseComPort(string value)
834         {
835             if(string.IsNullOrEmpty(value))
836             {
837                 return string.Empty;
838             }
839 
840             var tokens = value.Split(new char[] { ' ' });
841 
842             string portNumber = string.Empty;
843 
844             if(tokens.Length == 1)
845             {
846                 if(tokens[0].StartsWith("com", StringComparison.InvariantCultureIgnoreCase) && tokens[0].Length > 3)
847                 {
848                     portNumber = tokens[0].Substring(3);
849                 }
850                 else
851                 {
852                     portNumber = tokens[0];
853                 }
854             }
855             else if(tokens.Length == 2)
856             {
857                 if(tokens[0].Equals("com", StringComparison.InvariantCultureIgnoreCase))
858                 {
859                     portNumber = tokens[1];
860                 }
861             }
862 
863             int _;
864 
865             if(int.TryParse(portNumber, out _))
866             {
867                 return portNumber;
868             }
869 
870             return string.Empty;
871         }
872 
DoCommand(string[] args, bool quiet_mode)873         internal static CafeXReturn DoCommand(string[] args, bool quiet_mode)
874         {
875 #if DEBUG
876             Log.WriteLine("DoCommand started. quiet_mode=" + quiet_mode.ToString());
877             string argString = null;
878             if (args != null)
879             {
880                 foreach (string arg in args)
881                 {
882                     argString += arg + " ";
883                 }
884             }
885             Log.WriteLine("Arguments=" + argString);
886 #endif
887 
888             CafeXReturn ret = new CafeXReturn(CAFEX_ERROR.OK);
889 
890             switch (command.ToLowerInvariant())
891             {
892                 case "-h":
893                 case "help":
894                     {
895                         PrintCafeXHelp();
896                         break;
897                     }
898 
899                 case "stop":
900                     {
901                         ret.error = CheckDevkitVars();
902                         if (ret.error != CAFEX_ERROR.OK)
903                             break;
904 
905                         stop(args);
906 
907                         break;
908                     }
909 
910                 case "run":
911                     {
912                         ret.error = CheckDevkitVars();
913                         if (ret.error != CAFEX_ERROR.OK)
914                             break;
915 
916                         ret = run(args, true, true);
917 
918                         break;
919                     }
920 
921                 case "discrun":
922                     {
923                         ret.error = CheckDevkitVars();
924                         if (ret.error != CAFEX_ERROR.OK)
925                             break;
926 
927                         ret = discrun(args, true, true);
928 
929                         break;
930                     }
931 
932                 case "headlessrun":
933                     {
934                         ret.error = CheckDevkitVars();
935                         if (ret.error != CAFEX_ERROR.OK)
936                             break;
937 
938                         ret = headlessrun(args);
939                         break;
940                     }
941 
942                 case "on":
943                     {
944                         ret.error = CheckDevkitVars();
945                         if (ret.error != CAFEX_ERROR.OK)
946                             break;
947 
948                         ret = on(args, true, true);
949 
950                         break;
951                     }
952 
953                 case "update":
954                     {
955                         ret.error = CheckDevkitVars();
956                         if (ret.error != CAFEX_ERROR.OK)
957                             break;
958 
959                         ret.error = update(args);
960 
961                         break;
962                     }
963 
964                 case "revert":
965                     {
966                         ret.error = CheckDevkitVars();
967                         if (ret.error != CAFEX_ERROR.OK)
968                             break;
969 
970                         ret.error = revert(args);
971 
972                         break;
973                     }
974 
975                 case "recover":
976                     {
977                         ret.error = CheckDevkitVars();
978                         if (ret.error != CAFEX_ERROR.OK)
979                             break;
980 
981                         ret.error = recover(args);
982 
983                         break;
984                     }
985 
986                 case "cleardata":
987                     {
988                         ret.error = CheckDevkitVars();
989                         if (ret.error != CAFEX_ERROR.OK)
990                             break;
991 
992                         ret.error = cleardata(args);
993 
994                         break;
995                     }
996 
997                 case "setbootmode":
998                     {
999                         ret.error = CheckDevkitVars();
1000                         if (ret.error != CAFEX_ERROR.OK)
1001                             break;
1002 
1003                         ret.error = setbootmode(args);
1004 
1005                         break;
1006                     }
1007 
1008                  case "syncsession":
1009                     {
1010                         ret.error = CheckDevkitVars();
1011                         if (ret.error != CAFEX_ERROR.OK)
1012                             break;
1013 
1014                         ret.error = syncsession(args);
1015 
1016                         break;
1017                     }
1018                 case "makeaoc":
1019                 case "makemaster":
1020                 case "makedisc":
1021                 case "makedownload":
1022                     {
1023                         Console.WriteLine("CafeX Error: This function is no longer supported. Please use makecfmaster.exe to use commands for mastering.");
1024                         ret.error = CAFEX_ERROR.BAD_COMMAND;
1025                         break;
1026                     }
1027                 case "findbridges":
1028                     {
1029                         break;
1030                     }
1031 
1032                 case "version":
1033                     {
1034                         System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
1035 
1036                         // With an AssemblyVersion of <major>.<minor>.*, the build is the number of days since 1/1/2000
1037                         //   and the revision is the number of seconds since midnight
1038                         Version asmVer = asm.GetName().Version;
1039 
1040                         DateTime buildDate = new DateTime(2000, 1, 1);           // Start at 1/1/2000
1041                         buildDate = buildDate.AddDays(asmVer.Build);            // Add the days since 1/1/2000
1042                         buildDate = buildDate.AddSeconds(asmVer.Revision * 2);  // Add the number of seconds since midnight
1043 
1044                         System.Diagnostics.FileVersionInfo fileVer = System.Diagnostics.FileVersionInfo.GetVersionInfo(asm.Location);
1045 
1046                         //NEVER EVER CHANGE THE NEXT TWO LINES.  YOUR LIFE MAY BE FORFEIT IF YOU DO.
1047                         Console.WriteLine("cafex {0}", fileVer.FileVersion);
1048                         Console.WriteLine("{0}",buildDate.ToString("MM/dd/yyyy hh:mm tt",DateTimeFormatInfo.InvariantInfo));
1049 
1050                         ret.error = CAFEX_ERROR.OK;
1051                         break;
1052                     }
1053                 case "launch":
1054                     {
1055                         ret.error = launchTitle(args);
1056                         break;
1057                     }
1058                 case "install":
1059                     {
1060                         ret.error = install(args);
1061                         break;
1062                     }
1063                 default:
1064                     {
1065                         if (string.IsNullOrEmpty(command) || command.StartsWith("-"))
1066                         {
1067                             Console.WriteLine("CafeX Error: Missing command. Please try \"cafex help\" for usage information.");
1068                         }
1069                         else
1070                         {
1071                             Console.WriteLine("CafeX Error: Invalid command '{0}'. Please try \"cafex help\" for usage information.", command);
1072                         }
1073                         ret.error = CAFEX_ERROR.BAD_COMMAND;
1074                         break;
1075                     }
1076             }
1077 
1078             return ret;
1079         }
1080 
1081         private enum FileType { Unknown, Disk, Char, Pipe };
1082         private enum StdHandle { Stdin = -10, Stdout = -11, Stderr = -12 };
1083         [DllImport("kernel32.dll")]
GetFileType(IntPtr hdl)1084         private static extern FileType GetFileType(IntPtr hdl);
1085         [DllImport("kernel32.dll")]
GetStdHandle(StdHandle std)1086         private static extern IntPtr GetStdHandle(StdHandle std);
1087         [DllImport("Kernel32")]
SetConsoleCtrlHandler(HandlerRoutine Handler, Boolean Add)1088         public static extern Boolean SetConsoleCtrlHandler(HandlerRoutine Handler,
1089             Boolean Add);
1090 
1091         // sent to the handler routine.
1092         public enum CtrlTypes
1093         {
1094 
1095             CTRL_C_EVENT = 0,
1096             CTRL_BREAK_EVENT,
1097             CTRL_CLOSE_EVENT,
1098             CTRL_LOGOFF_EVENT = 5,
1099             CTRL_SHUTDOWN_EVENT
1100         }
1101 
1102 
1103         // A delegate type to be used as the handler routine
1104         // for SetConsoleCtrlHandler.
HandlerRoutine(CtrlTypes CtrlType)1105         public delegate bool HandlerRoutine(CtrlTypes CtrlType);
1106 
1107         internal static bool IsOutputRedirected
1108         {
1109             get
1110             {
1111                 return FileType.Char != GetFileType(GetStdHandle(StdHandle.Stdout));
1112             }
1113         }
1114 
1115         internal static bool IsInputRedirected
1116         {
1117             get
1118             {
1119                 return FileType.Char != GetFileType(GetStdHandle(StdHandle.Stdin));
1120             }
1121         }
1122 
1123 
PrintCafeXHelp()1124         internal static void PrintCafeXHelp()
1125         {
1126             Console.WriteLine("Usage: cafex [options] [command] [arguments]");
1127             Console.WriteLine();
1128             Console.WriteLine("options:");
1129             Console.WriteLine(" -q              The command will be executed, and CafeX returns immediately. No output is collected.");
1130             Console.WriteLine(" -o <filename>   All output is redirected to the specified file.");
1131             Console.WriteLine(" -I <encoding>   The encoding for the console standard in. Valid encoding arguments are: codepage");
1132             Console.WriteLine("                 (eg. 65001, 932, etc), or the encoding name (eg. UTF-8, shift_jis, etc).");
1133             Console.WriteLine("                 This setting overrides the CAFEX_STDIN_ENCODING environment variable.");
1134             Console.WriteLine("                 Note: If the environment variable and this switch is not provided, the default windows encoding is used.");
1135             Console.WriteLine(" -O <encoding>   The encoding for the console standard out. Valid encoding arguments are: codepage");
1136             Console.WriteLine("                 (eg. 65001, 932, etc), or the encoding name (eg. UTF-8, shift_jis, etc).");
1137             Console.WriteLine("                 This setting overrides the CAFEX_STDOUT_ENCODING environment variable.");
1138             Console.WriteLine("                 Note: If the environment variable and this switch is not provided, UTF-8 is default.");
1139             Console.WriteLine(" -noinput        Disables reading of the PC keyboard or STDIN if redirected.");
1140             Console.WriteLine("                 Note: Setting the environment variable NO_CONSOLE=1 has the same effect.");
1141             Console.WriteLine();
1142             Console.WriteLine("commands:");
1143             Console.WriteLine(" stop            Stop the devkit.");
1144             Console.WriteLine(" run             Run an application.");
1145             Console.WriteLine(" discrun         Run an application using block level interface.");
1146             Console.WriteLine(" headlessrun     Run an application disconnected from the network.");
1147             Console.WriteLine(" on              Start System Config Tool.");
1148             Console.WriteLine(" update          Update devkit firmware.");
1149             Console.WriteLine(" revert          Downgrade devkit OS to an older SDK.");
1150             Console.WriteLine(" recover         Attempt to recover a devkit from corrupted OS.");
1151             Console.WriteLine(" cleardata       Clear the user data in NAND.");
1152             Console.WriteLine(" setbootmode     Change the boot mode of the devkit.");
1153             Console.WriteLine(" syncsession     Sync the session data directory with the default one.");
1154             Console.WriteLine(" launch          Launches a title through system config tool.");
1155             Console.WriteLine(" install         Installs title to emulated MLC.");
1156             Console.WriteLine();
1157             Console.WriteLine(" version         Prints version and build information then exits.");
1158             Console.WriteLine();
1159             Console.WriteLine("arguments:");
1160             Console.WriteLine("Execute cafex [command] -h for information on valid arguments for each command.");
1161         }
1162     }
1163 }
1164