context: #region Copyright & License Information
  /*
   * Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
   * This file is part of OpenRA, which is free software. It is made
   * available to you under the terms of the GNU General Public License
   * as published by the Free Software Foundation. For more information,
   * see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Drawing;
  using System.Drawing.Imaging;
  using System.IO;
  using System.Linq;
  using System.Reflection;
  using System.Runtime.InteropServices;
  using System.Text;
  using OpenRA.FileFormats;
  using OpenRA.FileSystem;
  using OpenRA.Graphics;
  using OpenRA.Scripting;
  using OpenRA.Traits;
  
  namespace OpenRA.Utility
  {
  	public static class Command
  	{
  		static IEnumerable<string> GlobArgs(string[] args, int startIndex = 1)
  		{
  			for (var i = startIndex; i < args.Length; i++)
  				foreach (var path in Glob.Expand(args[i]))
  					yield return path;
  		}
  
  		[Desc("KEY", "Get value of KEY from settings.yaml")]
  		public static void Settings(string[] args)
  		{
  			if (args.Length < 2)
  			{
  				Console.WriteLine("Error: Invalid syntax");
  				return;
  			}
  
  			var section = args[1].Split('.')[0];
  			var field = args[1].Split('.')[1];
  			var settings = new Settings(Platform.SupportDir + "settings.yaml", Arguments.Empty);
  			var result = settings.Sections[section].GetType().GetField(field).GetValue(settings.Sections[section]);
  			Console.WriteLine(result);
  		}
  
  		[Desc("PNGFILE [PNGFILE ...]", "Combine a list of PNG images into a SHP")]
  		public static void ConvertPngToShp(string[] args)
  		{
  			var inputFiles = GlobArgs(args).OrderBy(a => a).ToList();
  			var dest = inputFiles[0].Split('-').First() + ".shp";
  			var frames = inputFiles.Select(a => PngLoader.Load(a));
  
  			var size = frames.First().Size;
  			if (frames.Any(f => f.Size != size))
  				throw new InvalidOperationException("All frames must be the same size");
  
  			using (var destStream = File.Create(dest))
  				ShpReader.Write(destStream, size, frames.Select(f => f.ToBytes()));
  
  			Console.WriteLine(dest + " saved.");
  		}
  
  		static byte[] ToBytes(this Bitmap bitmap)
  		{
  			var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly,
  				PixelFormat.Format8bppIndexed);
  
  			var bytes = new byte[bitmap.Width * bitmap.Height];
  			for (var i = 0; i < bitmap.Height; i++)
  				Marshal.Copy(new IntPtr(data.Scan0.ToInt64() + i * data.Stride),
  					bytes, i * bitmap.Width, bitmap.Width);
  
  			bitmap.UnlockBits(data);
  
  			return bytes;
  		}
  
  		[Desc("SPRITEFILE PALETTE [--noshadow] [--nopadding]",
  		      "Convert a shp/tmp/R8 to a series of PNGs, optionally removing shadow")]
  		public static void ConvertSpriteToPng(string[] args)
  		{
  			var src = args[1];
  			var shadowIndex = new int[] { };
  			if (args.Contains("--noshadow"))
  			{
  				Array.Resize(ref shadowIndex, shadowIndex.Length + 3);
  				shadowIndex[shadowIndex.Length - 1] = 1;
  				shadowIndex[shadowIndex.Length - 2] = 3;
  				shadowIndex[shadowIndex.Length - 3] = 4;
  			}
  
  			var palette = new ImmutablePalette(args[2], shadowIndex);
  
  			ISpriteSource source;
  			using (var stream = File.OpenRead(src))
  				source = SpriteSource.LoadSpriteSource(stream, src);
  
  			// The r8 padding requires external information that we can't access here.
  			var usePadding = !(args.Contains("--nopadding") || source is R8Reader);
  			var count = 0;
  			var prefix = Path.GetFileNameWithoutExtension(src);
  
  			foreach (var frame in source.Frames)
  			{
  				var frameSize = usePadding ? frame.FrameSize : frame.Size;
  				var offset = usePadding ? (frame.Offset - 0.5f * new float2(frame.Size - frame.FrameSize)).ToInt2() : int2.Zero;
  
  				// shp(ts) may define empty frames
  				if (frameSize.Width == 0 && frameSize.Height == 0)
  				{
  					count++;
  					continue;
  				}
  
  				using (var bitmap = new Bitmap(frameSize.Width, frameSize.Height, PixelFormat.Format8bppIndexed))
  				{
  					bitmap.Palette = palette.AsSystemPalette();
  					var data = bitmap.LockBits(new Rectangle(0, 0, frameSize.Width, frameSize.Height),
  						ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
  
  					// Clear the frame
  					if (usePadding)
  					{
  						var clearRow = new byte[data.Stride];
  						for (var i = 0; i < frameSize.Height; i++)
  							Marshal.Copy(clearRow, 0, new IntPtr(data.Scan0.ToInt64() + i * data.Stride), data.Stride);
  					}
  
  					for (var i = 0; i < frame.Size.Height; i++)
  					{
  						var destIndex = new IntPtr(data.Scan0.ToInt64() + (i + offset.Y) * data.Stride + offset.X);
  						Marshal.Copy(frame.Data, i * frame.Size.Width, destIndex, frame.Size.Width);
  					}
  
  					bitmap.UnlockBits(data);
  
  					var filename = "{0}-{1:D4}.png".F(prefix, count++);
  					bitmap.Save(filename);
  				}
  			}
  
  			Console.WriteLine("Saved {0}-[0..{1}].png", prefix, count - 1);
  		}
  
  		[Desc("MOD FILES", "Extract files from mod packages to the current directory")]
  		public static void ExtractFiles(string[] args)
  		{
  			var mod = args[1];
  			var files = args.Skip(2);
  
  			var manifest = new Manifest(mod);
  			GlobalFileSystem.LoadFromManifest(manifest);
  
  			foreach (var f in files)
  			{
  				var src = GlobalFileSystem.Open(f);
  				if (src == null)
  					throw new InvalidOperationException("File not found: {0}".F(f));
  				var data = src.ReadAllBytes();
  				File.WriteAllBytes(f, data);
  				Console.WriteLine(f + " saved.");
  			}
  		}
  
  		static int ColorDistance(uint a, uint b)
  		{
  			var ca = Color.FromArgb((int)a);
  			var cb = Color.FromArgb((int)b);
  
  			return Math.Abs((int)ca.R - (int)cb.R) +
  				Math.Abs((int)ca.G - (int)cb.G) +
  				Math.Abs((int)ca.B - (int)cb.B);
  		}
  
  		[Desc("SRCMOD:PAL DESTMOD:PAL SRCSHP DESTSHP", "Remap SHPs to another palette")]
  		public static void RemapShp(string[] args)
  		{
  			var remap = new Dictionary<int, int>();
  
  			/* the first 4 entries are fixed */
  			for (var i = 0; i < 4; i++)
  				remap[i] = i;
  
  			var srcMod = args[1].Split(':')[0];
  			Game.modData = new ModData(srcMod);
  			GlobalFileSystem.LoadFromManifest(Game.modData.Manifest);
  			var srcRules = Game.modData.RulesetCache.LoadDefaultRules();
  			var srcPaletteInfo = srcRules.Actors["player"].Traits.Get<PlayerColorPaletteInfo>();
  			var srcRemapIndex = srcPaletteInfo.RemapIndex;
  
  			var destMod = args[2].Split(':')[0];
  			Game.modData = new ModData(destMod);
  			GlobalFileSystem.LoadFromManifest(Game.modData.Manifest);
  			var destRules = Game.modData.RulesetCache.LoadDefaultRules();
  			var destPaletteInfo = destRules.Actors["player"].Traits.Get<PlayerColorPaletteInfo>();
  			var destRemapIndex = destPaletteInfo.RemapIndex;
  			var shadowIndex = new int[] { };
  
  			// the remap range is always 16 entries, but their location and order changes
  			for (var i = 0; i < 16; i++)
  				remap[PlayerColorRemap.GetRemapIndex(srcRemapIndex, i)]
  					= PlayerColorRemap.GetRemapIndex(destRemapIndex, i);
  
  			// map everything else to the best match based on channel-wise distance
  			var srcPalette = new ImmutablePalette(args[1].Split(':')[1], shadowIndex);
  			var destPalette = new ImmutablePalette(args[2].Split(':')[1], shadowIndex);
  
  			for (var i = 0; i < Palette.Size; i++)
  				if (!remap.ContainsKey(i))
  					remap[i] = Enumerable.Range(0, Palette.Size)
  						.Where(a => !remap.ContainsValue(a))
  						.MinBy(a => ColorDistance(destPalette[a], srcPalette[i]));
  
  			var srcImage = ShpReader.Load(args[3]);
  
  			using (var destStream = File.Create(args[4]))
  				ShpReader.Write(destStream, srcImage.Size,
  					srcImage.Frames.Select(im => im.Data.Select(px => (byte)remap[px]).ToArray()));
  		}
  
  		[Desc("SRCSHP DESTSHP START N M [START N M ...]",
  		      "Transpose the N*M block of frames starting at START.")]
  		public static void TransposeShp(string[] args)
  		{
  			var srcImage = ShpReader.Load(args[1]);
  
  			var srcFrames = srcImage.Frames;
  			var destFrames = srcImage.Frames.ToArray();
  
  			for (var z = 3; z < args.Length - 2; z += 3)
  			{
  				var start = Exts.ParseIntegerInvariant(args[z]);
  				var m = Exts.ParseIntegerInvariant(args[z + 1]);
  				var n = Exts.ParseIntegerInvariant(args[z + 2]);
  
  				for (var i = 0; i < m; i++)
  					for (var j = 0; j < n; j++)
  						destFrames[start + i * n + j] = srcFrames[start + j * m + i];
  			}
  
  			using (var destStream = File.Create(args[2]))
  				ShpReader.Write(destStream, srcImage.Size, destFrames.Select(f => f.Data));
  		}
  
  		static string FriendlyTypeName(Type t)
  		{
  			if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
  				return "Dictionary<{0},{1}>".F(t.GetGenericArguments().Select(FriendlyTypeName).ToArray());
  
  			if (t.IsSubclassOf(typeof(Array)))
  				return "Multiple {0}".F(FriendlyTypeName(t.GetElementType()));
  
  			if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(OpenRA.Primitives.Cache<,>))
  				return "Cached<{0},{1}>".F(t.GetGenericArguments().Select(FriendlyTypeName).ToArray());
  
  			if (t == typeof(int) || t == typeof(uint))
  				return "Integer";
  
  			if (t == typeof(int2))
  				return "2D Integer";
  
  			if (t == typeof(float) || t == typeof(decimal))
  				return "Real Number";
  
  
gt: 			if (t == typeof(float2))