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 [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 : Store additional files in the SRR (wildcards supported)");
Console.WriteLine("\t-i : Specify input directory (rebuild only).");
Console.WriteLine("\t-o : 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 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 files = new SortedList();
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 GetRarBlocks(string fileName, bool srrMode)
{
List blocks = new List();
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 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 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 > GetArgsDictionary(string[] args)
{
Dictionary> dict = new Dictionary >();
string cmdSwitch = null;
List 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();
}
else
{
if (switchParams != null)
switchParams.Add(args[i]);
}
}
if (cmdSwitch != null)
dict.Add(cmdSwitch, switchParams);
return dict;
}
static int Main(string[] args)
{
try
{
Dictionary > 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 storeFiles = argDict.ContainsKey("s") ? argDict["s"] : new List();
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;
}
}
}
}