using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;

namespace ReScene
{
	struct RarBlock
	{
		public byte Type;
		public ushort Flags;
		public byte[] Data;
		public long FilePos;
	}

	struct HeaderBlock // 0x69
	{
		public static ushort SupportedFlagMask = 0x1;

		public string AppName;

		public HeaderBlock(RarBlock block)
		{
			BinaryReader br = new BinaryReader(new MemoryStream(block.Data), Encoding.ASCII);

			// skip 7-byte block header
			br.BaseStream.Position += 7;

			// if flag 0x1 is set, header contains 2 bytes for app name length, then the name
			if ((block.Flags & 0x1) != 0)
				AppName = new string(br.ReadChars(br.ReadUInt16()));
			else
				AppName = "Unknown";
		}
	}

	struct StoreBlock // 0x6A
	{
		public static ushort SupportedFlagMask = 0x8000;

		public string FileName;
		public ushort FileOffset;
		public uint FileLength;

		public StoreBlock(RarBlock block)
		{
			BinaryReader br = new BinaryReader(new MemoryStream(block.Data), Encoding.ASCII);

			// skip 7-byte block header
			br.BaseStream.Position += 7;

			// 4 bytes for file length
			FileLength = br.ReadUInt32();

			// 2 bytes for name length, then the name
			FileName = new string(br.ReadChars(br.ReadUInt16()));
			FileOffset = (ushort)br.BaseStream.Position;
		}
	}

	struct SrrBlock // 0x71
	{
		public static ushort SupportedFlagMask = 0x1;

		public string FileName;

		public SrrBlock(RarBlock block)
		{
			BinaryReader br = new BinaryReader(new MemoryStream(block.Data), Encoding.ASCII);

			// skip 7-byte block header
			br.BaseStream.Position += 7;

			// 2 bytes for name length, then the name
			FileName = new string(br.ReadChars(br.ReadUInt16()));
		}
	}

	struct FileBlock // 0x74 or 0x7A (FILE and NEWSUB share the same structure)
	{
		public byte CompressionMethod;
		public ulong PackedSize;
		public ulong UnpackedSize;
		public uint FileCrc;
		public string FileName;
		public uint RecoverySectors;
		public ulong DataSectors;

		public FileBlock(RarBlock block)
		{
			BinaryReader br = new BinaryReader(new MemoryStream(block.Data), Encoding.ASCII);

			// skip 7-byte block header
			br.BaseStream.Position += 7;

			// 4 bytes for packed size, 4 for unpacked
			PackedSize = br.ReadUInt32();
			UnpackedSize = br.ReadUInt32();

			// skip 1 byte for OS
			br.BaseStream.Position++;

			// 4 bytes for crc
			FileCrc = br.ReadUInt32();

			// skip 4 bytes for file date/time, 1 for required RAR version
			br.BaseStream.Position += 5;

			// 1 byte for compression method, then 2 for filename length
			CompressionMethod = br.ReadByte();
			ushort nameLength = br.ReadUInt16();

			// skip 4 bytes for file attributes
			br.BaseStream.Position += 4;

			// if large file flag is set, next are 4 bytes each for high order bits of file sizes
			if ((block.Flags & 0x0100) == 0x0100)
			{
				PackedSize += br.ReadUInt32() * 0x100000000ul;
				UnpackedSize += br.ReadUInt32() * 0x100000000ul;
			}

			// and finally, the file name
			FileName = new string(br.ReadChars(nameLength));

			// if block type is 0x7A and file name is RR, this is a recovery record.  sizes follow
			if (block.Type == 0x7A && FileName == "RR")
			{
				// skip 8 bytes for 'Protect+'
				br.BaseStream.Position += 8;

				// 4 bytes for recovery sector count, 8 bytes for data sector count
				RecoverySectors = br.ReadUInt32();
				DataSectors = br.ReadUInt64();
			}
			else
			{
				RecoverySectors = 0;
				DataSectors = 0;
			}
		}
	}

	class Program
	{
		private const string appName = "ReScene .NET Beta 6";
		private static bool Verbose = false;
		private static uint[] CrcTab = new uint[256];

		static void InitCrcTab()
		{
			for (uint i = 0; i < 256; i++)
			{
				uint c = i;
				for (int j = 0; j < 8; j++)
					c = (c & 1) == 1 ? (c >> 1) ^ 0xedb88320u : (c >> 1);
				CrcTab[i] = c;
			}
		}

