/*---------------------------------------------------------------------------*
Copyright (C) Nintendo. All rights reserved.
These coded instructions, statements, and computer programs contain
proprietary information of Nintendo of America Inc. and/or Nintendo
Company Ltd., and are protected by Federal copyright law. They may
not be disclosed to third parties or copied or duplicated in any form,
in whole or in part, without the prior written consent of Nintendo.
*---------------------------------------------------------------------------*/
using System.Collections.Generic;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Globalization;
using System.Net;
namespace CafeX
{
public enum CAFEX_ERROR
{
OK = 0,
UPDATE_BRINGUP_BOOT1_UPDATE = 0,
RUN_SOFT_LAUNCH_WITH_DISCRUN = 1,
ON_RECURSIVE_CALL = 1,
SETBOOTMODE_BAD_MODE = 1,
MASTERING_AOC_INVALID_PARAMETERS = 1,
RUN_RPX_DOESNT_EXIST = 2,
HOSTCHECKVERSION_NO_HOSTBRIDGE = 2,
HOSTCHECKVERSION_FSEMUL_FAILED = 2,
HOSTCHECKVERSION_OLD_VERSIONS = 2,
BOOTMODEDETECT_FSEMUL_FAILED = 2,
MASTERING_DOWNLOAD_MASTERPART_NOT_FOUND = 2,
MASTERING_DOWNLOAD_WUMA_NOT_FOUND = 2,
MASTERING_DISC_MASTER_ARCHIVE_NOT_PROVIDED = 3,
MASTERING_AOC_CONTENT_SECTION_NOT_FOUND = 3,
MASTERING_DOWNLOAD_INVALID_WUMA = 4,
MASTERING_AOC_TOO_MANY_CONTENT_DIRS = 4,
MASTERING_DISC_INVALID_MASTER_ARCHIVE = 3,
MASTERING_DOWNLOAD_INVALID_OUTPUT_BASE_DIR = 5,
RUN_HOSTSTOP_RVAL_NON_ZERO = 5,
RUN_NO_RPX_SPECIFIED = 6,
RUN_TITLE_ID_NOT_FOUND_IN_MLC = 7,
RUN_FASTLAUNCH_FAILED = 7,
MASTERING_DOWNLOAD_INVALID_TEMP_DIR = 7,
MASTERING_DISC_INVALID_TEMP_DIR = 7,
RUN_CANT_FIND_OS_DIRECTORY = 8,
RUN_OS_NOT_PRESENT = 8,
RUN_DASH_R_WITH_NO_PREVIOUS_LAUNCH_INFO = 9,
RUN_DASH_R_CANT_FIND_APPLICATION = 9,
RUN_DISCRUN_NAND_UNABLE_TO_FIND_APPLICATION = 9,
RUN_UPDATE_ARGLIST_FAILED = 11,
RUN_CHECKTITLEID_FAILED = 12,
RUN_UPDATE_APP_XML_FAILED = 12,
RUN_UPDATE_COS_XML_FAILED = 12,
RUN_UPDATE_META_XML_FAILED = 12,
RUN_UPDATE_SYSTEM_XML_FAILED = 12,
MASTERING_ERROR = 14,
RUN_BAD_DEBUGGER = 20,
RUN_BAD_DEBUGGER_PORT = 21,
RUN_TEST_MODE_WITH_NAND_BOOT = 25,
RUN_PCFSSERVER_SYNC_FAILED = 30,
BOOTRUN_MAKEBSF_FAILED = 30,
BOOTRUN_MAKEDLF_FAILED = 31,
CAFEDEVRUN_CAFEMAKEDLF_FAILED = 31,
CAFEDEVRUN_NO_IMAGE_FILE = 50,
CAFEDEVRUN_CANT_FIND_APP_XML = 53,
RUN_KILL_RESTART_FAILED = 54,
CHECKNAME_FAILED = 55,
// CafeX only
UNEXPECTED = -1,
NO_SDK_HOSTBRIDGE_ENV_VARS = -2,
BAD_COMMAND = -3,
BAD_ARGUMENT = -4,
NO_DEVKIT_VARS = -5,
NO_SESSIONMANAGER_DLL = -6,
RUN_SPACES_IN_CAFE_ROOT = -7,
RUN_COULDNT_PARSE_SDK_VERSION_HEADER = -8,
RUN_NO_SDK_VERSION_HEADER = -9,
RUN_BAD_H_ARGUMENT = -10,
RUN_INVALID_OPTION = -11,
RUN_NO_TEMP_VAR = -12,
BOOTRUN_NO_TEMP_VAR = -13,
RUN_NON_MULTI_DEBUGGER = -14,
INVALID_OPTION = -15,
SETBOOTMODE_FAILED = -16,
RUN_TITLE_ID_REQUIRED = -17,
SYNCTOOL_FAILED = -18,
RECOVERY_FAILED = -19,
CLEARDATA_FAILED = -20,
MIONURL_FAILED = -21,
IMAGEUPLOADER_FAILED = -22,
SYSLOG_LEVEL_SET_FAILED = -23,
UPDATE_FAILED = -24,
UPLOADED_IMAGE_OS_MISMATCH = -25,
RUN_LAUNCH_FAILED = -26,
MIONPS_ERROR = -27, //mionps will always return an error code of 17 on failure, so we must check the string associated with the error.
BOOT_SYNC_FAILED = -28,
TITLE_SYNC_FAILED = -29,
PATH_DOES_NOT_EXIST = -30,
CANNOT_UPDATE_IN_NAND_MODE = -31,
// Unmapped errors - should return 2 until BASH scripts map them to other values.
RUN_MEMMAP_NOT_SUPPORTED = 2,
RUN_BAD_TITLE_ID = 2,
RUN_NO_CAFE_DATA_DIR = 2,
RUN_D_OPTION_USED = 2,
ON_DATA_DIR_MISSING = 2,
}
internal class CafeXReturn
{
public CAFEX_ERROR error;
public int appExitCode;
///
/// Constructor for CAFEX_ERROR class. Sets the Error to the user-specified error.
/// Exit code = 0.
///
///
public CafeXReturn(CAFEX_ERROR err)
{
error = err;
appExitCode = 0;
}
///
/// Constructor for CAFEX_ERROR class. Sets the application error code
/// according to the app's actual exit code. Error = OK.
///
///
public CafeXReturn(int exitcode)
{
error = CAFEX_ERROR.OK;
appExitCode = exitcode;
}
///
/// Constructor for CAFEX_ERROR class.
///
/// The Error CafeX should return
/// The exit code of the application
public CafeXReturn(CAFEX_ERROR err, int exitcode)
{
error = err;
appExitCode = exitcode;
}
public CafeXReturn()
{
error = CAFEX_ERROR.UNEXPECTED;
appExitCode = -1;
}
}
partial class Program
{
#if TIMER
public static PerfTimer CafeXSetupTime = new PerfTimer("CafeXSetupTime");
#endif
private enum TargetEncoding
{
ConsoleIn,
ConsoleOut,
CafeOut
}
// This variable is set if cafex is called with '-q'.
internal static bool quiet_mode = false;
// This variable is set if we want to ignore the internal setting of quiet mode
internal static bool ignore_quiet_mode = false;
// this variable is set if cafex is called with -noinput
internal static bool no_console = false;
// The cafex command to execute.
internal static string command = string.Empty;
// Global software and firmware versions
internal static int sw_ver_flat = 0;
internal static int fw_ver_flat = 0;
internal static readonly uint RETRY_ATTEMPS = 3;
internal const int DEVKIT_HELP_REQUEST_TIMEOUT = 5000;
internal const int DEVKIT_HELP_RESPONSE_TIMEOUT = 5000;
internal const int COSDEBUG_FASTLAUNCH_RETRIES = 3;
internal const int COSDEBUG_FASTLAUNCH_TIMEOUT = 10000; // 10 seconds
internal const int COSDEBUG_FASTLAUNCH_DELAY = 2000; // 2 seconds
internal const int COSDEBUG_KILLRESTART_RETRIES = 1;
internal const int COSDEBUG_KILLRESTART_TIMEOUT = 1000; // 1 second
internal const int COSDEBUG_KILLRESTART_DELAY = 1000; // 1 second
internal const int KILLRESTART_SOFTLAUNCH_TIMEOUT = 10000; // 10 seconds
internal static readonly TimeSpan RETRY_TIMESPAN = new TimeSpan(0, 0, 1);
internal static readonly TimeSpan COS_REBOOT_DELAY = new TimeSpan(0, 0, 5);
internal static readonly TimeSpan COS_READY_DELAY = new TimeSpan(0, 0, 12);
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.";
static public Encoding cafeOutEncoding;
///
/// When true, indicates PCFS server is running in client mode for TCP connection with CAT-R.
///
internal const int DEFAULT_PCFS_TCP_PORT = 5999;
static bool PcfsTcpOnly = false;
static IPEndPoint PcfsIpEndpoint = null;
static string DebugSerialPort;
// static private bool CALLED_FROM_CYGWIN = false;
///
/// Cleans up input argument array by removing any empty ones, and
/// breaking up those that are not spaced out properly (eg. -t0x00...)
/// which makes the argument parsing logic more reliable.
///
/// Input argument array of strings.
/// New array of arguments.
static private string[] CleanupArguments(string[] args)
{
#if DEBUG
Log.WriteLine("CleanEmptArguments started.");
#endif
List newList = new List();
int rpxIndex = int.MaxValue;
bool rpxFound = false;
for (int counter = 0; counter < args.Length; ++counter)
{
string argument = args[counter];
if (argument != null)
{
bool breakUpParameter = false;
bool notAfterDashA = false;
if (argument.Length > 2)
{
if (!rpxFound)
{
if (argument.ToLowerInvariant().EndsWith(".rpx") ||
argument.ToLowerInvariant().EndsWith(".elf"))
{
rpxFound = true;
rpxIndex = counter;
}
}
if (counter < rpxIndex)
{
// Special Cases
switch (argument.Substring(0, 2))
{
case "-t":
notAfterDashA = (counter == 0) || (!(args[counter - 1].StartsWith("-a")));
if (notAfterDashA)
{
breakUpParameter = true;
}
break;
case "-w":
notAfterDashA = (counter == 0) || (!(args[counter - 1].StartsWith("-a")));
if (notAfterDashA)
{
breakUpParameter = true;
}
break;
default:
// Nothing to do : breakUpParameter = false
break;
}
}
}
if (breakUpParameter)
{
// This is a case where the cafe argument and setting are not spaced out properly
newList.Add(argument.Substring(0, 2));
newList.Add(argument.Substring(2));
}
else
{
// Just add the argument - it is good!
newList.Add(argument);
}
}
}
return newList.ToArray();
}
///
/// Sets either the Console.In or Console.Out encoding using the encoder name or codepage provided.
///
/// Console In or Console Out.
/// String representing the codepage or encoding name.
/// True if encoding was changed, false if not.
static private bool SetEncoding(TargetEncoding target, string encoderValue)
{
if (string.IsNullOrEmpty(encoderValue))
{
// It will use the default encoders.
return false;
}
int codePage = 0;
bool returnVal = false;
string defaultEncodingCodePage = Encoding.Default.WindowsCodePage.ToString();
// could be a string as the name of the encoding
if (encoderValue.ToLowerInvariant().Equals("default"))
{
encoderValue = defaultEncodingCodePage;
}
//handle situation where user provides the number for the codepage
if (int.TryParse(encoderValue, out codePage))
{
if (codePage > 0)
{
//It was given a valid codepage
try
{
switch (target)
{
case TargetEncoding.ConsoleIn:
Console.InputEncoding = Encoding.GetEncoding(codePage);
returnVal = true;
break;
case TargetEncoding.ConsoleOut:
Console.OutputEncoding = Encoding.GetEncoding(codePage);
returnVal = true;
break;
case TargetEncoding.CafeOut:
Program.cafeOutEncoding = Encoding.GetEncoding(codePage);
returnVal = true;
break;
}
}
catch (ArgumentOutOfRangeException)
{ }
catch (ArgumentException)
{ }
catch (NotSupportedException)
{ }
catch (IOException)
{ }
catch (System.Security.SecurityException)
{ }
}
}
else //did they pass in the string representation of the encoding?
{
try
{
switch (target)
{
case TargetEncoding.ConsoleIn:
Console.InputEncoding = Encoding.GetEncoding(encoderValue);
returnVal = true;
break;
case TargetEncoding.ConsoleOut:
Console.OutputEncoding = Encoding.GetEncoding(encoderValue);
returnVal = true;
break;
case TargetEncoding.CafeOut:
Program.cafeOutEncoding = Encoding.GetEncoding(encoderValue);
returnVal = true;
break;
}
}
catch (ArgumentException)
{ }
catch (NotSupportedException)
{ }
catch (IOException)
{ }
catch (System.Security.SecurityException)
{ }
}
if (!returnVal && !encoderValue.Equals(defaultEncodingCodePage))
{
// Failed with the user request, trying to default
Console.WriteLine("cafex : unable to set {0} encoding to {1}. Reverting to default {2}.", target.ToString(), encoderValue, defaultEncodingCodePage);
return SetEncoding(target, defaultEncodingCodePage);
}
return returnVal;
}
private static HandlerRoutine cchr = null;
private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
{
Console.WriteLine("\ncafex: terminated");
Environment.Exit(0);
return true;
}
static int Main(string[] args)
{
Console.WriteLine("-------------------------- CAFEX NEW INSTANCE ----------------------------------");
Console.WriteLine("Main started. Command line = " + Environment.CommandLine);
#if TIMER
string logFilePath = "%CAFE_TEMP%\\%SESSION_PATH_PREFIX%caferun\\CafeXPerf.log";
Log.OpenLogFile(logFilePath);
CafeXSetupTime.Start();
#endif
cchr = new HandlerRoutine(ConsoleCtrlCheck);
SetConsoleCtrlHandler(cchr, true);
GC.KeepAlive(cchr);
#if DEBUG
//System.Diagnostics.Debugger.Break();
string logFilePath = "%CAFE_TEMP%\\%SESSION_PATH_PREFIX%caferun\\CafeX.log";
Log.OpenLogFile(logFilePath);
Log.WriteLine("-------------------------- CAFEX NEW INSTANCE ----------------------------------");
Log.WriteLine("Main started. Command line = " + Environment.CommandLine);
#endif
if(CAFE_PROFILE.value != null && CAFE_PROFILE.value == "1")
{
Console.WriteLine("CafeX: Profiling is enabled!");
CafeXEventtLog.Instance.WriteToEventLog(500, DateTime.Now, EventStatus.BEGIN, EventProcess.CAFEX, "CAFEX NEW INSTANCE");
}
StreamWriter output_writer = null;
TextWriter old_out = null;
TextWriter old_err = null;
FileUtil.GatherEnvironment();
// Setting default encoding variables
if (string.IsNullOrEmpty(CAFEX_STDIN_ENCODING.value))
{
CAFEX_STDIN_ENCODING.value = "default";
}
if (string.IsNullOrEmpty(CAFEX_STDOUT_ENCODING.value))
{
// Standard out should be different depending on the country.
int defaultEncodingCodePage = Encoding.Default.WindowsCodePage;
switch(defaultEncodingCodePage)
{
case 932: //Japan - Shift_JIS
CAFEX_STDOUT_ENCODING.value = "default";
break;
default:
CAFEX_STDOUT_ENCODING.value = "utf-8";
break;
}
}
try
{
// Check if "cafex" was the only thing in the command line
if (args == null || args.Length == 0)
{
PrintCafeXHelp();
return new CafeXReturn(CAFEX_ERROR.OK).appExitCode;
}
int arg_pos = 0;
args = CleanupArguments(args);
// Handle the arguments to cafex in any order.
// Valid options are -q and -o.
while (true)
{
if (args.Length > arg_pos && args[arg_pos] == "-q")
{
quiet_mode = true;
++arg_pos;
}
// Format for -o is -o [filename], so need to make sure there are 2 more arguments.
else if (args.Length > arg_pos + 1 && args[arg_pos] == "-o")
{
if (!ArgumentChecks.IsArgumentValidSetting(args, arg_pos + 1))
{
PrintCafeXHelp();
return (int)CAFEX_ERROR.BAD_ARGUMENT;
}
string output_file = PathConverter.Windowsify(args[arg_pos + 1]);
if (!FileUtil.IsValidFile(output_file, false))
{
Console.WriteLine("Invalid file name!\n");
PrintCafeXHelp();
return (int) CAFEX_ERROR.BAD_ARGUMENT;
}
arg_pos += 2;
FileStream fs = null;
try
{
fs = new FileStream(output_file, FileMode.Create);
}
catch (System.UnauthorizedAccessException)
{
Console.WriteLine(PERMISSION_ERROR_TEXT);
PrintCafeXHelp();
return (int)CAFEX_ERROR.BAD_ARGUMENT;
}
catch (System.Security.SecurityException)
{
Console.WriteLine(PERMISSION_ERROR_TEXT);
PrintCafeXHelp();
return (int)CAFEX_ERROR.BAD_ARGUMENT;
}
old_out = Console.Out;
old_err = Console.Error;
output_writer = new StreamWriter(fs);
Console.SetOut(output_writer);
Console.SetError(output_writer);
}
else if (args.Length > arg_pos + 1 && args[arg_pos] == "-I")
{
// Get the STDIN encoding
if (!ArgumentChecks.IsArgumentValidSetting(args, arg_pos + 1))
{
PrintCafeXHelp();
return (int)CAFEX_ERROR.BAD_ARGUMENT;
}
CAFEX_STDIN_ENCODING.value = args[arg_pos + 1];
arg_pos += 2;
}
else if (args.Length > arg_pos + 1 && args[arg_pos] == "-O")
{
if (!ArgumentChecks.IsArgumentValidSetting(args, arg_pos + 1))
{
PrintCafeXHelp();
return (int)CAFEX_ERROR.BAD_ARGUMENT;
}
CAFEX_STDOUT_ENCODING.value = args[arg_pos + 1];
arg_pos += 2;
}
#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.
else if(args.Length > arg_pos + 1 && args[arg_pos] == "-pcfsonly")
{
if (!ArgumentChecks.IsArgumentValidSetting(args, arg_pos + 1))
{
PrintCafeXHelp();
return (int)CAFEX_ERROR.BAD_ARGUMENT;
}
PcfsTcpOnly = true;
arg_pos++;
PcfsIpEndpoint = ParseIpEndPoint(args[arg_pos]);
if (PcfsIpEndpoint == null)
{
PrintCafeXHelp();
return (int)CAFEX_ERROR.BAD_ARGUMENT;
}
else
{
arg_pos++;
}
}
#endif
else if(args.Length > arg_pos + 1 && args[arg_pos] == "-debugport")
{
if (!ArgumentChecks.IsArgumentValidSetting(args, arg_pos + 1))
{
PrintCafeXHelp();
return (int)CAFEX_ERROR.BAD_ARGUMENT;
}
DebugSerialPort = TryParseComPort(args[++arg_pos]);
if(string.IsNullOrEmpty(DebugSerialPort))
{
PrintCafeXHelp();
return (int)CAFEX_ERROR.BAD_ARGUMENT;
}
arg_pos++;
}
else if (args[arg_pos] == "-noinput")
{
no_console = true;
arg_pos++;
}
else
{
// If it's anything else then it's the command.
break;
}
}
// Get the command.
if (args.Length > arg_pos)
{
command = args[arg_pos].ToLowerInvariant();
++arg_pos;
}
CAFEX_ERROR ret = InitialEnvVarSetup();
if (ret != CAFEX_ERROR.OK)
{
return (int)ret;
}
ret = CheckCafeRootAndHostBridgeVars();
if (ret != CAFEX_ERROR.OK)
{
return (int)ret;
}
if (!string.IsNullOrEmpty(NO_CONSOLE.value) && NO_CONSOLE.value == "1")
{
// Disable console input if it was specified in the enviromnment
no_console = true;
}
// Sets the quiet mode if CAFE_CONSOLE is toucan and
// "-q" parameter was not provided by caller.
if (!quiet_mode && (CAFE_CONSOLE.value != "cattoucan"))
{
quiet_mode = true;
}
else if (CAFE_CONSOLE.value == "cattoucan")
{
// If explicitly setting console to cattoucan, it
// should override even if the "-q" parameter was
// set. This solves validation.sh tests running
// in softlaunch mode.
quiet_mode = false;
}
// Print the encoding
if (!command.Equals("stop"))
{
// Set encodings
if (CafeX.Program.IsInputRedirected)
{
Console.WriteLine("cafex : input is redirected. Using legacy keyboard input and not changing encodings.");
}
else
{
SetEncoding(TargetEncoding.ConsoleIn, CAFEX_STDIN_ENCODING.value);
SetEncoding(TargetEncoding.ConsoleOut, CAFEX_STDOUT_ENCODING.value);
Console.WriteLine("cafex input/keyboard encoding code page: {0}", Console.InputEncoding.CodePage);
Console.WriteLine("cafex output/screen encoding code page: {0}", Console.OutputEncoding.CodePage);
}
//always want to set this.
SetEncoding(TargetEncoding.CafeOut, CAFE_OUTPUT_ENCODING.value);
if (Program.cafeOutEncoding != null)
{
Console.WriteLine("cafe output/stream encoding code page: {0}", Program.cafeOutEncoding.CodePage);
}
}
int args_to_copy = args.Length - arg_pos;
string[] args_for_command = new string[args_to_copy];
Array.Copy(args, arg_pos, args_for_command, 0, args_to_copy);
CafeXReturn exitCode = DoCommand(args_for_command, quiet_mode);
// Only output general CafeX help if user inputted a bad command.
// If they inputted an invalid option to a valid command, then that command's method should output its own help text.
if (exitCode.error == CAFEX_ERROR.BAD_COMMAND)
{
Console.WriteLine("cafex : Bad command.");
PrintCafeXHelp();
}
if (exitCode.error != CAFEX_ERROR.OK)
return (int)exitCode.error;
else
return exitCode.appExitCode;
}
catch (Exception e)
{
Console.OutputEncoding.GetEncoder().Reset();
string errorFile = "cafexErrorLog_" + System.Diagnostics.Process.GetCurrentProcess().Id.ToString() + "_" +
DateTime.Now.ToString("yyyyMMddHHmm") + ".log";
string errorLog = "CafeX error log\nDate:" + DateTime.Now + "\nError message:" +
e.Message + "\nStack trace:" + e.StackTrace + "\n";
if (string.IsNullOrEmpty(CAFE_TEMP.value))
{
errorFile = Path.Combine(Directory.GetCurrentDirectory(), SESSION_PATH_PREFIX.value + errorFile);
}
else
{
errorFile = Path.Combine(CAFE_TEMP.value, SESSION_PATH_PREFIX.value + errorFile);
}
try
{
System.IO.File.WriteAllText(errorFile, errorLog);
Console.WriteLine("CafeX error log was saved as '{0}'", errorFile);
}
catch (IOException)
{
Console.WriteLine("Unable to write the file '{0}'", errorFile);
Console.WriteLine("Writing log to console window:");
Console.WriteLine(errorLog);
}
Console.WriteLine("CafeX encountered an error executing the command.");
Console.WriteLine("Please confirm this is a supported command and has the correct arguments.");
Console.WriteLine("Error message: " + e.Message);
return (int)CAFEX_ERROR.UNEXPECTED;
}
finally
{
// Re-set the original stdout and stderr.
if (output_writer != null)
{
Console.SetOut(old_out);
Console.SetError(old_err);
output_writer.Close();
}
#if DEBUG || TIMER
Log.WriteLine("-------------------------- END OF CAFEX INSTANCE ----------------------------------");
Log.CloseLogFile();
#endif
if (CAFE_PROFILE.value != null && CAFE_PROFILE.value == "1")
{
CafeXEventtLog.Instance.WriteToEventLog(501, DateTime.Now, EventStatus.END, EventProcess.CAFEX, "END OF CAFEX INSTANCE");
}
}
}
internal static CAFEX_ERROR CheckCafeRootAndHostBridgeVars()
{
#if DEBUG
Log.WriteLine("CheckCafeRootAndHostBridgeVars started.");
#endif
CAFEX_ERROR ret = CAFEX_ERROR.OK;
if (CAFE_ROOT.value == null)
{
Console.WriteLine("CafeX Error: CAFE_ROOT environment variable is not set.");
ret = CAFEX_ERROR.NO_SDK_HOSTBRIDGE_ENV_VARS;
}
if (MION_BRIDGE_TOOLS.value == null && !PcfsTcpOnly)
{
Console.WriteLine("CafeX Error: MION_BRIDGE_TOOLS environment variable is not set.");
ret = CAFEX_ERROR.NO_SDK_HOSTBRIDGE_ENV_VARS;
}
return ret;
}
internal static CAFEX_ERROR CheckDevkitVars()
{
#if DEBUG
Log.WriteLine("CheckDevkitVars started. Retuns CAFEX_ERRO.OK if BRIDGE_CURRENT_NAME and BRIDGE_CURRENT_IP_ADDRESS are provided.");
#endif
CAFEX_ERROR ret = CAFEX_ERROR.OK;
if (!PcfsTcpOnly)
{
if (string.IsNullOrEmpty(BRIDGE_CURRENT_NAME.value))
{
Console.WriteLine("CafeX Error: BRIDGE_CURRENT_NAME environment variable is not set.");
ret = CAFEX_ERROR.NO_DEVKIT_VARS;
}
if (string.IsNullOrEmpty(BRIDGE_CURRENT_IP_ADDRESS.value))
{
Console.WriteLine("CafeX Error: BRIDGE_CURRENT_IP_ADDRESS environment variable is not set.");
ret = CAFEX_ERROR.NO_DEVKIT_VARS;
}
}
return ret;
}
private static IPEndPoint ParseIpEndPoint(string value)
{
string ipValue = string.Empty;
string portValue = string.Empty;
int portIndex = value.IndexOf(':');
if (portIndex >= 0)
{
ipValue = value.Substring(0, portIndex);
portValue = value.Substring(portIndex + 1);
}
else
{
ipValue = value;
portValue = DEFAULT_PCFS_TCP_PORT.ToString();
}
IPAddress ip;
int port;
if (IPAddress.TryParse(ipValue, out ip) &&
int.TryParse(portValue, out port))
{
//the parsing will make sure no garbage was in the port string
//now make sure the port is actually valid between 1 and 65536
if(port <= 0 || port > ushort.MaxValue)
{
port = DEFAULT_PCFS_TCP_PORT;
}
return new IPEndPoint(ip, port);
}
return null;
}
///
/// Attempts to parse the COM port number from the given string. Strings looked for include
/// "COM 1", "1", and "COM1".
///
/// String to parse.
/// if the value cannot be parsed, otherwise an integer number string representing the port.
private static string TryParseComPort(string value)
{
if(string.IsNullOrEmpty(value))
{
return string.Empty;
}
var tokens = value.Split(new char[] { ' ' });
string portNumber = string.Empty;
if(tokens.Length == 1)
{
if(tokens[0].StartsWith("com", StringComparison.InvariantCultureIgnoreCase) && tokens[0].Length > 3)
{
portNumber = tokens[0].Substring(3);
}
else
{
portNumber = tokens[0];
}
}
else if(tokens.Length == 2)
{
if(tokens[0].Equals("com", StringComparison.InvariantCultureIgnoreCase))
{
portNumber = tokens[1];
}
}
int _;
if(int.TryParse(portNumber, out _))
{
return portNumber;
}
return string.Empty;
}
internal static CafeXReturn DoCommand(string[] args, bool quiet_mode)
{
#if DEBUG
Log.WriteLine("DoCommand started. quiet_mode=" + quiet_mode.ToString());
string argString = null;
if (args != null)
{
foreach (string arg in args)
{
argString += arg + " ";
}
}
Log.WriteLine("Arguments=" + argString);
#endif
CafeXReturn ret = new CafeXReturn(CAFEX_ERROR.OK);
switch (command.ToLowerInvariant())
{
case "-h":
case "help":
{
PrintCafeXHelp();
break;
}
case "stop":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
stop(args);
break;
}
case "run":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
ret = run(args, true, true);
break;
}
case "discrun":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
ret = discrun(args, true, true);
break;
}
case "headlessrun":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
ret = headlessrun(args);
break;
}
case "on":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
ret = on(args, true, true);
break;
}
case "update":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
ret.error = update(args);
break;
}
case "revert":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
ret.error = revert(args);
break;
}
case "recover":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
ret.error = recover(args);
break;
}
case "cleardata":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
ret.error = cleardata(args);
break;
}
case "setbootmode":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
ret.error = setbootmode(args);
break;
}
case "syncsession":
{
ret.error = CheckDevkitVars();
if (ret.error != CAFEX_ERROR.OK)
break;
ret.error = syncsession(args);
break;
}
case "makeaoc":
case "makemaster":
case "makedisc":
case "makedownload":
{
Console.WriteLine("CafeX Error: This function is no longer supported. Please use makecfmaster.exe to use commands for mastering.");
ret.error = CAFEX_ERROR.BAD_COMMAND;
break;
}
case "findbridges":
{
break;
}
case "version":
{
System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly();
// With an AssemblyVersion of ..*, the build is the number of days since 1/1/2000
// and the revision is the number of seconds since midnight
Version asmVer = asm.GetName().Version;
DateTime buildDate = new DateTime(2000, 1, 1); // Start at 1/1/2000
buildDate = buildDate.AddDays(asmVer.Build); // Add the days since 1/1/2000
buildDate = buildDate.AddSeconds(asmVer.Revision * 2); // Add the number of seconds since midnight
System.Diagnostics.FileVersionInfo fileVer = System.Diagnostics.FileVersionInfo.GetVersionInfo(asm.Location);
//NEVER EVER CHANGE THE NEXT TWO LINES. YOUR LIFE MAY BE FORFEIT IF YOU DO.
Console.WriteLine("cafex {0}", fileVer.FileVersion);
Console.WriteLine("{0}",buildDate.ToString("MM/dd/yyyy hh:mm tt",DateTimeFormatInfo.InvariantInfo));
ret.error = CAFEX_ERROR.OK;
break;
}
case "launch":
{
ret.error = launchTitle(args);
break;
}
case "install":
{
ret.error = install(args);
break;
}
default:
{
if (string.IsNullOrEmpty(command) || command.StartsWith("-"))
{
Console.WriteLine("CafeX Error: Missing command. Please try \"cafex help\" for usage information.");
}
else
{
Console.WriteLine("CafeX Error: Invalid command '{0}'. Please try \"cafex help\" for usage information.", command);
}
ret.error = CAFEX_ERROR.BAD_COMMAND;
break;
}
}
return ret;
}
private enum FileType { Unknown, Disk, Char, Pipe };
private enum StdHandle { Stdin = -10, Stdout = -11, Stderr = -12 };
[DllImport("kernel32.dll")]
private static extern FileType GetFileType(IntPtr hdl);
[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(StdHandle std);
[DllImport("Kernel32")]
public static extern Boolean SetConsoleCtrlHandler(HandlerRoutine Handler,
Boolean Add);
// sent to the handler routine.
public enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
// A delegate type to be used as the handler routine
// for SetConsoleCtrlHandler.
public delegate bool HandlerRoutine(CtrlTypes CtrlType);
internal static bool IsOutputRedirected
{
get
{
return FileType.Char != GetFileType(GetStdHandle(StdHandle.Stdout));
}
}
internal static bool IsInputRedirected
{
get
{
return FileType.Char != GetFileType(GetStdHandle(StdHandle.Stdin));
}
}
internal static void PrintCafeXHelp()
{
Console.WriteLine("Usage: cafex [options] [command] [arguments]");
Console.WriteLine();
Console.WriteLine("options:");
Console.WriteLine(" -q The command will be executed, and CafeX returns immediately. No output is collected.");
Console.WriteLine(" -o All output is redirected to the specified file.");
Console.WriteLine(" -I The encoding for the console standard in. Valid encoding arguments are: codepage");
Console.WriteLine(" (eg. 65001, 932, etc), or the encoding name (eg. UTF-8, shift_jis, etc).");
Console.WriteLine(" This setting overrides the CAFEX_STDIN_ENCODING environment variable.");
Console.WriteLine(" Note: If the environment variable and this switch is not provided, the default windows encoding is used.");
Console.WriteLine(" -O The encoding for the console standard out. Valid encoding arguments are: codepage");
Console.WriteLine(" (eg. 65001, 932, etc), or the encoding name (eg. UTF-8, shift_jis, etc).");
Console.WriteLine(" This setting overrides the CAFEX_STDOUT_ENCODING environment variable.");
Console.WriteLine(" Note: If the environment variable and this switch is not provided, UTF-8 is default.");
Console.WriteLine(" -noinput Disables reading of the PC keyboard or STDIN if redirected.");
Console.WriteLine(" Note: Setting the environment variable NO_CONSOLE=1 has the same effect.");
Console.WriteLine();
Console.WriteLine("commands:");
Console.WriteLine(" stop Stop the devkit.");
Console.WriteLine(" run Run an application.");
Console.WriteLine(" discrun Run an application using block level interface.");
Console.WriteLine(" headlessrun Run an application disconnected from the network.");
Console.WriteLine(" on Start System Config Tool.");
Console.WriteLine(" update Update devkit firmware.");
Console.WriteLine(" revert Downgrade devkit OS to an older SDK.");
Console.WriteLine(" recover Attempt to recover a devkit from corrupted OS.");
Console.WriteLine(" cleardata Clear the user data in NAND.");
Console.WriteLine(" setbootmode Change the boot mode of the devkit.");
Console.WriteLine(" syncsession Sync the session data directory with the default one.");
Console.WriteLine(" launch Launches a title through system config tool.");
Console.WriteLine(" install Installs title to emulated MLC.");
Console.WriteLine();
Console.WriteLine(" version Prints version and build information then exits.");
Console.WriteLine();
Console.WriteLine("arguments:");
Console.WriteLine("Execute cafex [command] -h for information on valid arguments for each command.");
}
}
}