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; 13 using System.Collections.Generic; 14 using System.Diagnostics; 15 using System.IO; 16 using System.Linq; 17 using System.Text; 18 using System.Xml; 19 20 namespace CafeX 21 { 22 /// <summary> 23 /// This class encapsulates the environment variables required by Cafe 24 /// </summary> 25 internal class EnvVar 26 { 27 private string var_name; 28 private string var_value; 29 private static Dictionary<string, EnvVar> dictionaryOfVariables = new Dictionary<string, EnvVar>(); 30 private static Dictionary<string, string> dictionaryOfSavedValues = new Dictionary<string, string>(); 31 BackUpCurrentEnvironment()32 public static void BackUpCurrentEnvironment() 33 { 34 #if DEBUG 35 Log.WriteLine("BackUpCurrentEnvironment started."); 36 #endif 37 dictionaryOfSavedValues.Clear(); 38 39 foreach (string variableName in dictionaryOfVariables.Keys) 40 { 41 dictionaryOfSavedValues.Add(variableName, dictionaryOfVariables[variableName].value); 42 } 43 #if DEBUG 44 Log.WriteLine("BackUpCurrentEnvironment completed."); 45 #endif 46 } 47 RestoreSavedEnvironment()48 public static void RestoreSavedEnvironment() 49 { 50 #if DEBUG 51 Log.WriteLine("RestoreSavedEnvironment started."); 52 #endif 53 if (dictionaryOfSavedValues.Count == 0) 54 { 55 #if DEBUG 56 Log.WriteLine("RestoreSavedEnvironment ignored. No backup available."); 57 #endif 58 59 // No backup had been made, so there is no way to restore. Just return. 60 return; 61 } 62 63 foreach (string variableName in dictionaryOfVariables.Keys) 64 { 65 // Set the respective envVar instance in the dictionary of variables. 66 if (dictionaryOfSavedValues.ContainsKey(variableName)) 67 { 68 EnvVar temp = dictionaryOfVariables[variableName]; 69 temp.value = dictionaryOfSavedValues[variableName]; 70 } 71 } 72 #if DEBUG 73 Log.WriteLine("RestoreSavedEnvironment completed successfully."); 74 #endif 75 } 76 EnvVar(string name)77 public EnvVar(string name) 78 { 79 if (string.IsNullOrEmpty(name)) 80 { 81 throw new ArgumentNullException("name cannot be null."); 82 } 83 84 var_name = name; 85 var_value = Environment.GetEnvironmentVariable(var_name); 86 // Store the environment variable, so it can be backed-up and restored at will 87 EnvVar.dictionaryOfVariables.Add(name, this); 88 } 89 90 public string value 91 { 92 get 93 { 94 var_value = Environment.GetEnvironmentVariable(var_name); 95 return var_value; 96 } 97 98 set 99 { 100 var_value = value; 101 Environment.SetEnvironmentVariable(var_name, var_value); 102 } 103 } 104 AddToVar(string addition, char delimiter)105 public void AddToVar(string addition, char delimiter) 106 { 107 if (value == null) 108 value = addition; 109 else 110 value = value + delimiter + addition; 111 } 112 RemoveFromVar(string removal, char delimiter)113 public void RemoveFromVar(string removal, char delimiter) 114 { 115 if (string.IsNullOrEmpty(value)) 116 { 117 #if DEBUG 118 Log.WriteLine("RemoveFromVar: removal is empty!"); 119 #endif 120 121 return; 122 } 123 if (string.IsNullOrEmpty(delimiter.ToString())) 124 { 125 #if DEBUG 126 Log.WriteLine("RemoveFromVar: delimiter is null!"); 127 #endif 128 129 return; 130 } 131 132 int position = value.IndexOf(removal); 133 134 if (position >= 0) 135 { 136 int len = removal.Length; 137 if (position > 0) 138 { 139 // There is more text in value than only the one we want to remove. 140 // ex: data1 data2 removal data3 141 // ex: data1 removal. 142 // In those scenarios, we need to remove the delimiter char before, since AddToVar does value = value + delimiter + addition; 143 --position; 144 ++len; 145 } 146 147 value = value.Remove(position, len); 148 } 149 else 150 { 151 #if DEBUG 152 Log.WriteLine("RemoveFromVar: \"" + removal + "\" does not exist in the arg list!"); 153 #endif 154 return; 155 } 156 } 157 158 159 } 160 161 /// <summary> 162 /// This class provides enables CafeX to work in both Windows and Cygwin environments by converting paths to the Windows format. 163 /// </summary> 164 internal static class PathConverter 165 { 166 /// <summary> 167 /// Ensure the provide path is rooted. 168 /// </summary> 169 /// <param name="input">Path to a file.</param> 170 /// <returns>Rooted path.</returns> Root(string input)171 internal static string Root(string input) 172 { 173 if (Path.IsPathRooted(input)) 174 { 175 return input; 176 } 177 else 178 { 179 return Environment.CurrentDirectory + "\\" + input; 180 } 181 } 182 183 /// <summary> 184 /// Convert a Cygwin path to a Windows path. 185 /// If the input is already a valid Windows path, it is returned unchanged. 186 /// </summary> 187 /// <param name="input">Cywin path.</param> 188 /// <returns>Windows equivalent of the provided Cygwin path.</returns> Windowsify(string input)189 internal static string Windowsify(string input) 190 { 191 #if DEBUG 192 Log.WriteLine("Windowsify input = " + input); 193 #endif 194 if (string.IsNullOrEmpty(input)) 195 { 196 return input; 197 } 198 199 int cygdriverIndex = -1; 200 201 cygdriverIndex = input.IndexOf(@"/cygdrive///"); 202 203 if ((cygdriverIndex == 0 || cygdriverIndex == 1 ) && 204 !string.IsNullOrEmpty(CafeX.Program.CYGWIN_PATH.value) ) 205 { 206 // Special treatment 207 input = input.Replace( @"/cygdrive///", Path.Combine(CafeX.Program.CYGWIN_PATH.value, "cygdrive/") ); 208 // it should continue with normal conversion to fix the \\ 209 } 210 211 // Normal conversion -> path starts with /cygdriver/<stuff> or "/cygdriver/<stuff>" 212 cygdriverIndex = input.IndexOf("/cygdrive/"); 213 if (cygdriverIndex == 0 || cygdriverIndex == 1) 214 { 215 char drive_letter = input.ToCharArray()[10 + cygdriverIndex]; 216 input = input.Remove(0, 11 + cygdriverIndex); 217 input = input.Replace('/', '\\'); 218 input = drive_letter + ":" + input; 219 } 220 else 221 { 222 // This should be a path that doesn't start with cygdrive, 223 // so just make sure it has the proper \\ instead of / 224 input = input.Replace('/', '\\'); 225 } 226 227 #if DEBUG 228 Log.WriteLine("Windowsify return = " + input); 229 #endif 230 return input; 231 } 232 } 233 234 /// <summary> 235 /// This class enables external processes to be started and monitored. 236 /// </summary> 237 internal static class ProcessExecutor 238 { 239 /// <summary> 240 /// Creates a process, starts it, wait for its execution and return its exit code. 241 /// </summary> 242 /// <param name="filename">Path to the executable to start on a different process.</param> 243 /// <param name="arguments">Arguments to the executable.</param> 244 /// <returns>The process' exit code.</returns> DoProcess(string filename, string arguments)245 internal static int DoProcess(string filename, string arguments) 246 { 247 #if DEBUG 248 Log.WriteLine("ProcessorExecutor.DoProcess : " + filename + " " + arguments); 249 #endif 250 251 return DoProcess_WaitTime(filename, arguments, Int32.MaxValue); 252 } 253 254 /// <summary> 255 /// Creates and starts a new process, but return immediately the execution to the caller. 256 /// The child's stdout and errout is printed to this process' stdout. 257 /// </summary> 258 /// <param name="filename">Path to the executable to start on a different process.</param> 259 /// <param name="arguments">Arguments to the executable.</param> 260 /// <returns>0 if the process is created, -1 otherwise.</returns> DoProcess_DontWait(string filename, string arguments)261 internal static int DoProcess_DontWait(string filename, string arguments) 262 { 263 #if DEBUG 264 Log.WriteLine("ProcessorExecutor.DoProcess_DontWait : " + filename + " " + arguments); 265 #endif 266 267 try 268 { 269 Process p = new Process(); 270 ProcessStartInfo si = new ProcessStartInfo(filename); 271 si.UseShellExecute = false; 272 si.CreateNoWindow = true; 273 si.Arguments = arguments; 274 si.RedirectStandardError = true; 275 si.RedirectStandardOutput = true; 276 p.StartInfo = si; 277 // Won't wait, but need to connect stdout and stderr to this parent process. 278 p.OutputDataReceived += (sender, args) => Console.WriteLine(args.Data); 279 p.ErrorDataReceived += (sender, args) => Console.WriteLine(args.Data); 280 p.Start(); 281 p.BeginOutputReadLine(); 282 p.BeginErrorReadLine(); 283 return 0; 284 } 285 catch (Exception e) 286 { 287 Console.WriteLine("CafeX Error: Exception in process executor"); 288 Console.WriteLine(filename + " " + arguments); 289 Console.WriteLine(e.Message); 290 return -1; 291 } 292 } 293 294 /// <summary> 295 /// Creates a process, starts it, and waits for a user-specified amount of time, then returns 296 /// the process's exit code 297 /// </summary> 298 /// <param name="filename">Path to the executable to start on a different process.</param> 299 /// <param name="arguments">Arguments to the executable</param> 300 /// <param name="time">Time in milliseconds to wait. Max 32-bit number implies forever.</param> 301 /// <returns>The process' exit code</returns> DoProcess_WaitTime(string filename, string arguments, int time)302 internal static int DoProcess_WaitTime(string filename, string arguments, int time) 303 { 304 #if DEBUG 305 Log.WriteLine(String.Format("ProcessorExecutor.DoProcessWaitTime : {0} {1} {2}",filename,arguments,time)); 306 #endif 307 try 308 { 309 Process p = new Process(); 310 ProcessStartInfo si = new ProcessStartInfo(filename); 311 si.UseShellExecute = false; 312 si.CreateNoWindow = true; 313 si.Arguments = arguments; 314 si.RedirectStandardError = true; 315 si.RedirectStandardOutput = true; 316 p.StartInfo = si; 317 318 p.Start(); 319 320 if (!p.WaitForExit(time)) 321 { 322 //Well this sucks. Kill the process and hopefully the caller will try again 323 Log.WriteLine(String.Format("{0} didn't exit in time, so force kill it.", filename)); 324 p.Kill(); 325 p.WaitForExit(); // This had better work this time! 326 } 327 int ret = p.ExitCode; 328 p.Close(); 329 p.Dispose(); 330 p = null; 331 332 return ret; 333 } 334 catch (Exception e) 335 { 336 Console.WriteLine("CafeX Error: Exception in process executor"); 337 Console.WriteLine(filename + " " + arguments); 338 Console.WriteLine(e.Message); 339 return -1; 340 } 341 } 342 343 /// <summary> 344 /// Creates a process, starts it, wait for its execution and return its exit code. 345 /// The child's process stdout is captured and returned to the caller in the ouput variable. 346 /// </summary> 347 /// <param name="filename">Path to the executable to start on a different process.</param> 348 /// <param name="arguments">Arguments to the executable.</param> 349 /// <param name="output">Returns the child's process captured output the the caller.</param> 350 /// <returns>Chid's process exit code.</returns> DoProcess_GetOutput(string filename, string arguments, out string output)351 internal static int DoProcess_GetOutput(string filename, string arguments, out string output) 352 { 353 #if DEBUG 354 Log.WriteLine("ProcessorExecutor.DoProcess_GetOutput : " + filename + " " + arguments); 355 #endif 356 357 return DoProcess_GetOutput(filename, arguments, false, -1, out output); 358 } 359 360 /// <summary> 361 /// Creates a process, starts it, wait for its execution and return its exit code. 362 /// The child's process stdout is captured and returned to the caller in the ouput variable. 363 /// </summary> 364 /// <param name="filename">Path to the executable to start on a different process.</param> 365 /// <param name="arguments">Arguments to the executable.</param> 366 /// <param name="timeout">How long (in ms) to wait for the process to end. Set to values less than 1 to wait indefinately.</param> 367 /// <param name="output">Returns the child's process captured output the the caller.</param> 368 /// <returns>Chid's process exit code.</returns> DoProcess_GetOutput(string filename, string arguments, bool waitDataReceived, int timeout, out string output)369 internal static int DoProcess_GetOutput(string filename, string arguments, bool waitDataReceived, int timeout, out string output) 370 { 371 #if DEBUG 372 Log.WriteLine("ProcessorExecutor.DoProcess_GetOutput : " + filename + " " + arguments); 373 #endif 374 375 try 376 { 377 int ret; 378 bool isDataReceived = !(waitDataReceived); // If we want to wait for data received event (waitDataReceived=true), need to set isDataReceived = false. 379 System.Threading.ManualResetEvent dataReceivedEvent = new System.Threading.ManualResetEvent(false); 380 TimeSpan DATA_RECEIVE_FLUSH_TIMEOUT = new TimeSpan(0, 0, 2); 381 Process p = new Process(); 382 ProcessStartInfo si = new ProcessStartInfo(filename); 383 si.UseShellExecute = false; 384 si.CreateNoWindow = true; 385 si.Arguments = arguments; 386 StringBuilder outputTmp = new StringBuilder(); 387 DataReceivedEventHandler callback = (sender, e) => 388 { 389 if (e != null && e.Data != null) 390 { 391 outputTmp.AppendLine( e.Data ); 392 if (!isDataReceived) 393 { 394 isDataReceived = true; 395 dataReceivedEvent.Set(); 396 } 397 } 398 }; 399 400 si.RedirectStandardError = true; 401 si.RedirectStandardOutput = true; 402 p.StartInfo = si; 403 p.OutputDataReceived += callback; 404 p.ErrorDataReceived += callback; 405 p.Start(); 406 p.BeginOutputReadLine(); 407 p.BeginErrorReadLine(); 408 bool exitedProperly = true; 409 410 if (timeout > 0) 411 { 412 exitedProperly = p.WaitForExit(timeout); 413 if (!exitedProperly) 414 { 415 Console.WriteLine("ERROR: Process started for '" + filename + " " + arguments + "' did not finish in the expected timeout (" + timeout.ToString() + "ms)."); 416 Console.WriteLine("Dumping process std/out std/err until now:\n {0} \n", outputTmp.ToString()); 417 } 418 } 419 else 420 { 421 p.WaitForExit(); 422 } 423 424 if (exitedProperly) 425 { 426 ret = p.ExitCode; 427 } 428 else 429 { 430 ret = -1; 431 } 432 433 // Some output should have been received either in stdout or stderr, so wait 434 if (isDataReceived == false) 435 { 436 dataReceivedEvent.WaitOne(DATA_RECEIVE_FLUSH_TIMEOUT); 437 } 438 439 p.CancelOutputRead(); 440 p.CancelErrorRead(); 441 p.StartInfo.RedirectStandardOutput = false; 442 p.StartInfo.RedirectStandardError = false; 443 p.OutputDataReceived -= callback; 444 p.ErrorDataReceived -= callback; 445 446 p.Close(); 447 p.Dispose(); 448 p = null; 449 450 output = outputTmp.ToString(); 451 452 return ret; 453 } 454 catch (Exception e) 455 { 456 output = string.Empty; 457 Console.WriteLine("CafeX Error: Exception in process executor"); 458 Console.WriteLine(filename + " " + arguments); 459 Console.WriteLine(e.Message); 460 return -1; 461 } 462 } 463 464 /// <summary> 465 /// Creates a process, starts it, wait for its execution and return its exit code. 466 /// The child's process stdout and errout are set to a callback handler provided. 467 /// </summary> 468 /// <param name="filename">Path to the executable to start on a different process.</param> 469 /// <param name="arguments">Arguments to the executable.</param> 470 /// <param name="callback">An instance of DataReceivedEventHandler provided by the caller.</param> 471 /// <returns>Chid's process exit code.</returns> StartProcess_RedirectOutput(string filename, string arguments, DataReceivedEventHandler callback, bool waitCompletion)472 internal static int StartProcess_RedirectOutput(string filename, string arguments, DataReceivedEventHandler callback, bool waitCompletion) 473 { 474 #if DEBUG 475 Log.WriteLine("ProcessorExecutor.StartProcess_RedirectOutput : " + filename + " " + arguments); 476 #endif 477 478 try 479 { 480 Process p = new Process(); 481 ProcessStartInfo si = new ProcessStartInfo(filename); 482 si.UseShellExecute = false; 483 si.CreateNoWindow = true; 484 si.Arguments = arguments; 485 si.RedirectStandardError = true; 486 si.RedirectStandardOutput = true; 487 p.StartInfo = si; 488 p.OutputDataReceived += callback; 489 p.ErrorDataReceived += callback; 490 491 p.Start(); 492 493 p.BeginOutputReadLine(); 494 p.BeginErrorReadLine(); 495 496 if (waitCompletion) 497 { 498 p.WaitForExit(); 499 int ret = p.ExitCode; 500 p.CancelOutputRead(); 501 p.CancelErrorRead(); 502 p.StartInfo.RedirectStandardOutput = false; 503 p.StartInfo.RedirectStandardError = false; 504 p.OutputDataReceived -= callback; 505 p.ErrorDataReceived -= callback; 506 p.Close(); 507 p.Dispose(); 508 p = null; 509 510 return ret; 511 512 } 513 else 514 { 515 return 0; 516 } 517 518 } 519 catch (Exception e) 520 { 521 Console.WriteLine("CafeX Error: Exception in process executor"); 522 Console.WriteLine(filename + " " + arguments); 523 Console.WriteLine(e.Message); 524 return -1; 525 } 526 } 527 StartProcess_RedirectOutput(string filename, string arguments)528 internal static int StartProcess_RedirectOutput(string filename, string arguments) 529 { 530 #if DEBUG 531 Log.WriteLine("ProcessorExecutor.StartProcess_RedirectOutput : " + filename + " " + arguments); 532 #endif 533 534 try 535 { 536 Process p = new Process(); 537 ProcessStartInfo si = new ProcessStartInfo(filename); 538 si.UseShellExecute = false; 539 si.CreateNoWindow = true; 540 si.Arguments = arguments; 541 si.RedirectStandardError = true; 542 si.RedirectStandardOutput = true; 543 si.RedirectStandardInput = true; 544 p.StartInfo = si; 545 546 p.Start(); 547 548 p.BeginOutputReadLine(); 549 550 return 0; 551 } 552 catch (Exception e) 553 { 554 Console.WriteLine("CafeX Error: Exception in process executor"); 555 Console.WriteLine(filename + " " + arguments); 556 Console.WriteLine(e.Message); 557 return -1; 558 } 559 } 560 } 561 562 /// <summary> 563 /// This class allows string arrays to be combined into a single string with the delimiter of choice. 564 /// </summary> 565 internal static class StringCombiner 566 { MakeDelimitedString(string[] arr, char delimiter)567 internal static string MakeDelimitedString(string[] arr, char delimiter) 568 { 569 string ret = string.Empty; 570 if (arr != null) 571 { 572 for (int i = 0; i < arr.Length; ++i) 573 { 574 if (i == 0) 575 { 576 ret += arr[i]; 577 } 578 else 579 { 580 ret += delimiter + arr[i]; 581 } 582 } 583 } 584 585 return ret; 586 } 587 } 588 589 /// <summary> 590 /// This class is used to edit xml content files. 591 /// </summary> 592 internal static class XmlHandler 593 { 594 /// <summary> 595 /// Traverses the xml document from the provided root node until it finds the node that matches the provided name. 596 /// </summary> 597 /// <param name="root_name">Root node</param> 598 /// <param name="node_name">Name of the node being searched</param> 599 /// <returns>The node that matches the provided node name, null if it was not found.</returns> GetXmlNodeRecursive(XmlNode root_name, string node_name)600 internal static XmlNode GetXmlNodeRecursive(XmlNode root_name, string node_name) 601 { 602 if (root_name.Name == node_name) 603 return root_name; 604 605 XmlNode ret = null; 606 foreach (XmlNode child in root_name.ChildNodes) 607 { 608 ret = GetXmlNodeRecursive(child, node_name); 609 if (ret != null) 610 break; 611 } 612 613 return ret; 614 } 615 616 /// <summary> 617 /// Check if a node by the provided name exists inside an xml document. 618 /// </summary> 619 /// <param name="file_name">Full path to the xml document.</param> 620 /// <param name="node_name">Name of the node to search.</param> 621 /// <returns>True if the node was found, false otherwise.</returns> NodeExists(string file_name, string node_name)622 internal static bool NodeExists(string file_name, string node_name) 623 { 624 #if DEBUG 625 Log.WriteLine(string.Format("NodeExists: File: {0}, Node: {1}", file_name, node_name)); 626 #endif 627 XmlDocument document = new XmlDocument(); 628 document.Load(file_name); 629 XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name); 630 return (node == null) ? false : true; 631 } 632 633 /// <summary> 634 /// Returns the value of a node from an existing xml document. 635 /// </summary> 636 /// <param name="file_name">Full path to the xml document.</param> 637 /// <param name="node_name">Name of the node to search.</param> 638 /// <returns>String representing the node's value, null if the node was not found.</returns> GetNodeValue(string file_name, string node_name)639 internal static string GetNodeValue(string file_name, string node_name) 640 { 641 #if DEBUG 642 Log.WriteLine("GetNodeValue called. file_name=" + file_name + ", node_name=" + node_name); 643 #endif 644 XmlDocument document = new XmlDocument(); 645 document.Load(file_name); 646 XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name); 647 if (node == null) 648 { 649 Console.WriteLine("cafex xmlhandler getnodevalue failed: Node " + node_name + " does not exist in " + file_name); 650 return null; 651 } 652 #if DEBUG 653 Log.WriteLine("GetNodeValue return = " + node.InnerText); 654 #endif 655 return node.InnerText; 656 } 657 658 /// <summary> 659 /// Updates the value of a node inside an xml document. 660 /// </summary> 661 /// <param name="file_name">Full path to the xml document.</param> 662 /// <param name="node_name">Name of the node to search.</param> 663 /// <param name="new_value">The new value for the node.</param> UpdateNodeValue(string file_name, string node_name, string new_value)664 internal static void UpdateNodeValue(string file_name, string node_name, string new_value) 665 { 666 #if DEBUG 667 Log.WriteLine("UpdateNodeValue called. file_name=" + file_name + ", node_name=" + node_name + ", new_value=" + new_value); 668 #endif 669 XmlDocument document = new XmlDocument(); 670 document.Load(file_name); 671 XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name); 672 if (node == null) 673 { 674 Console.WriteLine("cafex xmlhandler updatenodevalue failed: Node " + node_name + " does not exist in " + file_name); 675 return; 676 } 677 678 // Only update if necessary. 679 if (node.InnerText != new_value) 680 { 681 if (File.Exists(file_name)) 682 { 683 // Strip the read-only attribute so we can overwrite it if it exists 684 File.SetAttributes(file_name, File.GetAttributes(file_name) & ~FileAttributes.ReadOnly); 685 } 686 687 // Before the update, check if the file is not locked for 3 times 688 uint retries = Program.RETRY_ATTEMPS; 689 while (FileUtil.IsFileLocked(file_name) && retries > 0) 690 { 691 System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN); 692 --retries; 693 } 694 695 node.InnerText = new_value; 696 #if DEBUG 697 Log.WriteLine("updateNodeValue: file: " + file_name + ", node: " + node_name + ", newvalue: " + new_value); 698 #endif 699 XmlWriterSettings settings = new XmlWriterSettings(); 700 settings.NewLineChars = "\n"; 701 settings.Indent = true; 702 settings.Encoding = new UTF8Encoding(false); 703 using (XmlWriter writer = XmlWriter.Create(file_name, settings)) 704 { 705 document.WriteTo(writer); 706 writer.Flush(); 707 writer.Close(); 708 } 709 710 File.SetAttributes(file_name, FileAttributes.Normal); 711 #if DEBUG 712 Log.WriteLine("updateNodeValue completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name)); 713 #endif 714 } 715 } 716 717 /// <summary> 718 /// Add an xml node to an xml document. 719 /// </summary> 720 /// <param name="file_name">Full path to the xml document.</param> 721 /// <param name="node_name">Name for the new xml node.</param> 722 /// <param name="value">Node's value.</param> 723 /// <param name="attributes">Array of strings representing the node's attributes.</param> AddNode(string file_name, string node_name, string value, string[] attributes)724 internal static void AddNode(string file_name, string node_name, string value, string[] attributes) 725 { 726 #if DEBUG 727 Log.WriteLine("AddNode called. file_name=" + file_name + ", node_name=" + node_name + ", value=" + value); 728 string attrString = null; 729 foreach (string attribute in attributes) 730 { 731 attrString += attribute + " "; 732 } 733 Log.WriteLine("Attributes = " + attrString); 734 #endif 735 XmlDocument document = new XmlDocument(); 736 document.Load(file_name); 737 XmlElement node = document.CreateElement(node_name); 738 node.InnerText = value; 739 740 for (int i = 0; i < attributes.Length; i += 2) 741 { 742 node.SetAttribute(attributes[i], attributes[i + 1]); 743 } 744 745 document.DocumentElement.AppendChild(node); 746 #if DEBUG 747 Log.WriteLine("AddNode: file: " + file_name + ", node: " + node_name + ", value: " + value); 748 #endif 749 XmlWriterSettings settings = new XmlWriterSettings(); 750 settings.NewLineChars = "\n"; 751 settings.Indent = true; 752 settings.Encoding = new UTF8Encoding(false); 753 // Before the update, check if the file is not locked for 3 times 754 uint retries = Program.RETRY_ATTEMPS; 755 while (FileUtil.IsFileLocked(file_name) && retries > 0) 756 { 757 System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN); // 1s 758 --retries; 759 } 760 761 using (XmlWriter writer = XmlWriter.Create(file_name, settings)) 762 { 763 document.WriteTo(writer); 764 writer.Flush(); 765 writer.Close(); 766 } 767 768 File.SetAttributes(file_name, FileAttributes.Normal); 769 #if DEBUG 770 Log.WriteLine("AddNode completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name)); 771 #endif 772 } 773 774 /// <summary> 775 /// Remove a node from an xml document. 776 /// </summary> 777 /// <param name="file_name">Full path to the xml document.</param> 778 /// <param name="node_name">Name for the node to search.</param> RemoveNode(string file_name, string node_name)779 internal static void RemoveNode(string file_name, string node_name) 780 { 781 #if DEBUG 782 Log.WriteLine("RemoveNode called. file_name=" + file_name + ", node_name=" + node_name); 783 #endif 784 XmlDocument document = new XmlDocument(); 785 document.Load(file_name); 786 XmlNode node = GetXmlNodeRecursive(document.DocumentElement, node_name); 787 if (node == null) 788 { 789 return; 790 } 791 #if DEBUG 792 Log.WriteLine("RemoveNode: file: " + file_name + ", node: " + node_name); 793 #endif 794 document.DocumentElement.RemoveChild(node); 795 796 XmlWriterSettings settings = new XmlWriterSettings(); 797 settings.NewLineChars = "\n"; 798 settings.Indent = true; 799 settings.Encoding = new UTF8Encoding(false); 800 // Before the update, check if the file is not locked for 3 times 801 uint retries = Program.RETRY_ATTEMPS; 802 while (FileUtil.IsFileLocked(file_name) && retries > 0) 803 { 804 System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN); // 1s 805 --retries; 806 } 807 808 using (XmlWriter writer = XmlWriter.Create(file_name, settings)) 809 { 810 document.WriteTo(writer); 811 writer.Flush(); 812 writer.Close(); 813 } 814 815 File.SetAttributes(file_name, FileAttributes.Normal); 816 #if DEBUG 817 Log.WriteLine("RemoveNode completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name)); 818 #endif 819 } 820 UpdateNodeValueByPath(string file_name, string xpath, string new_value)821 internal static void UpdateNodeValueByPath(string file_name, string xpath, string new_value) 822 { 823 #if DEBUG 824 Log.WriteLine("UpdateNodeValueByPath called. file_name=" + file_name + ", xpath=" + xpath + ", new_value=" + new_value); 825 #endif 826 827 XmlDocument document = new XmlDocument(); 828 document.Load(file_name); 829 830 try 831 { 832 XmlNode target = document.SelectSingleNode(xpath); 833 834 if (target == null) 835 { 836 Console.WriteLine("cafex xmlhandler UpdateNodeValueByPath failed: Node specified by path " + xpath + " does not exist in " + file_name); 837 return; 838 } 839 840 if (target.InnerText != new_value) 841 { 842 uint retries = Program.RETRY_ATTEMPS; 843 while (FileUtil.IsFileLocked(file_name) && retries > 0) 844 { 845 System.Threading.Thread.Sleep(Program.RETRY_TIMESPAN); 846 --retries; 847 } 848 849 target.InnerText = new_value; 850 #if DEBUG 851 Log.WriteLine("UpdateNodeValueByPath: file=" + file_name + ", xpath=" + xpath + ", new_value=" + new_value); 852 #endif 853 XmlWriterSettings settings = new XmlWriterSettings(); 854 settings.NewLineChars = "\n"; 855 settings.Indent = true; 856 settings.Encoding = new UTF8Encoding(false); 857 using (XmlWriter writer = XmlWriter.Create(file_name, settings)) 858 { 859 document.WriteTo(writer); 860 writer.Flush(); 861 writer.Close(); 862 } 863 864 File.SetAttributes(file_name, FileAttributes.Normal); 865 #if DEBUG 866 Log.WriteLine("UpdateNodeValueByPath completed on '" + file_name + "' and file in use = " + FileUtil.IsFileLocked(file_name)); 867 #endif 868 } 869 } 870 catch (System.Xml.XPath.XPathException) 871 { 872 Console.WriteLine("UpdateNodeValueByPath contained an invalid xpath parameter! xpath={0}\nNode will not be updated!",xpath); 873 return; 874 } 875 } 876 } 877 878 /// <summary> 879 /// Represents a title id hex string. 880 /// </summary> 881 internal class TitleId 882 { 883 private const int TitleIdLength = 16; 884 private const int TitleIdHalfLength = TitleIdLength / 2; 885 private static readonly string[] SystemTitleRanges = { "00050010", "0005001b", "00050030" }; 886 private const string HexPrefix = "0x"; 887 888 private string titleIdAsString; 889 890 /// <summary> 891 /// Creates new instance of TitleId, verifying that the input string is of the correct format. 892 /// </summary> 893 /// <param name="titleId">Title ID as string</param> 894 /// <remarks>Throws <see cref="ArgumentException"/> on parsing failure.</remarks> TitleId(string titleId)895 public TitleId(string titleId) 896 { 897 ulong titleIdAsLong; 898 899 if (titleId.StartsWith(HexPrefix)) 900 { 901 titleId = titleId.Remove(0, HexPrefix.Length); 902 } 903 904 if (titleId.Length != TitleIdLength) 905 { 906 throw new ArgumentException("Title ID has incorrect length. A correct ID is 18 characters with the leading 0x, or 16 characters without 0x."); 907 } 908 909 if (!UInt64.TryParse(titleId, System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture, out titleIdAsLong)) 910 { 911 throw new ArgumentException("Title ID has invalid characters. Only hexadecimal characters are allowed."); 912 } 913 914 this.titleIdAsString = titleId.StartsWith(HexPrefix) ? titleId.ToLowerInvariant() : string.Format("{0}{1}", HexPrefix, titleId).ToLowerInvariant(); 915 } 916 917 /// <summary> 918 /// Returns true if the input string is a valid title ID. 919 /// </summary> 920 /// <param name="input">Title ID as string</param> 921 /// <returns>True if parsed, else false.</returns> TryParse(string input, out TitleId titleId)922 public static bool TryParse(string input, out TitleId titleId) 923 { 924 try 925 { 926 titleId = new TitleId(input); 927 return true; 928 } 929 catch (ArgumentException) 930 { 931 titleId = null; 932 return false; 933 } 934 } 935 936 /// <summary> 937 /// Returns true if the high value of the title ID matches one of the system title high values. 938 /// </summary> 939 public bool IsSystemTitle 940 { 941 get 942 { 943 return SystemTitleRanges.Any(x => x == HighValue); 944 } 945 } 946 947 /// <summary> 948 /// Returns value of the title ID, with leading hex prefix. 949 /// </summary> 950 public string Value 951 { 952 get 953 { 954 return this.titleIdAsString; 955 } 956 } 957 958 /// <summary> 959 /// Returns value of the title ID, without the leading hex prefix. 960 /// </summary> 961 public string ValueWithoutPrefix 962 { 963 get 964 { 965 return this.titleIdAsString.Substring(HexPrefix.Length, TitleIdLength); 966 } 967 } 968 969 /// <summary> 970 /// High value (most significant bits) of the title ID. 971 /// </summary> 972 public string HighValue 973 { 974 get 975 { 976 return this.titleIdAsString.Substring(HexPrefix.Length, TitleIdHalfLength); 977 } 978 } 979 980 /// <summary> 981 /// Low value (least significant bits) of the title ID. 982 /// </summary> 983 public string LowValue 984 { 985 get 986 { 987 return this.titleIdAsString.Substring(HexPrefix.Length + TitleIdHalfLength, TitleIdHalfLength); 988 } 989 } 990 } 991 992 /// <summary> 993 /// This class is a wrapper on HEX conversion functions used by CafeX. 994 /// </summary> 995 internal static class HexHandler 996 { 997 /// <summary> 998 /// Transform the input string into its integer representation. 999 /// Strings that are invalid representations of integers will cause exceptions. 1000 /// </summary> 1001 /// <param name="input">String representing a number.</param> 1002 /// <returns>The integer that represents the provided string.</returns> GetNumberFromString(string input)1003 internal static uint GetNumberFromString(string input) 1004 { 1005 #if DEBUG 1006 Log.WriteLine("GetNumberFromString called. input=" + input); 1007 #endif 1008 if (input.StartsWith("0x")) 1009 { 1010 return Convert.ToUInt32(input, 16); 1011 } 1012 else 1013 { 1014 foreach (char c in input) 1015 { 1016 if ((c >= 'a' || c >= 'A') && 1017 (c <= 'f' || c <= 'F')) 1018 { 1019 return Convert.ToUInt32(input, 16); 1020 } 1021 } 1022 1023 return Convert.ToUInt32(input); 1024 } 1025 } 1026 1027 /// <summary> 1028 /// Transform the input string into its long representation. 1029 /// Strings that are invalid representations of longs will cause exceptions. 1030 /// </summary> 1031 /// <param name="input">String representing a number.</param> 1032 /// <returns>The long that represents the provided string.</returns> GetLongNumberFromString(string input)1033 internal static long GetLongNumberFromString(string input) 1034 { 1035 #if DEBUG 1036 Log.WriteLine("GetLongNumberFromString called. input=" + input); 1037 #endif 1038 1039 if (input.StartsWith("0x")) 1040 { 1041 return Convert.ToInt64(input, 16); 1042 } 1043 else 1044 { 1045 foreach (char c in input) 1046 { 1047 if ((c >= 'a' || c >= 'A') && 1048 (c <= 'f' || c <= 'F')) 1049 { 1050 return Convert.ToInt64(input, 16); 1051 } 1052 } 1053 return Convert.ToInt64(input); 1054 } 1055 } 1056 } 1057 1058 /// <summary> 1059 /// Utilities for file verification. 1060 /// </summary> 1061 internal static class FileUtil 1062 { 1063 private static string cafex_system_wide_mtx_name = "Global\\cafex_system_wide_mtx"; 1064 private static DateTime entryAssemblyTs; 1065 private static bool bRunningFromCygwin = false; 1066 private static string assemblyFileName; 1067 1068 1069 /// <summary> 1070 /// Initializes one-shot only utility variables 1071 /// </summary> GatherEnvironment()1072 internal static void GatherEnvironment() 1073 { 1074 string cygcheck; 1075 assemblyFileName = System.Reflection.Assembly.GetExecutingAssembly().Location; 1076 entryAssemblyTs = File.GetLastWriteTimeUtc(assemblyFileName); 1077 cygcheck = Environment.GetEnvironmentVariable("CYGCHECK"); 1078 if ( !(string.IsNullOrEmpty(cygcheck)) ) 1079 { 1080 if( cygcheck.StartsWith("cygwin" ) ) 1081 { 1082 bRunningFromCygwin = true; 1083 } 1084 } 1085 } 1086 1087 1088 /// <summary> 1089 /// Determine whether we are running from within cygwin 1090 /// </summary> 1091 internal static bool RunningFromCygwin 1092 { 1093 get { return bRunningFromCygwin; } 1094 } 1095 1096 /// <summary> 1097 /// Check if a file is locked for R/W and/or delete. 1098 /// </summary> 1099 /// <param name="filename">Full path to the file.</param> 1100 /// <returns>True if the file is locked, false otherwise.</returns> IsFileLocked(string filename)1101 internal static bool IsFileLocked(string filename) 1102 { 1103 FileStream stream = null; 1104 bool result = false; 1105 1106 try 1107 { 1108 FileInfo file = new FileInfo(filename); 1109 stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None); 1110 result = false; 1111 } 1112 catch (IOException) 1113 { 1114 //the file is unavailable because it is: 1115 //a) still being written to 1116 //b) or being processed by another thread (maybe held by PCFS) 1117 //c) or does not exist (has already been processed) 1118 #if DEBUG 1119 // Collect debug information to help troubleshoot who has this file handle 1120 try 1121 { 1122 Process handleTool = new Process(); 1123 handleTool.StartInfo.FileName = "handle.exe"; 1124 handleTool.StartInfo.Arguments = filename + " /accepteula"; 1125 handleTool.StartInfo.UseShellExecute = false; 1126 handleTool.StartInfo.RedirectStandardOutput = true; 1127 handleTool.Start(); 1128 handleTool.WaitForExit(); 1129 string handleOutput = handleTool.StandardOutput.ReadToEnd(); 1130 Log.WriteLine("############## FILE LOCKED WARNING REPORT ##############"); 1131 Log.WriteLine("Filename = " + filename); 1132 Log.WriteLine("Handles opened:"); 1133 Log.WriteLine(handleOutput); 1134 Log.WriteLine("############## FILE LOCKED END OF REPORT ##############"); 1135 1136 1137 } 1138 catch (Exception) 1139 { 1140 Log.WriteLine("Unable to get handle information on file " + filename); 1141 } 1142 finally 1143 #endif 1144 { 1145 result = true; 1146 } 1147 } 1148 finally 1149 { 1150 if (stream != null) 1151 { 1152 stream.Close(); 1153 } 1154 } 1155 1156 return result; 1157 1158 } 1159 1160 1161 1162 /// <summary> 1163 /// Expand an executable to the temp folder from a resource location 1164 /// </summary> 1165 /// <param name="payload">Executable bytes from resource</param> 1166 /// <param name="filename">File to be written to disk</param> 1167 /// <returns>Full path to temporary file written to disk.</returns> ExpandExecutable(byte[] payload, string filename)1168 internal static string ExpandExecutable(byte[] payload, string filename) 1169 { 1170 System.Threading.Mutex mtx = null; 1171 string full_tmp_path; 1172 1173 full_tmp_path = Environment.GetEnvironmentVariable("CAFE_TEMP"); 1174 1175 if( string.IsNullOrEmpty(full_tmp_path) ) 1176 { 1177 full_tmp_path = Environment.GetEnvironmentVariable("TEMP"); 1178 if( string.IsNullOrEmpty(full_tmp_path) ) 1179 { 1180 full_tmp_path = Environment.GetEnvironmentVariable("TMP"); 1181 if ( string.IsNullOrEmpty(full_tmp_path) ) 1182 { 1183 full_tmp_path = Path.GetDirectoryName(assemblyFileName); 1184 } 1185 } 1186 } 1187 1188 1189 full_tmp_path = PathConverter.Windowsify(full_tmp_path) + "\\" + filename; 1190 1191 // the kinds of things that can prevent this from happening are all fatal 1192 // errors, don't try to catch anything 1193 mtx = new System.Threading.Mutex(false, cafex_system_wide_mtx_name); 1194 1195 mtx.WaitOne(); 1196 1197 // from this point on, we must release the mutex no matter what happens! 1198 1199 try 1200 { 1201 DateTime tempExeTs; 1202 bool doOverwrite = false; 1203 try 1204 { 1205 tempExeTs = File.GetLastWriteTimeUtc(full_tmp_path); 1206 1207 // if the internal resource is newer than the file on disk 1208 if (entryAssemblyTs >= tempExeTs) 1209 { 1210 doOverwrite = true; 1211 } 1212 // else, we will use the file that's there, as it is newer than we are 1213 } 1214 catch (FileNotFoundException) 1215 { 1216 // file's not there - we will overwrite it 1217 doOverwrite = true; 1218 } 1219 1220 if (doOverwrite) 1221 { 1222 try 1223 { 1224 File.WriteAllBytes(full_tmp_path, payload); 1225 } 1226 catch( System.IO.DirectoryNotFoundException ) 1227 { 1228 try 1229 { 1230 Directory.CreateDirectory( Path.GetDirectoryName(full_tmp_path) ); 1231 } 1232 catch 1233 { 1234 } 1235 File.WriteAllBytes(full_tmp_path, payload); 1236 } 1237 1238 } 1239 } 1240 finally 1241 { 1242 mtx.ReleaseMutex(); 1243 } 1244 1245 return full_tmp_path; 1246 1247 } 1248 1249 /// <summary> 1250 /// Check if the provide string is a valid file name. 1251 /// </summary> 1252 /// <param name="filename">String to check.</param> 1253 /// <returns>True if it is a valid file name, false otherwise.</returns> IsValidFile(string filename, bool checkExistence)1254 internal static bool IsValidFile(string filename, bool checkExistence) 1255 { 1256 #if DEBUG 1257 Log.WriteLine("IsValidFile called. filename=" + filename + ", checkExistence=" + checkExistence.ToString()); 1258 #endif 1259 if (string.IsNullOrEmpty(filename)) 1260 { 1261 return false; 1262 } 1263 1264 // Convert to windows, if it was a cygwin path 1265 filename = PathConverter.Windowsify(filename); 1266 bool returnVal = false; 1267 try 1268 { 1269 FileInfo file = new FileInfo(filename); 1270 // A path without a filename resolves to a valid FileInfo instance 1271 // so we need to check if there is a file at the end of the path. 1272 if (Path.GetFileName(file.FullName).Length == 0) 1273 { 1274 // There is no filename, only a valid path! 1275 return false; 1276 } 1277 1278 if (!checkExistence) 1279 { 1280 returnVal = true; 1281 } 1282 else 1283 { 1284 returnVal = file.Exists; 1285 } 1286 } 1287 catch (ArgumentException) 1288 { 1289 } 1290 catch (System.IO.PathTooLongException) 1291 { 1292 } 1293 catch (NotSupportedException) 1294 { 1295 } 1296 1297 #if DEBUG 1298 Log.WriteLine("IsValidFile return " + returnVal.ToString()); 1299 #endif 1300 return returnVal; 1301 } 1302 AddQuotes(ref string fileNameOrListWithSpaces)1303 internal static void AddQuotes(ref string fileNameOrListWithSpaces) 1304 { 1305 #if DEBUG 1306 Log.WriteLine("AddQuotes called. " + "fileNameOrListWithSpaces=" + fileNameOrListWithSpaces); 1307 #endif 1308 if (string.IsNullOrEmpty(fileNameOrListWithSpaces)) 1309 { 1310 return; 1311 } 1312 1313 if (fileNameOrListWithSpaces.StartsWith("\"") && fileNameOrListWithSpaces.EndsWith("\"")) 1314 { 1315 return; 1316 } 1317 1318 fileNameOrListWithSpaces = "\"" + fileNameOrListWithSpaces + "\""; 1319 } 1320 1321 1322 /// <summary> 1323 /// Creates a directory if it does not already exist. 1324 /// </summary> 1325 /// <param name="directoryPath">Directory to create</param> DirectoryCreate(string directoryPath)1326 internal static void DirectoryCreate(string directoryPath) 1327 { 1328 #if DEBUG 1329 Console.WriteLine("FileUtil::DirectoryCreate: Creating '{0}' if it does not already exist", directoryPath); 1330 #endif 1331 if (!Directory.Exists(directoryPath)) 1332 { 1333 Directory.CreateDirectory(directoryPath); 1334 } 1335 } 1336 1337 1338 /// <summary> 1339 /// Copy a directory from one location to another with optional recursion. 1340 /// </summary> 1341 /// <param name="sourceDirName">Source folder name.</param> 1342 /// <param name="destDirName">Destination folder name.</param> 1343 /// <param name="copySubDirs">Flag to copy all of the sub-folders recurively.</param> DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs, bool overwriteFiles)1344 internal static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs, bool overwriteFiles) 1345 { 1346 #if DEBUG 1347 Console.WriteLine("FileUtil::DirectoryCopy: Copying '{0}' => '{1}'{2}", sourceDirName, destDirName, copySubDirs ? " recursively" : ""); 1348 #endif 1349 // Get the subdirectories for the specified directory. 1350 DirectoryInfo dir = new DirectoryInfo(sourceDirName); 1351 DirectoryInfo[] dirs = dir.GetDirectories(); 1352 1353 if (!dir.Exists) 1354 { 1355 throw new DirectoryNotFoundException("Source directory does not exist or could not be found: " + sourceDirName); 1356 } 1357 1358 // If the destination directory doesn't exist, create it. 1359 if (!Directory.Exists(destDirName)) 1360 { 1361 Directory.CreateDirectory(destDirName); 1362 } 1363 1364 // Get the files in the directory and copy them to the new location. 1365 FileInfo[] files = dir.GetFiles(); 1366 foreach (FileInfo file in files) 1367 { 1368 string temppath = Path.Combine(destDirName, file.Name); 1369 file.CopyTo(temppath, overwriteFiles); 1370 } 1371 1372 // If copying subdirectories, copy them and their contents to new location. 1373 if (copySubDirs) 1374 { 1375 foreach (DirectoryInfo subdir in dirs) 1376 { 1377 string temppath = Path.Combine(destDirName, subdir.Name); 1378 DirectoryCopy(subdir.FullName, temppath, copySubDirs, overwriteFiles); 1379 } 1380 } 1381 } 1382 1383 /// <summary> 1384 /// Deletes the content of a directory with optional recursion 1385 /// </summary> 1386 /// <param name="dirName">Directory folder name.</param> 1387 /// <param name="recursive">Flag to remove subdirectories recursively.</param> DirectoryDelete(string dirName, bool recursive)1388 internal static void DirectoryDelete(string dirName, bool recursive) 1389 { 1390 if (Directory.Exists(dirName)) 1391 { 1392 #if DEBUG 1393 Console.WriteLine("FileUtil::DirectoryDelete: Deleting '{0}'{1}", dirName, recursive ? " recursively" : ""); 1394 #endif 1395 DirectoryInfo di = new DirectoryInfo(dirName); 1396 1397 RemoveReadOnlyAttribute(di, recursive); 1398 1399 // If the directory is a junction, this command will still work correctly. 1400 // That is, only the junction is deleted and not the contents of the linked directory. 1401 // Otherwise, the directory and its contents delete normally. 1402 di.Delete(recursive); 1403 } 1404 } 1405 RemoveReadOnlyAttribute(DirectoryInfo dirInfo, bool recursive)1406 internal static void RemoveReadOnlyAttribute(DirectoryInfo dirInfo, bool recursive) 1407 { 1408 // Change attributes of source directory 1409 if ((dirInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) 1410 { 1411 dirInfo.Attributes = FileAttributes.Normal; 1412 } 1413 // Change all of the attributes in the directory specified by dirInfo 1414 foreach (FileInfo fi in dirInfo.GetFiles()) 1415 { 1416 if ((fi.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) 1417 { 1418 //don't modify any other flags. 1419 fi.Attributes &= ~(FileAttributes.ReadOnly); 1420 } 1421 } 1422 // Recursively change attributes in all files in all directories if specified 1423 if (recursive) 1424 { 1425 //Can't use actual recursion because there would be a stack overflow. 1426 foreach (DirectoryInfo di in dirInfo.GetDirectories()) 1427 { 1428 // Change attributes of current directory 1429 if ((di.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) 1430 { 1431 //don't modify any other flags. 1432 di.Attributes &= ~(FileAttributes.ReadOnly); 1433 } 1434 // Change all of the attributes in the directory specified by dirInfo 1435 foreach (FileInfo fi in di.GetFiles()) 1436 { 1437 if ((fi.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) 1438 { 1439 //don't modify any other flags. 1440 fi.Attributes &= ~(FileAttributes.ReadOnly); 1441 } 1442 } 1443 } 1444 } 1445 } 1446 FileDelete(string fileName)1447 internal static void FileDelete(string fileName) 1448 { 1449 #if DEBUG 1450 Console.WriteLine("FileUtil::FileDelete: Deleting '{0}'", fileName); 1451 #endif 1452 if (File.Exists(fileName)) 1453 { 1454 // Strip any readonly, hidden and system attributes 1455 FileInfo fileInfo = new FileInfo(fileName); 1456 fileInfo.Attributes = FileAttributes.Normal; 1457 1458 // Now delete the file 1459 File.Delete(fileName); 1460 } 1461 } 1462 1463 /// <summary> 1464 /// Copies a file exactly (preserves timestamp). Creates target directory for file if needed. 1465 /// </summary> 1466 /// <param name="copyFromPath">Source file</param> 1467 /// <param name="copyToPath">Destination file</param> FileCopy(string copyFromPath, string copyToPath)1468 internal static void FileCopy(string copyFromPath, string copyToPath) 1469 { 1470 #if DEBUG 1471 Console.WriteLine("FileUtil::FileCopy: Copying '{0}' to '{1}'", copyFromPath, copyToPath); 1472 #endif 1473 // Strip read-only attriute if destination file already exists. 1474 if (File.Exists(copyToPath)) 1475 { 1476 var target = new FileInfo(copyToPath); 1477 if (target.IsReadOnly) 1478 target.IsReadOnly = false; 1479 } 1480 1481 // Create destination dir if it doesn't already exist. 1482 string copyToDir = Path.GetDirectoryName(copyToPath); 1483 DirectoryCreate(copyToDir); 1484 1485 // Now do the copy. 1486 var origin = new FileInfo(copyFromPath); 1487 origin.CopyTo(copyToPath, true); 1488 1489 // And preserve its timestamp info and read-only attribute. 1490 var destination = new FileInfo(copyToPath); 1491 if (destination.IsReadOnly) 1492 { 1493 destination.IsReadOnly = false; 1494 destination.CreationTime = origin.CreationTime; 1495 destination.LastWriteTime = origin.LastWriteTime; 1496 destination.LastAccessTime = origin.LastAccessTime; 1497 destination.IsReadOnly = true; 1498 } 1499 else 1500 { 1501 destination.CreationTime = origin.CreationTime; 1502 destination.LastWriteTime = origin.LastWriteTime; 1503 destination.LastAccessTime = origin.LastAccessTime; 1504 } 1505 } 1506 } 1507 1508 /// <summary> 1509 /// Log to the stdout and debug. This is only used in debug builds. 1510 /// </summary> 1511 internal static class Log 1512 { 1513 internal static StreamWriter sw = null; 1514 OpenLogFile(string logFilePath)1515 internal static bool OpenLogFile(string logFilePath) 1516 { 1517 try 1518 { 1519 if (sw == null) 1520 { 1521 string path = Environment.ExpandEnvironmentVariables(logFilePath); 1522 path = PathConverter.Windowsify(path); 1523 string directory = path.Substring(0, path.LastIndexOf('\\')); 1524 if (!Directory.Exists(directory)) 1525 { 1526 Directory.CreateDirectory(directory); 1527 } 1528 sw = new StreamWriter(path); 1529 } 1530 } 1531 catch (Exception ex) 1532 { 1533 Console.WriteLine("Failed to open the log file. InnerException: {0}", ex.Message); 1534 sw = null; 1535 } 1536 return (sw != null); 1537 } WriteLine(string message)1538 internal static void WriteLine(string message) 1539 { 1540 message = string.Format("[ {0} - DEBUG: {1} ] {2}", System.Diagnostics.Process.GetCurrentProcess().Id, DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss-ffff"), message); 1541 Console.WriteLine(message); 1542 Debug.WriteLine(message); 1543 if (sw != null) 1544 { 1545 sw.WriteLine(message); 1546 } 1547 } 1548 WriteLine(string format, object arg0)1549 internal static void WriteLine(string format, object arg0) 1550 { 1551 WriteLine(string.Format(format, arg0)); 1552 } 1553 WriteLine(string format, object arg0, object arg1)1554 internal static void WriteLine(string format, object arg0, object arg1) 1555 { 1556 WriteLine(string.Format(format, arg0, arg1)); 1557 } 1558 WriteLine(string format, object arg0, object arg1, object arg2)1559 internal static void WriteLine(string format, object arg0, object arg1, object arg2) 1560 { 1561 WriteLine(string.Format(format, arg0, arg1, arg2)); 1562 } 1563 WriteLineRaw(string message)1564 internal static void WriteLineRaw(string message) 1565 { 1566 Console.WriteLine(message); 1567 Debug.WriteLine(message); 1568 if (sw != null) 1569 { 1570 sw.WriteLine(message); 1571 } 1572 } 1573 WriteLineRaw(string format, object arg0)1574 internal static void WriteLineRaw(string format, object arg0) 1575 { 1576 WriteLineRaw(string.Format(format, arg0)); 1577 } 1578 WriteLineRaw(string format, object arg0, object arg1)1579 internal static void WriteLineRaw(string format, object arg0, object arg1) 1580 { 1581 WriteLineRaw(string.Format(format, arg0, arg1)); 1582 } 1583 WriteLineRaw(string format, object arg0, object arg1, object arg2)1584 internal static void WriteLineRaw(string format, object arg0, object arg1, object arg2) 1585 { 1586 WriteLineRaw(string.Format(format, arg0, arg1, arg2)); 1587 } 1588 CloseLogFile()1589 internal static void CloseLogFile() 1590 { 1591 if (sw != null) 1592 { 1593 sw.Close(); 1594 } 1595 } 1596 } 1597 1598 /// <summary> 1599 /// This class provides tools for argument validation. 1600 /// </summary> 1601 internal static class ArgumentChecks 1602 { IsArgumentValidSetting(string[] args, int index)1603 internal static bool IsArgumentValidSetting(string[] args, int index) 1604 { 1605 #if DEBUG 1606 Log.WriteLine("IsArgumentValidSetting called. Checking index=" + index.ToString()); 1607 string argString = null; 1608 for (int counter = 0; counter < args.Length; ++counter) 1609 { 1610 argString += "{" + counter.ToString() + "}" + args[counter] + " "; 1611 } 1612 Log.WriteLine("arguments=" + argString); 1613 #endif 1614 bool returnVal = false; 1615 // If the argument doesn't exist, it is not valid. 1616 if (!ArgumentExists(args, index)) 1617 { 1618 returnVal = false; 1619 } 1620 else 1621 { 1622 // If the argument starts with '-' it is another option, not a setting. 1623 if (!args[index].StartsWith("-")) 1624 { 1625 returnVal = true; 1626 } 1627 else 1628 { 1629 returnVal = false; 1630 } 1631 } 1632 1633 #if DEBUG 1634 Log.WriteLine("IsArgumentValidSetting return " + returnVal.ToString()); 1635 #endif 1636 return returnVal; 1637 } 1638 1639 /// <summary> 1640 /// Check if the provided index exists in the argument list. 1641 /// </summary> 1642 /// <param name="args">Array of string representing an argument list.</param> 1643 /// <param name="index">The index of the argument that will be checked.</param> 1644 /// <returns>True if the argument at the provide index exists, false otherwise.</returns> ArgumentExists(string[] args, int index)1645 internal static bool ArgumentExists(string[] args, int index) 1646 { 1647 if (args == null || args.Length == 0 || 1648 index < 0 || index > (args.Length - 1)) 1649 { 1650 return false; 1651 } 1652 else 1653 { 1654 return true; 1655 } 1656 } 1657 } 1658 1659 internal class PerfTimer 1660 { 1661 private System.Diagnostics.Stopwatch _clock; 1662 private string _name; 1663 1664 public string Name 1665 { 1666 get { return _name;} 1667 } 1668 PerfTimer(string name)1669 public PerfTimer(string name) 1670 { 1671 _name = name; 1672 _clock = new Stopwatch(); 1673 } 1674 Start()1675 public void Start() 1676 { 1677 Log.WriteLine("[TIMER]: Starting PerfTimer " + _name); 1678 _clock.Start(); 1679 } 1680 Report()1681 public void Report() 1682 { 1683 Log.WriteLine("[TIMER]: PerfTimer " + _name + " current time is " + _clock.ElapsedMilliseconds + " ms."); 1684 } 1685 Stop()1686 public void Stop() 1687 { 1688 _clock.Stop(); 1689 Log.WriteLine("[TIMER]: PerfTimer " + _name + " stopped."); 1690 this.Report(); 1691 } 1692 Reset()1693 public void Reset() 1694 { 1695 _clock.Reset(); 1696 Log.WriteLine("[TIMER]: PerfTimer " + _name + " reset."); 1697 } 1698 } 1699 }