		static uint UpdateCrc(uint startCrc, byte[] data, int offset, int length)
		{
			if (CrcTab[1] == 0)
				InitCrcTab();

			for (int i = offset; i < length + offset; i++)
				startCrc = CrcTab[(byte)(startCrc ^ data[i])] ^ (startCrc >> 8);

			return startCrc;
		}

		static void ReportError(string msg)
		{
			ConsoleColor normalColor = Console.ForegroundColor;
			Console.ForegroundColor = ConsoleColor.Red;
			Console.WriteLine(msg);
			Console.ForegroundColor = normalColor;
		}

		static void DisplayUsage()
		{
			Console.WriteLine(appName);
			Console.WriteLine("\nUsage:  srr <input file> [switches]");
			Console.WriteLine("\tTo create a reconstruction file (SRR), use the release SFV file.\n\tAll files referenced by the SFV must be in the same folder as the SFV.\n\t\tex: srr example.sfv -s *.nfo");
			Console.WriteLine("\tTo reconstruct a release, use the SRR file created from the release.\n\tAll files to be archived must be in the same folder as the SRR file.\n\t\tex: srr example.srr");
			Console.WriteLine("\nAvailable switches:");
			Console.WriteLine("\t-v: Enable verbose (technical) output.");
			Console.WriteLine("\t-d: Use directory name as basis for generated .srr file name.");
			Console.WriteLine("\t-s <file list>: Store additional files in the SRR (wildcards supported)");
			Console.WriteLine("\t-i <path>: Specify input directory (rebuild only).");
			Console.WriteLine("\t-o <path>: Specify output file or directory path.");
		}

		static void ReportUnsupportedFlag()
		{
			ReportError("Warning: Unsupported flag value encountered in SRR file.  This file may use features not supported in this version of the application");
		}

		static bool CheckOverwrite(string filePath)
		{
			if (File.Exists(filePath))
			{
				Console.WriteLine("Warning: File {0} already exists.  Do you wish to continue? (Y/N)", filePath);
				char res = Console.ReadKey(true).KeyChar;
				if (res != 'y' && res != 'Y')
					return false;
			}

			return true;
		}

		static IList<string> GetSfvFileList(byte[] sfvData)
		{
			// get list of files referenced by the SFV.  Make sure RAR files are sorted so we get the data in the right place
			SortedList<int, string> files = new SortedList<int, string>();

			StreamReader sr = new StreamReader(new MemoryStream(sfvData));
			Regex newNameFormat = new Regex(@"\.part(\d+)\.rar$", RegexOptions.IgnoreCase);
			Regex oldNameFormat = new Regex(@"\.([rs])(ar|\d{2})$", RegexOptions.IgnoreCase);
			Regex alternateNameFormat = new Regex(@"\.(\d{3})$");
			int format = 0;

			string line;
			while ((line = sr.ReadLine()) != null)
			{
				if (line.Trim().Length < 10 || line.StartsWith(";"))
					continue;

				// last 8 characters is crc32 value.  rest should be file name
				line = line.Substring(0, line.Length - 9).Trim();

				// fix for bug with strange names like .part1.rar, .part1.r00, etc.
				//  those look like both old and new format, so we'll pick one based on the first file and stick with it
				if (format == 0)
				{
					if (newNameFormat.IsMatch(line))
						format = 1;
					else if (oldNameFormat.IsMatch(line))
						format = 2;
					else if (alternateNameFormat.IsMatch(line))
						format = 3;
				}

				if (format == 1 && newNameFormat.IsMatch(line))
				{
					files.Add(int.Parse(newNameFormat.Match(line).Groups[1].Value), line);
				}
				else if (format == 2 && oldNameFormat.IsMatch(line))
				{
					Match match = oldNameFormat.Match(line);
					files.Add((match.Groups[1].Value.ToLower() == "s" ? 100 : 0) + (match.Groups[2].Value.ToLower() != "ar" ? int.Parse(match.Groups[2].Value) : -1), line);
				}
				else if (format == 3 && alternateNameFormat.IsMatch(line))
				{
					files.Add(int.Parse(alternateNameFormat.Match(line).Groups[1].Value), line);
				}
				else
				{
					ReportError(string.Format("Warning: Non-RAR file referenced in SFV.  This file cannot be recreated:\n\t{0}", line));
				}
			}

			return files.Values;
		}

		static IList<RarBlock> GetRarBlocks(string fileName, bool srrMode)
		{
			List<RarBlock> blocks = new List<RarBlock>();

			using (FileStream fs = new FileStream(fileName, FileMode.Open))
			{
				BinaryReader br = new BinaryReader(fs, Encoding.ASCII);
				bool includeRecovery = false;

				while (br.BaseStream.Position <= br.BaseStream.Length - 7)
				{
					long blockStartPos = br.BaseStream.Position;

					// block header is always 7 bytes.  2 for crc, 1 for block type, 2 for flags, and 2 for block length
					ushort crc = br.ReadUInt16();
					byte blockType = br.ReadByte();
					ushort flags = br.ReadUInt16();
					ushort length = br.ReadUInt16();

					// shouldn't happen, but if it does, we could go into an infinite loop
					if (length < 7)
					{
						ReportError("Warning: Invalid block length detected.  File parsing stopped prematurely.");
						break;
					}

					int addlLength = 0;
					// if ADD_SIZE flag is set, next 4 bytes are additional data size
					if ((flags & 0x8000) == 0x8000)
						addlLength = br.ReadInt32();

					// reset position to the beginning we can read the entire block
					br.BaseStream.Position = blockStartPos;

					byte[] buff = new byte[length];
					br.Read(buff, 0, length);

					// check for srr block and see if we should include recovery record data
					//  required for compatibility with rescene versions that don't remove recovery records
					if (blockType == 0x71)
						includeRecovery = (flags & 0x1) == 0;

					// next, check to see if this is a recovery record.  recovery records are stored in the RAR NEWSUB block type (0x7A)
					//  and have file name length of 2 (bytes 27 and 28) and a file name of RR (bytes 33 and 34)
					bool isRecovery = blockType == 0x7A && length > 34 && buff[26] == 0x02 && buff[27] == 0x00 && buff[32] == 0x52 && buff[33] == 0x52;

					// if there is additional data in the block, decide whether we want to include include it in the RarBlock we return
					//  the basic rules are  1) we don't return the additional data for file blocks and 2) we don't include data for
					//    recovery records, except if we are reading an SRR file and that file includes it
					bool skipAddData = blockType == 0x74 || (isRecovery && !includeRecovery);

					//  if we are building the SRR file, we want to copy the data unless we decided to skip it above
					if (!skipAddData && addlLength > 0)
					{
						byte[] oldbuff = buff;
						buff = new byte[length + addlLength];

						oldbuff.CopyTo(buff, 0);
						br.BaseStream.Read(buff, length, addlLength);
					}
					//  if we are in the process of reconstruction, the data isn't in the file, so don't try to read or skip it
					else if (!srrMode && addlLength > 0)
					{
						br.BaseStream.Seek(addlLength, SeekOrigin.Current);
					}

					blocks.Add(new RarBlock() { Type = blockType, Flags = flags, Data = buff, FilePos = blockStartPos });
				}
			}

			return blocks;
		}

		static int CreateReconstructionFile(FileInfo sfvFileInfo, List<string> storeFiles, string srrName)
		{
			using (FileStream srrfs = new FileStream(srrName, FileMode.Create))
			{
				BinaryWriter bw = new BinaryWriter(srrfs, Encoding.ASCII);

				// SRR blocks are based on RAR block format.  Header block type is 0x69.  We don't use crc for blocks (as of now), so crc value is set to 0x6969
				//  Flag 0x1 indicates the header contains appName. Length of the block is 7 (header length) + 2 bytes for appName length + the length of the appName.
				//  See http://datacompression.info/ArchiveFormats/RAR202.txt for more details on RAR file format
				bw.Write(new byte[] { 0x69, 0x69, 0x69, 0x01, 0x00 });
				bw.Write((ushort)(7 + 2 + appName.Length));
				bw.Write((ushort)appName.Length);
				bw.Write(appName.ToCharArray());

				// we store copies of any files included in the storeFiles list in the .srr using a "store block".  the SFV file is always included.
				//  since the sfv is always last, we'll leave its contents in the buffer when we move on the the next step.
				byte[] storeBuff = null;
				foreach (string fileName in storeFiles)
				{
					string searchName = fileName;
					if (!Path.IsPathRooted(searchName))
						searchName = Path.Combine(sfvFileInfo.DirectoryName, fileName);

					foreach (FileInfo storeFile in new DirectoryInfo(Path.GetDirectoryName(searchName)).GetFiles(Path.GetFileName(searchName)))
					{
						Console.WriteLine("Storing file: {0}", storeFile.Name);
						using (FileStream storefs = storeFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
						{
							storeBuff = new byte[storefs.Length];
							storefs.Read(storeBuff, 0, storeBuff.Length);
						}

						// store block (type 0x6A) has the 0x8000 flag set to indicate there is additional data following the block.
						// format is 7 byte header followed by 4 byte file size, 2 byte file name length, and file name
						bw.Write(new byte[] { 0x6A, 0x6A, 0x6A, 0x00, 0x80 });
						bw.Write((ushort)(7 + 4 + 2 + storeFile.Name.Length));
						bw.Write((uint)storeBuff.Length);
						bw.Write((ushort)storeFile.Name.Length);
						bw.Write(storeFile.Name.ToCharArray());

						// then the file data
						bw.Write(storeBuff);
					}
				}

				// sfv data should be left in the buffer, so let's use it from there
				foreach (string file in GetSfvFileList(storeBuff))
				{
					Console.WriteLine("Processing file: {0}", file);

					string fileName = Path.Combine(sfvFileInfo.DirectoryName, file);
					if (!File.Exists(fileName))
					{
						ReportError(string.Format("Referenced file not found: {0}", fileName));
						srrfs.Close();
						File.Delete(srrName);
						return 2;
					}

					// we create one SRR block (type 0x71) for each RAR file.
					//  it has 7 byte header, 2 bytes for file name length, then file name
					//  flag 0x1 means recovery records have been removed if present
					bw.Write(new byte[] { 0x71, 0x71, 0x71, 0x01, 0x00 });
					bw.Write((ushort)(7 + 2 + file.Length));
					bw.Write((ushort)file.Length);
					bw.Write(file.ToCharArray());

					foreach (RarBlock block in GetRarBlocks(fileName, false))
					{
						if (Verbose)
						{
							Console.WriteLine("\tBlock Type: 0x{0:x2}", block.Type);
							Console.WriteLine("\tBlock Size: {0}", block.Data.Length);
						}

						if (block.Type == 0x74) // file block
						{
							FileBlock fileData = new FileBlock(block);

							if (Verbose)
							{
								Console.WriteLine("\t\tCompression Type: 0x{0:x2}", fileData.CompressionMethod);
								Console.WriteLine("\t\tPacked Data Size: {0:n0}", fileData.PackedSize);
								Console.WriteLine("\t\tFile Size: {0:n0}", fileData.UnpackedSize);
								Console.WriteLine("\t\tFile Name: {0}", fileData.FileName);
							}

							if (fileData.CompressionMethod != 0x30)
							{
								ReportError(string.Format("Archive uses unsupported compression method: {0}", fileName));
								srrfs.Close();
								File.Delete(srrName);
								return 3;
							}
						}
						else if (block.Type == 0x7A) // newsub block
						{
							FileBlock subData = new FileBlock(block);

							if (Verbose & subData.RecoverySectors > 0)
							{
								Console.WriteLine("\t\tRecovery Record Size: {0:n0}", subData.PackedSize);
								Console.WriteLine("\t\tRecovery Sectors: {0:n0}", subData.RecoverySectors);
								Console.WriteLine("\t\tProtected Sectors: {0:n0}", subData.DataSectors);
							}
						}

						// store the raw data for any blocks found
						bw.Write(block.Data);
					}
				}
			}

			Console.WriteLine("\nReconstruction file successfully created: {0}", srrName);

			return 0;
		}

		static int AddStoredFiles(FileInfo srrFileInfo, List<string> storeFiles)
		{
			FileInfo newFileInfo = new FileInfo(srrFileInfo.FullName + ".tmp");

			using (FileStream fsOut = newFileInfo.Create())
			using (BinaryWriter bw = new BinaryWriter(fsOut))
			{
				bool filesAdded = false;
				foreach (RarBlock block in GetRarBlocks(srrFileInfo.FullName, true))
				{
					if (block.Type == 0x71 && !filesAdded)
					{
						byte[] storeBuff = null;
						foreach (string fileName in storeFiles)
						{
							string searchName = fileName;
							if (!Path.IsPathRooted(searchName))
								searchName = Path.Combine(srrFileInfo.DirectoryName, fileName);

							foreach (FileInfo storeFile in new DirectoryInfo(Path.GetDirectoryName(searchName)).GetFiles(Path.GetFileName(searchName)))
							{
								Console.WriteLine("Storing file: {0}", storeFile.Name);
								using (FileStream storefs = storeFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
								{
									storeBuff = new byte[storefs.Length];
									storefs.Read(storeBuff, 0, storeBuff.Length);
								}

								// store block (type 0x6A) has the 0x8000 flag set to indicate there is additional data following the block.
								// format is 7 byte header followed by 4 byte file size, 2 byte file name length, and file name
								bw.Write(new byte[] { 0x6A, 0x6A, 0x6A, 0x00, 0x80 });
								bw.Write((ushort)(7 + 4 + 2 + storeFile.Name.Length));
								bw.Write((uint)storeBuff.Length);
								bw.Write((ushort)storeFile.Name.Length);
								bw.Write(storeFile.Name.ToCharArray());

								// then the file data
								bw.Write(storeBuff);
							}
						}

						filesAdded = true;
					}

					bw.Write(block.Data);
				}
			}

			srrFileInfo.Delete();
			newFileInfo.MoveTo(srrFileInfo.FullName);

			return 0;
		}

		static int Reconstruct(FileInfo srrFileInfo, DirectoryInfo inFolder, DirectoryInfo outFolder)
		{
			if (!outFolder.Exists)
				outFolder.Create();

			string rarName = null, srcName = null;
			FileStream rarfs = null, srcfs = null;
			bool rebuildRecovery = false;
			byte[] copyBuff = new byte[0x10000];

			foreach (RarBlock block in GetRarBlocks(srrFileInfo.FullName, true))
			{
				if (Verbose)
				{
					Console.WriteLine("\tBlock Type: 0x{0:x2}", block.Type);
					Console.WriteLine("\tBlock Size: {0}", block.Data.Length);
				}

				if (block.Type == 0x69) // header block
				{
					// file header block.  the only thing here so far is the name of the app that created the SRR file
					if ((block.Flags & ~HeaderBlock.SupportedFlagMask) != 0)
						ReportUnsupportedFlag();

					HeaderBlock headBlock = new HeaderBlock(block);

					if (Verbose)
						Console.WriteLine("SRR file created with {0}", headBlock.AppName);
				}
				else if (block.Type == 0x6A) // store block
				{
					// There is a file stored within the .srr.  extract it.
					if ((block.Flags & ~StoreBlock.SupportedFlagMask) != 0)
						ReportUnsupportedFlag();

					StoreBlock sb = new StoreBlock(block);
					string fileName = Path.Combine(outFolder.FullName, sb.FileName);
					if (CheckOverwrite(fileName))
					{
						Console.WriteLine("Re-creating stored file: {0}", sb.FileName);
						using (FileStream sffs = new FileStream(Path.Combine(outFolder.FullName, fileName), FileMode.Create))
						{
							sffs.Write(block.Data, sb.FileOffset, (int)sb.FileLength);
						}
					}
					else
					{
						ReportError("Operation aborted.");
						return -1;
					}
				}
				/*
				else if (block.Type >= 0x6B && block.Type <= 70)
				{
					// reserved for future use
				}
				*/
				else if (block.Type == 0x71) // srr block
				{
					// for each SRR block, we need to create a RAR file.  get the stored name and create it.
					if ((block.Flags & ~SrrBlock.SupportedFlagMask) != 0)
						ReportUnsupportedFlag();

					SrrBlock srrBlock = new SrrBlock(block);

					if (rarName != srrBlock.FileName)
					{
						// we use flag 0x1 to mark files that have recovery records removed.  all other flags are currently undefined.
						rebuildRecovery = (block.Flags & 0x1) != 0;

						rarName = srrBlock.FileName;
						if (rarfs != null)
							rarfs.Close();

						if (CheckOverwrite(Path.Combine(outFolder.FullName, rarName)))
						{
							rarfs = new FileStream(Path.Combine(outFolder.FullName, rarName), FileMode.Create, FileAccess.ReadWrite);
							Console.WriteLine("Re-creating RAR file: {0}", srrBlock.FileName);
						}
						else
						{
							ReportError("Operation aborted.");
							return -1;
						}
					}
				}
				else if (block.Type == 0x74) // file data
				{
					// this is the main RAR block we treat differently.  We removed the data when storing it, so we need to get the data back from the extracted file
					FileBlock fileData = new FileBlock(block);

					if (Verbose)
					{
						Console.WriteLine("\t\tPacked Data Size: {0:n0}", fileData.PackedSize);
						Console.WriteLine("\t\tFile Name: {0}", fileData.FileName);
					}

					if (srcName != fileData.FileName)
					{
						srcName = fileData.FileName;
						if (srcfs != null)
							srcfs.Close();

						FileInfo srcInfo = new FileInfo(Path.Combine(inFolder.FullName, srcName));
						if (!srcInfo.Exists)
						{
							ReportError(string.Format("Could not locate data file: {0}", srcInfo.FullName));
							return 4;
						}
						if ((ulong)srcInfo.Length != fileData.UnpackedSize)
						{
							ReportError(string.Format("Data file is not the correct size: {0}\n\tFound: {1:n0}\n\tExpected: {2:n0}", srcInfo.FullName, srcInfo.Length, fileData.UnpackedSize));
							return 5;
						}

						srcfs = new FileStream(srcInfo.FullName, FileMode.Open);
					}

					// write the block contents from the .srr file
					rarfs.Write(block.Data, 0, block.Data.Length);

					// then grab the correct amount of data from the extracted file
					int bytesCopied = 0;
					while (bytesCopied < (int)fileData.PackedSize)
					{
						int bytesToCopy = (int)fileData.PackedSize - bytesCopied;
						if (bytesToCopy > copyBuff.Length)
							bytesToCopy = copyBuff.Length;

						srcfs.Read(copyBuff, 0, bytesToCopy);
						rarfs.Write(copyBuff, 0, bytesToCopy);
						bytesCopied += bytesToCopy;
					}
				}
				else if (block.Type == 0x7A) // newsub block
				{
					// the multi-purpose newsub block is used for recovery record data.  it consists of two parts: crc's and recovery sectors
					//  all file data preceding the recovery record block is protected by the recovery record.  that data is broken into sectors of 512 bytes.
					//  the crc portion of the recovery block is the 2 low-order bytes of the crc32 value for each sector (2 bytes * protected sector count)
					//  the recovery sectors are created by breaking the data into slices based on the recovery sector count. (512 bytes * recovery sector count)
					//  each slice will get one parity sector created by xor-ing the corresponding bytes from all other sectors in the slice.
					FileBlock subData = new FileBlock(block);

					if (subData.RecoverySectors > 0 && rebuildRecovery)
					{
						if (Verbose)
						{
							Console.WriteLine("\t\tCRC entries to rebuild: {0:n0}", subData.DataSectors);
							Console.WriteLine("\t\tRecovery sectors to rebuild: {0:n0}", subData.RecoverySectors);
						}

						byte[] crc = new byte[subData.DataSectors * 2];
						byte[][] rr = new byte[subData.RecoverySectors][];
						for (int i = 0; i < subData.RecoverySectors; i++)
							rr[i] = new byte[512];

						int rrSlice = 0;
						long currentSector = 0;
						long rarPos = rarfs.Position;

						byte[] sector = new byte[512];
						rarfs.Position = 0;
						while (rarfs.Position < rarPos)
						{
							// read data 1 sector at a time.  pad the last sector with 0's
							if (rarPos - rarfs.Position >= 512)
								rarfs.Read(sector, 0, 512);
							else
							{
								long pos = rarfs.Position;
								rarfs.Read(sector, 0, (int)(rarPos - pos));
								for (int i = (int)(rarPos - pos); i < 512; i++)
									sector[i] = 0;
							}

							// calculate the crc32 for the sector and store the 2 low-order bytes
							ushort sectorCrc = (ushort)(UpdateCrc(0xffffffff, sector, 0, sector.Length) & 0xffff);
							crc[currentSector * 2] = (byte)(sectorCrc & 0xff);
							crc[currentSector * 2 + 1] = (byte)((sectorCrc >> 8) & 0xff);
							currentSector++;

							// update the recovery sector parity data for this slice
							for (int i = 0; i < 512; i++)
								rr[rrSlice][i] ^= sector[i];

							if (++rrSlice % subData.RecoverySectors == 0)
								rrSlice = 0;
						}

						// write the backed-up block header, crc data, and recovery sectors
						rarfs.Write(block.Data, 0, block.Data.Length);
						rarfs.Write(crc, 0, crc.Length);
						foreach (byte[] ba in rr)
							rarfs.Write(ba, 0, ba.Length);
					}
					else
					{
						// block is from a previous ReScene version or is not a recovery record.  just copy it
						rarfs.Write(block.Data, 0, block.Data.Length);
					}
				}
				else if (block.Type >= 0x72 && block.Type <= 0x7B) // rar block
				{
					// copy any other rar blocks to the destination unmodified
					rarfs.Write(block.Data, 0, block.Data.Length);
				}
				else
				{
					ReportError(string.Format("Warning: Unknown block type ({0:X2}) encountered in SRR file, consisting of {1:n0} bytes.  This block will be skipped.", block.Type, block.Data.Length));
				}
			}

			if (rarfs != null)
				rarfs.Close();

			Console.WriteLine("\nRelease successfully reconstructed.  Please re-check files against the SFV to verify before using.");

			return 0;
		}

		static Dictionary<string, List<string> > GetArgsDictionary(string[] args)
		{
			Dictionary<string, List<string>> dict = new Dictionary<string, List<string> >();
			string cmdSwitch = null;
			List<string> switchParams = null;

			for (int i = 1; i < args.Length; i++)
			{
				if (args[i].StartsWith("-") || args[i].StartsWith("/"))
				{
					if (cmdSwitch != null)
						dict.Add(cmdSwitch, switchParams);

					cmdSwitch = args[i].Substring(1).ToLower();
					switchParams = new List<string>();
				}
				else
				{
					if (switchParams != null)
						switchParams.Add(args[i]);
				}
			}
			if (cmdSwitch != null)
				dict.Add(cmdSwitch, switchParams);

			return dict;
		}

		static int Main(string[] args)
		{
			try
			{
				Dictionary<string, List<string> > argDict = GetArgsDictionary(args);
				if (args.Length < 1 || argDict.ContainsKey("?"))
				{
					DisplayUsage();
					return -1;
				}

				Verbose = argDict.ContainsKey("v");

				FileInfo inputFileInfo = new FileInfo(args[0]);
				if (!inputFileInfo.Exists)
				{
					ReportError(string.Format("Input file not found: {0}\n", inputFileInfo.FullName));
					DisplayUsage();
					return 1;
				}
				else if (inputFileInfo.Extension.ToLower() == ".sfv")
				{
					List<string> storeFiles = argDict.ContainsKey("s") ? argDict["s"] : new List<string>();
					storeFiles.Add(inputFileInfo.Name);

					string srrName = null;
					string outFolder = null;
					if (argDict.ContainsKey("o") && argDict["o"].Count == 1)
						if (Path.GetExtension(argDict["o"][0]).ToLower() == ".srr")
							srrName = argDict["o"][0];
						else
							outFolder = argDict["o"][0];
					else
						outFolder = inputFileInfo.DirectoryName;

					if (srrName == null)
					{
						if (argDict.ContainsKey("d"))
							srrName = Path.Combine(outFolder, inputFileInfo.Directory.Name + ".srr");
						else
							srrName = Path.Combine(outFolder, Path.GetFileNameWithoutExtension(inputFileInfo.Name) + ".srr");
					}

					return CreateReconstructionFile(inputFileInfo, storeFiles, srrName);
				}
				else if (inputFileInfo.Extension.ToLower() == ".srr")
				{
					DirectoryInfo outFolder;
					if (argDict.ContainsKey("o") && argDict["o"].Count == 1)
						outFolder = new DirectoryInfo(argDict["o"][0]);
					else
						outFolder = new DirectoryInfo(inputFileInfo.DirectoryName);

					DirectoryInfo inFolder;
					if (argDict.ContainsKey("i") && argDict["i"].Count == 1)
						inFolder = new DirectoryInfo(argDict["i"][0]);
					else
						inFolder = new DirectoryInfo(inputFileInfo.DirectoryName);

					if (argDict.ContainsKey("s"))
						return AddStoredFiles(inputFileInfo, argDict["s"]);
					else
						return Reconstruct(inputFileInfo, inFolder, outFolder);
				}
				else
				{
					ReportError(string.Format("Input file type not recognized: {0}\n", inputFileInfo.Extension));
					DisplayUsage();
					return -1;
				}
			}
			catch (Exception ex)
			{
				ReportError(string.Format("Unexpected Error:\n{0}", ex.ToString()));
				return 99;
			}
		}
	}
}
