diff --git a/CUETools/CUETools.sln b/CUETools/CUETools.sln
index a618e4f..0bc6484 100644
--- a/CUETools/CUETools.sln
+++ b/CUETools/CUETools.sln
@@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AudioCodecsDotNet", "..\Aud
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ALACDotNet", "..\ALACDotNet\ALACDotNet.csproj", "{F2EC7193-D5E5-4252-9803-5CEB407E910F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnRarDotNet", "..\UnRarDotNet\UnRarDotNet.csproj", "{8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -200,6 +202,16 @@ Global
{F2EC7193-D5E5-4252-9803-5CEB407E910F}.Release|x64.Build.0 = Release|x64
{F2EC7193-D5E5-4252-9803-5CEB407E910F}.Release|x86.ActiveCfg = Release|x86
{F2EC7193-D5E5-4252-9803-5CEB407E910F}.Release|x86.Build.0 = Release|x86
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}.Debug|x64.ActiveCfg = Debug|x64
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}.Debug|x64.Build.0 = Debug|x64
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}.Debug|x86.ActiveCfg = Debug|x86
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}.Debug|x86.Build.0 = Debug|x86
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}.Release|x64.ActiveCfg = Release|x64
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}.Release|x64.Build.0 = Release|x64
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}.Release|x86.ActiveCfg = Release|x86
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/CUEToolsLib/AudioReadWrite.cs b/CUEToolsLib/AudioReadWrite.cs
index b8e0e4a..bf987b2 100644
--- a/CUEToolsLib/AudioReadWrite.cs
+++ b/CUEToolsLib/AudioReadWrite.cs
@@ -28,6 +28,18 @@ namespace CUEToolsLib {
throw new Exception("Unsupported audio type.");
}
}
+ public static IAudioSource GetAudioSource(string path, Stream IO)
+ {
+ switch (Path.GetExtension(path).ToLower())
+ {
+#if !MONO
+ case ".flac":
+ return new FLACReader(path, IO);
+#endif
+ default:
+ throw new Exception("Unsupported audio type in archive.");
+ }
+ }
public static IAudioDest GetAudioDest(string path, int bitsPerSample, int channelCount, int sampleRate, long finalSampleCount) {
IAudioDest dest;
@@ -59,7 +71,14 @@ namespace CUEToolsLib {
uint _bufferOffset, _bufferLength;
public FLACReader(string path) {
- _flacReader = new FLACDotNet.FLACReader(path);
+ _flacReader = new FLACDotNet.FLACReader(path, null);
+ _bufferOffset = 0;
+ _bufferLength = 0;
+ }
+
+ public FLACReader(string path, Stream IO)
+ {
+ _flacReader = new FLACDotNet.FLACReader(path, IO);
_bufferOffset = 0;
_bufferLength = 0;
}
diff --git a/CUEToolsLib/CUEToolsLib.csproj b/CUEToolsLib/CUEToolsLib.csproj
index ef4f6c9..57b1fb1 100644
--- a/CUEToolsLib/CUEToolsLib.csproj
+++ b/CUEToolsLib/CUEToolsLib.csproj
@@ -109,6 +109,10 @@
{32338A04-5B6B-4C63-8EE7-C6400F73B5D7}
HDCDDotNet
+
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}
+ UnRarDotNet
+
{CC2E74B6-534A-43D8-9F16-AC03FE955000}
WavPackDotNet
diff --git a/CUEToolsLib/Main.cs b/CUEToolsLib/Main.cs
index bb14ee5..358e1f6 100644
--- a/CUEToolsLib/Main.cs
+++ b/CUEToolsLib/Main.cs
@@ -29,6 +29,9 @@ using System.Net;
using System.Threading;
using AudioCodecsDotNet;
using HDCDDotNet;
+#if !MONO
+using UnRarDotNet;
+#endif
namespace CUEToolsLib
{
@@ -415,6 +418,8 @@ namespace CUEToolsLib
private HDCDDotNet.HDCDDotNet hdcdDecoder;
CUEConfig _config;
string _cddbDiscIdTag;
+ private bool _isArchive;
+ private string _archivePath;
public CUESheet(string pathIn, CUEConfig config)
{
@@ -466,6 +471,7 @@ namespace CUEToolsLib
seenDataTrack = false;
accDisks = new List();
_hasEmbeddedCUESheet = false;
+ _isArchive = false;
TextReader sr;
@@ -480,8 +486,56 @@ namespace CUEToolsLib
if (cueSheet == null)
throw new Exception("Input directory doesn't contain supported audio files.");
sr = new StringReader(cueSheet);
+ }
+#if !MONO
+ else if (Path.GetExtension(pathIn).ToLower() == ".rar")
+ {
+ Unrar _unrar = new Unrar();
+ string cueName = null, cueText = null;
+ _unrar.Open(pathIn, Unrar.OpenMode.List);
+ while (_unrar.ReadHeader())
+ {
+ if (!_unrar.CurrentFile.IsDirectory && Path.GetExtension(_unrar.CurrentFile.FileName).ToLower() == ".cue")
+ {
+ cueName = _unrar.CurrentFile.FileName;
+ break;
+ }
+ else
+ _unrar.Skip();
+ }
+ _unrar.Close();
+ if (cueName != null)
+ try
+ {
+ RarStream rarStream = new RarStream(pathIn, cueName);
+ StreamReader cueReader = new StreamReader(rarStream, CUESheet.Encoding);
+ cueText = cueReader.ReadToEnd();
+ cueReader.Close();
+ rarStream.Close();
+ }
+ catch { }
+ if (cueText == null)
+ throw new Exception("Input archive doesn't contain a cue sheet.");
+ sr = new StringReader(cueText);
+ _isArchive = true;
+ _archivePath = pathIn;
+ }
+#endif
+ else if (Path.GetExtension(pathIn).ToLower() == ".cue")
+ {
+ if (_config.autoCorrectFilenames)
+ sr = new StringReader (CorrectAudioFilenames(pathIn, false));
+ else
+ sr = new StreamReader (pathIn, CUESheet.Encoding);
+
+ try
+ {
+ StreamReader logReader = new StreamReader(Path.ChangeExtension(pathIn, ".log"), CUESheet.Encoding);
+ _eacLog = logReader.ReadToEnd();
+ logReader.Close();
+ }
+ catch { }
} else
- if (Path.GetExtension(pathIn).ToLower() != ".cue")
{
IAudioSource audioSource;
NameValueCollection tags;
@@ -500,20 +554,6 @@ namespace CUEToolsLib
sr = new StringReader (cuesheetTag);
pathAudio = pathIn;
_hasEmbeddedCUESheet = true;
- } else
- {
- if (_config.autoCorrectFilenames)
- sr = new StringReader (CorrectAudioFilenames(pathIn, false));
- else
- sr = new StreamReader (pathIn, CUESheet.Encoding);
-
- try
- {
- StreamReader logReader = new StreamReader(Path.ChangeExtension(pathIn, ".log"), CUESheet.Encoding);
- _eacLog = logReader.ReadToEnd();
- logReader.Close();
- }
- catch { }
}
using (sr) {
@@ -533,10 +573,17 @@ namespace CUEToolsLib
else {
if (!_hasEmbeddedCUESheet)
{
- pathAudio = LocateFile(cueDir, line.Params[1]);
- if (pathAudio == null)
+#if !MONO
+ if (_isArchive)
+ pathAudio = line.Params[1];
+ else
{
- throw new Exception("Unable to locate file \"" + line.Params[1] + "\".");
+#endif
+ pathAudio = LocateFile(cueDir, line.Params[1]);
+ if (pathAudio == null)
+ {
+ throw new Exception("Unable to locate file \"" + line.Params[1] + "\".");
+ }
}
} else
{
@@ -645,6 +692,18 @@ namespace CUEToolsLib
{
_accurateRipId = line.Params[2];
}
+ //else if ((command == "REM") &&
+ // (line.Params.Count == 3) &&
+ // (line.Params[1].ToUpper() == "SHORTEN"))
+ //{
+ // fileTimeLengthFrames -= General.TimeFromString(line.Params[2]);
+ //}
+ //else if ((command == "REM") &&
+ // (line.Params.Count == 3) &&
+ // (line.Params[1].ToUpper() == "LENGTHEN"))
+ //{
+ // fileTimeLengthFrames += General.TimeFromString(line.Params[2]);
+ //}
else
{
if (trackInfo != null)
@@ -951,7 +1010,14 @@ namespace CUEToolsLib
{
IAudioSource audioSource;
- audioSource = AudioReadWrite.GetAudioSource(path);
+#if !MONO
+ if (_isArchive)
+ {
+ RarStream IO = new RarStream(_archivePath, path);
+ audioSource = AudioReadWrite.GetAudioSource(path, IO);
+ } else
+#endif
+ audioSource = AudioReadWrite.GetAudioSource(path);
if ((audioSource.BitsPerSample != 16) ||
(audioSource.ChannelCount != 2) ||
@@ -1686,7 +1752,7 @@ namespace CUEToolsLib
if (_accurateRip)
{
statusDel((string)"Generating AccurateRip report...", 0, 0, null, null);
- if (!_accurateOffset && _config.writeArTagsOnVerify && _writeOffset == 0)
+ if (!_accurateOffset && _config.writeArTagsOnVerify && _writeOffset == 0 && !_isArchive)
{
uint tracksMatch;
int bestOffset;
@@ -2141,7 +2207,15 @@ namespace CUEToolsLib
return sw.ToString();
}
- public static string CorrectAudioFilenames(string path, bool always) {
+ public static string CorrectAudioFilenames(string path, bool always)
+ {
+ StreamReader sr = new StreamReader(path, CUESheet.Encoding);
+ string cue = sr.ReadToEnd();
+ sr.Close();
+ return CorrectAudioFilenames(Path.GetDirectoryName(path), cue, always);
+ }
+
+ public static string CorrectAudioFilenames(string dir, string cue, bool always) {
string[] audioExts = new string[] { "*.wav", "*.flac", "*.wv", "*.ape", "*.m4a" };
List lines = new List();
List filePos = new List();
@@ -2150,12 +2224,9 @@ namespace CUEToolsLib
string[] audioFiles = null;
string lineStr;
CUELine line;
- string dir;
int i;
- dir = Path.GetDirectoryName(path);
-
- using (StreamReader sr = new StreamReader(path, CUESheet.Encoding)) {
+ using (StringReader sr = new StringReader(cue)) {
while ((lineStr = sr.ReadLine()) != null) {
lines.Add(lineStr);
line = new CUELine(lineStr);
@@ -2335,10 +2406,19 @@ namespace CUEToolsLib
audioSource = new SilenceGenerator(sourceInfo.Offset + sourceInfo.Length);
}
else {
- audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path);
+#if !MONO
+ if (_isArchive)
+ {
+ RarStream IO = new RarStream(_archivePath, sourceInfo.Path);
+ audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path, IO);
+ }
+ else
+#endif
+ audioSource = AudioReadWrite.GetAudioSource(sourceInfo.Path);
}
- audioSource.Position = sourceInfo.Offset;
+ if (sourceInfo.Offset != 0)
+ audioSource.Position = sourceInfo.Offset;
return audioSource;
}
diff --git a/FLACDotNet/flacdotnet.cpp b/FLACDotNet/flacdotnet.cpp
index 7a8d8dd..30a6f42 100644
--- a/FLACDotNet/flacdotnet.cpp
+++ b/FLACDotNet/flacdotnet.cpp
@@ -69,7 +69,7 @@ namespace FLACDotNet {
public ref class FLACReader {
public:
- FLACReader(String^ path) {
+ FLACReader(String^ path, Stream^ IO) {
_tags = gcnew NameValueCollection();
_writeDel = gcnew DecoderWriteDelegate(this, &FLACReader::WriteCallback);
@@ -88,7 +88,10 @@ namespace FLACDotNet {
_sampleBuffer = nullptr;
_path = path;
- _IO = gcnew FileStream (path, FileMode::Open, FileAccess::Read, FileShare::Read);
+ if (IO)
+ _IO = IO;
+ else
+ _IO = gcnew FileStream (path, FileMode::Open, FileAccess::Read, FileShare::Read);
_decoder = FLAC__stream_decoder_new();
@@ -252,7 +255,10 @@ namespace FLACDotNet {
_decoderActive = false;
}
if (_IO != nullptr)
+ {
_IO->Close ();
+ _IO = nullptr;
+ }
}
Int32 Read([Out] array^% sampleBuffer)
@@ -288,7 +294,7 @@ namespace FLACDotNet {
NameValueCollection^ _tags;
String^ _path;
bool _decoderActive;
- FileStream^ _IO;
+ Stream^ _IO;
FLAC__StreamDecoderWriteStatus WriteCallback(const FLAC__StreamDecoder *decoder,
const FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *client_data)
diff --git a/UnRarDotNet/Properties/AssemblyInfo.cs b/UnRarDotNet/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..d6fdbc8
--- /dev/null
+++ b/UnRarDotNet/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("UnRarDotNet")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("UnRarDotNet")]
+[assembly: AssemblyCopyright("Copyright © Gregory S. Chudov, Michael A. McCloskey")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("87d4581b-faf5-4b85-999c-247f0a3eb64a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Revision and Build Numbers
+// by using the '*' as shown below:
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/UnRarDotNet/RarStream.cs b/UnRarDotNet/RarStream.cs
new file mode 100644
index 0000000..a1ea2d8
--- /dev/null
+++ b/UnRarDotNet/RarStream.cs
@@ -0,0 +1,170 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.IO;
+using System.Threading;
+
+
+/* Author: Gregory S. Chudov
+ *
+ */
+
+namespace UnRarDotNet
+{
+ public class RarStream : Stream
+ {
+ public RarStream(string archive, string fileName)
+ {
+ _stop = false;
+ _unrar = new Unrar();
+ _buffer = null;
+ _offset = 0;
+ _length = 0;
+ _unrar.PasswordRequired += new PasswordRequiredHandler(unrar_PasswordRequired);
+ _unrar.DataAvailable += new DataAvailableHandler(unrar_DataAvailable);
+ _unrar.Open(archive, Unrar.OpenMode.Extract);
+ _fileName = fileName;
+ _workThread = new Thread(Decompress);
+ _workThread.Priority = ThreadPriority.BelowNormal;
+ _workThread.IsBackground = true;
+ _workThread.Start(null);
+ }
+ public override bool CanRead
+ {
+ get { return true; }
+ }
+ public override bool CanSeek
+ {
+ get { return false; }
+ }
+ public override bool CanWrite
+ {
+ get { return false; }
+ }
+ public override long Length
+ {
+ get { throw new NotSupportedException(); }
+ }
+ public override long Position
+ {
+ get { throw new NotSupportedException(); }
+ set { Seek(value, SeekOrigin.Begin); }
+ }
+ public override void Close()
+ {
+ lock (this)
+ {
+ _stop = true;
+ Monitor.Pulse(this);
+ }
+ _workThread.Join();
+ _workThread = null;
+ _unrar.Close();
+ base.Close();
+ }
+ public override void Flush()
+ {
+ throw new NotSupportedException();
+ }
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+ public override int Read(byte[] array, int offset, int count)
+ {
+ int total = 0;
+ while (count > 0)
+ {
+ lock (this)
+ {
+ while (_buffer == null && !_stop)
+ Monitor.Wait(this);
+ if (_buffer == null)
+ return total;
+ if (_length > count)
+ {
+ Array.Copy(_buffer, _offset, array, offset, count);
+ total += count;
+ _offset += count;
+ _length -= count;
+ return total;
+ }
+ Array.Copy(_buffer, _offset, array, offset, _length);
+ total += _length;
+ offset += _length;
+ count -= _length;
+ _buffer = null;
+ Monitor.Pulse(this);
+ }
+ }
+ return total;
+ }
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+ public override void Write(byte[] array, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+
+ private Unrar _unrar;
+ private string _fileName;
+ private Thread _workThread;
+ private bool _stop;
+ private byte[] _buffer;
+ int _offset, _length;
+
+ private void unrar_PasswordRequired(object sender, PasswordRequiredEventArgs e)
+ {
+ e.Password = "PARS";
+ e.ContinueOperation = true;
+ }
+
+ private void unrar_DataAvailable(object sender, DataAvailableEventArgs e)
+ {
+ lock (this)
+ {
+ while (_buffer != null && !_stop)
+ Monitor.Wait(this);
+ if (_stop)
+ {
+ e.ContinueOperation = false;
+ Monitor.Pulse(this);
+ return;
+ }
+ _buffer = e.Data;
+ _length = _buffer.Length;
+ _offset = 0;
+ e.ContinueOperation = true;
+ Monitor.Pulse(this);
+ }
+ }
+
+ private void Decompress(object o)
+ {
+ //try
+ {
+ while (_unrar.ReadHeader())
+ {
+ if (_unrar.CurrentFile.FileName == _fileName)
+ {
+ // unrar.CurrentFile.UnpackedSize;
+ _unrar.Test();
+ lock (this)
+ {
+ _stop = true;
+ Monitor.Pulse(this);
+ }
+ break;
+ }
+ else
+ _unrar.Skip();
+ }
+ }
+ //catch (StopExtractionException)
+ //{
+ //}
+ }
+ }
+}
diff --git a/UnRarDotNet/UnRarDotNet.csproj b/UnRarDotNet/UnRarDotNet.csproj
new file mode 100644
index 0000000..a15809d
--- /dev/null
+++ b/UnRarDotNet/UnRarDotNet.csproj
@@ -0,0 +1,92 @@
+
+
+ Debug
+ AnyCPU
+ 8.0.50727
+ 2.0
+ {8427CAA5-80B8-4952-9A68-5F3DFCFBDF40}
+ Library
+ Properties
+ UnRarDotNet
+ UnRarDotNet
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+ true
+ ..\bin\x64\Debug\
+ DEBUG;TRACE
+ full
+ x64
+ C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules
+ true
+ GlobalSuppressions.cs
+ prompt
+
+
+ ..\bin\x64\Release\
+ TRACE
+ true
+ pdbonly
+ x64
+ C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules
+ true
+ GlobalSuppressions.cs
+ prompt
+
+
+ true
+ ..\bin\Win32\Debug\
+ DEBUG;TRACE
+ full
+ x86
+ C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules
+ true
+ GlobalSuppressions.cs
+ prompt
+
+
+ ..\bin\Win32\Release\
+ TRACE
+ true
+ pdbonly
+ x86
+ C:\Program Files (x86)\Microsoft Visual Studio 8\Team Tools\Static Analysis Tools\FxCop\\rules
+ true
+ GlobalSuppressions.cs
+ prompt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/UnRarDotNet/Unrar.cs b/UnRarDotNet/Unrar.cs
new file mode 100644
index 0000000..a670de3
--- /dev/null
+++ b/UnRarDotNet/Unrar.cs
@@ -0,0 +1,969 @@
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Collections;
+
+
+/* Author: Michael A. McCloskey
+ * Company: Schematrix
+ * Version: 20040714
+ *
+ * Personal Comments:
+ * I created this unrar wrapper class for personal use
+ * after running into a number of issues trying to use
+ * another COM unrar product via COM interop. I hope it
+ * proves as useful to you as it has to me and saves you
+ * some time in building your own products.
+ */
+
+namespace UnRarDotNet
+{
+ #region Event Delegate Definitions
+
+ ///
+ /// Represents the method that will handle data available events
+ ///
+ public delegate void DataAvailableHandler(object sender, DataAvailableEventArgs e);
+ ///
+ /// Represents the method that will handle extraction progress events
+ ///
+ public delegate void ExtractionProgressHandler(object sender, ExtractionProgressEventArgs e);
+ ///
+ /// Represents the method that will handle missing archive volume events
+ ///
+ public delegate void MissingVolumeHandler(object sender, MissingVolumeEventArgs e);
+ ///
+ /// Represents the method that will handle new volume events
+ ///
+ public delegate void NewVolumeHandler(object sender, NewVolumeEventArgs e);
+ ///
+ /// Represents the method that will handle new file notifications
+ ///
+ public delegate void NewFileHandler(object sender, NewFileEventArgs e);
+ ///
+ /// Represents the method that will handle password required events
+ ///
+ public delegate void PasswordRequiredHandler(object sender, PasswordRequiredEventArgs e);
+
+ #endregion
+
+ ///
+ /// Wrapper class for unrar DLL supplied by RARSoft.
+ /// Calls unrar DLL via platform invocation services (pinvoke).
+ /// DLL is available at http://www.rarlab.com/rar/UnRARDLL.exe
+ ///
+ public class Unrar : IDisposable
+ {
+ #region Unrar DLL enumerations
+
+ ///
+ /// Mode in which archive is to be opened for processing.
+ ///
+ public enum OpenMode
+ {
+ ///
+ /// Open archive for listing contents only
+ ///
+ List=0,
+ ///
+ /// Open archive for testing or extracting contents
+ ///
+ Extract=1
+ }
+
+ private enum RarError : uint
+ {
+ EndOfArchive=10,
+ InsufficientMemory=11,
+ BadData=12,
+ BadArchive=13,
+ UnknownFormat=14,
+ OpenError=15,
+ CreateError=16,
+ CloseError=17,
+ ReadError=18,
+ WriteError=19,
+ BufferTooSmall=20,
+ UnknownError=21
+ }
+
+ private enum Operation : uint
+ {
+ Skip=0,
+ Test=1,
+ Extract=2
+ }
+
+ private enum VolumeMessage : uint
+ {
+ Ask=0,
+ Notify=1
+ }
+
+ [Flags]
+ private enum ArchiveFlags : uint
+ {
+ Volume=0x1, // Volume attribute (archive volume)
+ CommentPresent=0x2, // Archive comment present
+ Lock=0x4, // Archive lock attribute
+ SolidArchive=0x8, // Solid attribute (solid archive)
+ NewNamingScheme=0x10, // New volume naming scheme ('volname.partN.rar')
+ AuthenticityPresent=0x20, // Authenticity information present
+ RecoveryRecordPresent=0x40, // Recovery record present
+ EncryptedHeaders=0x80, // Block headers are encrypted
+ FirstVolume=0x100 // 0x0100 - First volume (set only by RAR 3.0 and later)
+ }
+
+ private enum CallbackMessages : uint
+ {
+ VolumeChange=0,
+ ProcessData=1,
+ NeedPassword=2
+ }
+
+ #endregion
+
+ #region Unrar DLL structure definitions
+
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
+ private struct RARHeaderData
+ {
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=260)]
+ public string ArcName;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=260)]
+ public string FileName;
+ public uint Flags;
+ public uint PackSize;
+ public uint UnpSize;
+ public uint HostOS;
+ public uint FileCRC;
+ public uint FileTime;
+ public uint UnpVer;
+ public uint Method;
+ public uint FileAttr;
+ [MarshalAs(UnmanagedType.LPStr)]
+ public string CmtBuf;
+ public uint CmtBufSize;
+ public uint CmtSize;
+ public uint CmtState;
+
+ public void Initialize()
+ {
+ this.CmtBuf=new string((char)0, 65536);
+ this.CmtBufSize=65536;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
+ public struct RARHeaderDataEx
+ {
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=512)]
+ public string ArcName;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=1024)]
+ public string ArcNameW;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=512)]
+ public string FileName;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=1024)]
+ public string FileNameW;
+ public uint Flags;
+ public uint PackSize;
+ public uint PackSizeHigh;
+ public uint UnpSize;
+ public uint UnpSizeHigh;
+ public uint HostOS;
+ public uint FileCRC;
+ public uint FileTime;
+ public uint UnpVer;
+ public uint Method;
+ public uint FileAttr;
+ [MarshalAs(UnmanagedType.LPStr)]
+ public string CmtBuf;
+ public uint CmtBufSize;
+ public uint CmtSize;
+ public uint CmtState;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst=1024)]
+ public uint[] Reserved;
+
+ public void Initialize()
+ {
+ this.CmtBuf=new string((char)0, 65536);
+ this.CmtBufSize=65536;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
+ public struct RAROpenArchiveData
+ {
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst=260)]
+ public string ArcName;
+ public uint OpenMode;
+ public uint OpenResult;
+ [MarshalAs(UnmanagedType.LPStr)]
+ public string CmtBuf;
+ public uint CmtBufSize;
+ public uint CmtSize;
+ public uint CmtState;
+
+ public void Initialize()
+ {
+ this.CmtBuf=new string((char)0,65536);
+ this.CmtBufSize=65536;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct RAROpenArchiveDataEx
+ {
+ [MarshalAs(UnmanagedType.LPStr)]
+ public string ArcName;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string ArcNameW;
+ public uint OpenMode;
+ public uint OpenResult;
+ [MarshalAs(UnmanagedType.LPStr)]
+ public string CmtBuf;
+ public uint CmtBufSize;
+ public uint CmtSize;
+ public uint CmtState;
+ public uint Flags;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]
+ public uint[] Reserved;
+
+ public void Initialize()
+ {
+ this.CmtBuf=new string((char)0,65536);
+ this.CmtBufSize=65536;
+ this.Reserved=new uint[32];
+ }
+ }
+
+ #endregion
+
+ #region Unrar function declarations
+
+ [DllImport("unrar.dll")]
+ private static extern IntPtr RAROpenArchive(ref RAROpenArchiveData archiveData);
+
+ [DllImport("UNRAR.DLL")]
+ private static extern IntPtr RAROpenArchiveEx(ref RAROpenArchiveDataEx archiveData);
+
+ [DllImport("unrar.dll")]
+ private static extern int RARCloseArchive(IntPtr hArcData);
+
+ [DllImport("unrar.dll")]
+ private static extern int RARReadHeader(IntPtr hArcData, ref RARHeaderData headerData);
+
+ [DllImport("unrar.dll")]
+ private static extern int RARReadHeaderEx(IntPtr hArcData, ref RARHeaderDataEx headerData);
+
+ [DllImport("unrar.dll")]
+ private static extern int RARProcessFile(IntPtr hArcData, int operation,
+ [MarshalAs(UnmanagedType.LPStr)] string destPath,
+ [MarshalAs(UnmanagedType.LPStr)] string destName );
+
+ [DllImport("unrar.dll")]
+ private static extern void RARSetCallback(IntPtr hArcData, UNRARCallback callback, int userData);
+
+ [DllImport("unrar.dll")]
+ private static extern void RARSetPassword(IntPtr hArcData,
+ [MarshalAs(UnmanagedType.LPStr)] string password);
+
+ // Unrar callback delegate signature
+ private delegate int UNRARCallback(uint msg, int UserData, IntPtr p1, int p2);
+
+ #endregion
+
+ #region Public event declarations
+
+ ///
+ /// Event that is raised when a new chunk of data has been extracted
+ ///
+ public event DataAvailableHandler DataAvailable;
+ ///
+ /// Event that is raised to indicate extraction progress
+ ///
+ public event ExtractionProgressHandler ExtractionProgress;
+ ///
+ /// Event that is raised when a required archive volume is missing
+ ///
+ public event MissingVolumeHandler MissingVolume;
+ ///
+ /// Event that is raised when a new file is encountered during processing
+ ///
+ public event NewFileHandler NewFile;
+ ///
+ /// Event that is raised when a new archive volume is opened for processing
+ ///
+ public event NewVolumeHandler NewVolume;
+ ///
+ /// Event that is raised when a password is required before continuing
+ ///
+ public event PasswordRequiredHandler PasswordRequired;
+
+ #endregion
+
+ #region Private fields
+
+ private string archivePathName=string.Empty;
+ private IntPtr archiveHandle=new IntPtr(0);
+ private bool retrieveComment=true;
+ private string password=string.Empty;
+ private string comment=string.Empty;
+ private ArchiveFlags archiveFlags=0;
+ private RARHeaderDataEx header=new RARHeaderDataEx();
+ private string destinationPath=string.Empty;
+ private RARFileInfo currentFile=null;
+ private UNRARCallback callback=null;
+
+ #endregion
+
+ #region Object lifetime procedures
+
+ public Unrar()
+ {
+ this.callback=new UNRARCallback(RARCallback);
+ }
+
+ public Unrar(string archivePathName) : this()
+ {
+ this.archivePathName=archivePathName;
+ }
+
+ ~Unrar()
+ {
+ if(this.archiveHandle!=IntPtr.Zero)
+ {
+ Unrar.RARCloseArchive(this.archiveHandle);
+ this.archiveHandle=IntPtr.Zero;
+ }
+ }
+
+ public void Dispose()
+ {
+ if(this.archiveHandle!=IntPtr.Zero)
+ {
+ Unrar.RARCloseArchive(this.archiveHandle);
+ this.archiveHandle=IntPtr.Zero;
+ }
+ }
+
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Path and name of RAR archive to open
+ ///
+ public string ArchivePathName
+ {
+ get
+ {
+ return this.archivePathName;
+ }
+ set
+ {
+ this.archivePathName=value;
+ }
+ }
+
+ ///
+ /// Archive comment
+ ///
+ public string Comment
+ {
+ get
+ {
+ return(this.comment);
+ }
+ }
+
+ ///
+ /// Current file being processed
+ ///
+ public RARFileInfo CurrentFile
+ {
+ get
+ {
+ return(this.currentFile);
+ }
+ }
+
+ ///
+ /// Default destination path for extraction
+ ///
+ public string DestinationPath
+ {
+ get
+ {
+ return this.destinationPath;
+ }
+ set
+ {
+ this.destinationPath=value;
+ }
+ }
+
+ ///
+ /// Password for opening encrypted archive
+ ///
+ public string Password
+ {
+ get
+ {
+ return(this.password);
+ }
+ set
+ {
+ this.password=value;
+ if(this.archiveHandle!=IntPtr.Zero)
+ RARSetPassword(this.archiveHandle, value);
+ }
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Close the currently open archive
+ ///
+ ///
+ public void Close()
+ {
+ // Exit without exception if no archive is open
+ if(this.archiveHandle==IntPtr.Zero)
+ return;
+
+ // Close archive
+ int result=Unrar.RARCloseArchive(this.archiveHandle);
+
+ // Check result
+ if(result!=0)
+ {
+ ProcessFileError(result);
+ }
+ else
+ {
+ this.archiveHandle=IntPtr.Zero;
+ }
+ }
+
+ ///
+ /// Opens archive specified by the ArchivePathName property for testing or extraction
+ ///
+ public void Open()
+ {
+ if(this.ArchivePathName.Length==0)
+ throw new IOException("Archive name has not been set.");
+ this.Open(this.ArchivePathName, OpenMode.Extract);
+ }
+
+ ///
+ /// Opens archive specified by the ArchivePathName property with a specified mode
+ ///
+ /// Mode in which archive should be opened
+ public void Open(OpenMode openMode)
+ {
+ if(this.ArchivePathName.Length==0)
+ throw new IOException("Archive name has not been set.");
+ this.Open(this.ArchivePathName, openMode);
+ }
+
+ ///
+ /// Opens specified archive using the specified mode.
+ ///
+ /// Path of archive to open
+ /// Mode in which to open archive
+ public void Open(string archivePathName, OpenMode openMode)
+ {
+ IntPtr handle=IntPtr.Zero;
+
+ // Close any previously open archives
+ if(this.archiveHandle!=IntPtr.Zero)
+ this.Close();
+
+ // Prepare extended open archive struct
+ this.ArchivePathName=archivePathName;
+ RAROpenArchiveDataEx openStruct=new RAROpenArchiveDataEx();
+ openStruct.Initialize();
+ openStruct.ArcName=this.archivePathName+"\0";
+ openStruct.ArcNameW=this.archivePathName+"\0";
+ openStruct.OpenMode=(uint)openMode;
+ if(this.retrieveComment)
+ {
+ openStruct.CmtBuf=new string((char)0,65536);
+ openStruct.CmtBufSize=65536;
+ }
+ else
+ {
+ openStruct.CmtBuf=null;
+ openStruct.CmtBufSize=0;
+ }
+
+ // Open archive
+ handle=Unrar.RAROpenArchiveEx(ref openStruct);
+
+ // Check for success
+ if(openStruct.OpenResult!=0)
+ {
+ switch((RarError)openStruct.OpenResult)
+ {
+ case RarError.InsufficientMemory:
+ throw new OutOfMemoryException("Insufficient memory to perform operation.");
+
+ case RarError.BadData:
+ throw new IOException("Archive header broken");
+
+ case RarError.BadArchive:
+ throw new IOException("File is not a valid archive.");
+
+ case RarError.OpenError:
+ throw new IOException("File could not be opened.");
+ }
+ }
+
+ // Save handle and flags
+ this.archiveHandle=handle;
+ this.archiveFlags=(ArchiveFlags)openStruct.Flags;
+
+ // Set callback
+ Unrar.RARSetCallback(this.archiveHandle, this.callback, this.GetHashCode());
+
+ // If comment retrieved, save it
+ if(openStruct.CmtState==1)
+ this.comment=openStruct.CmtBuf.ToString();
+
+ // If password supplied, set it
+ if(this.password.Length!=0)
+ Unrar.RARSetPassword(this.archiveHandle, this.password);
+
+ // Fire NewVolume event for first volume
+ this.OnNewVolume(this.archivePathName);
+ }
+
+ ///
+ /// Reads the next archive header and populates CurrentFile property data
+ ///
+ ///
+ public bool ReadHeader()
+ {
+ // Throw exception if archive not open
+ if(this.archiveHandle==IntPtr.Zero)
+ throw new IOException("Archive is not open.");
+
+ // Initialize header struct
+ this.header=new RARHeaderDataEx();
+ header.Initialize();
+
+ // Read next entry
+ currentFile=null;
+ int result=Unrar.RARReadHeaderEx(this.archiveHandle, ref this.header);
+
+ // Check for error or end of archive
+ if((RarError)result==RarError.EndOfArchive)
+ return false;
+ else if((RarError)result==RarError.BadData)
+ throw new IOException("Archive data is corrupt.");
+
+ // Determine if new file
+ if(((header.Flags & 0x01) != 0) && currentFile!=null)
+ currentFile.ContinuedFromPrevious=true;
+ else
+ {
+ // New file, prepare header
+ currentFile=new RARFileInfo();
+ currentFile.FileName=header.FileNameW.ToString();
+ if((header.Flags & 0x02) != 0)
+ currentFile.ContinuedOnNext=true;
+ if(header.PackSizeHigh != 0)
+ currentFile.PackedSize=(header.PackSizeHigh * 0x100000000) + header.PackSize;
+ else
+ currentFile.PackedSize=header.PackSize;
+ if(header.UnpSizeHigh != 0)
+ currentFile.UnpackedSize=(header.UnpSizeHigh * 0x100000000) + header.UnpSize;
+ else
+ currentFile.UnpackedSize=header.UnpSize;
+ currentFile.HostOS=(int)header.HostOS;
+ currentFile.FileCRC=header.FileCRC;
+ currentFile.FileTime=FromMSDOSTime(header.FileTime);
+ currentFile.VersionToUnpack=(int)header.UnpVer;
+ currentFile.Method=(int)header.Method;
+ currentFile.FileAttributes=(int)header.FileAttr;
+ currentFile.BytesExtracted=0;
+ if((header.Flags & 0xE0) == 0xE0)
+ currentFile.IsDirectory=true;
+ this.OnNewFile();
+ }
+
+ // Return success
+ return true;
+ }
+
+ ///
+ /// Returns array of file names contained in archive
+ ///
+ ///
+ public string[] ListFiles()
+ {
+ ArrayList fileNames=new ArrayList();
+ while(this.ReadHeader())
+ {
+ if(!currentFile.IsDirectory)
+ fileNames.Add(currentFile.FileName);
+ this.Skip();
+ }
+ string[] files=new string[fileNames.Count];
+ fileNames.CopyTo(files);
+ return files;
+ }
+
+ ///
+ /// Moves the current archive position to the next available header
+ ///
+ ///
+ public void Skip()
+ {
+ int result=Unrar.RARProcessFile(this.archiveHandle, (int)Operation.Skip, string.Empty, string.Empty);
+
+ // Check result
+ if(result!=0)
+ {
+ ProcessFileError(result);
+ }
+ }
+
+ ///
+ /// Tests the ability to extract the current file without saving extracted data to disk
+ ///
+ ///
+ public void Test()
+ {
+ int result=Unrar.RARProcessFile(this.archiveHandle, (int)Operation.Test, string.Empty, string.Empty);
+
+ // Check result
+ if(result!=0)
+ {
+ ProcessFileError(result);
+ }
+ }
+
+ ///
+ /// Extracts the current file to the default destination path
+ ///
+ ///
+ public void Extract()
+ {
+ this.Extract(this.destinationPath, string.Empty);
+ }
+
+ ///
+ /// Extracts the current file to a specified destination path and filename
+ ///
+ /// Path and name of extracted file
+ ///
+ public void Extract(string destinationName)
+ {
+ this.Extract(string.Empty, destinationName);
+ }
+
+ ///
+ /// Extracts the current file to a specified directory without renaming file
+ ///
+ ///
+ ///
+ public void ExtractToDirectory(string destinationPath)
+ {
+ this.Extract(destinationPath, string.Empty);
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void Extract(string destinationPath, string destinationName)
+ {
+ int result=Unrar.RARProcessFile(this.archiveHandle, (int)Operation.Extract, destinationPath, destinationName);
+
+ // Check result
+ if(result!=0)
+ {
+ ProcessFileError(result);
+ }
+ }
+
+ private DateTime FromMSDOSTime(uint dosTime)
+ {
+ int day=0;
+ int month=0;
+ int year=0;
+ int second=0;
+ int hour=0;
+ int minute=0;
+ ushort hiWord;
+ ushort loWord;
+ hiWord=(ushort)((dosTime & 0xFFFF0000) >> 16);
+ loWord=(ushort)(dosTime & 0xFFFF);
+ year=((hiWord & 0xFE00) >> 9)+1980;
+ month=(hiWord & 0x01E0) >> 5;
+ day=hiWord & 0x1F;
+ hour=(loWord & 0xF800) >> 11;
+ minute=(loWord & 0x07E0) >> 5;
+ second=(loWord & 0x1F) << 1;
+ return new DateTime(year, month, day, hour, minute, second);
+ }
+
+ private void ProcessFileError(int result)
+ {
+ switch((RarError)result)
+ {
+ case RarError.UnknownFormat:
+ throw new OutOfMemoryException("Unknown archive format.");
+
+ case RarError.BadData:
+ throw new IOException("File CRC Error");
+
+ case RarError.BadArchive:
+ throw new IOException("File is not a valid archive.");
+
+ case RarError.OpenError:
+ throw new IOException("File could not be opened.");
+
+ case RarError.CreateError:
+ throw new IOException("File could not be created.");
+
+ case RarError.CloseError:
+ throw new IOException("File close error.");
+
+ case RarError.ReadError:
+ throw new IOException("File read error.");
+
+ case RarError.WriteError:
+ throw new IOException("File write error.");
+ }
+ }
+
+ private int RARCallback(uint msg, int UserData, IntPtr p1, int p2)
+ {
+ string volume=string.Empty;
+ string newVolume=string.Empty;
+ int result=-1;
+
+ switch((CallbackMessages)msg)
+ {
+ case CallbackMessages.VolumeChange:
+ volume=Marshal.PtrToStringAnsi(p1);
+ if((VolumeMessage)p2==VolumeMessage.Notify)
+ result=OnNewVolume(volume);
+ else if((VolumeMessage)p2==VolumeMessage.Ask)
+ {
+ newVolume=OnMissingVolume(volume);
+ if(newVolume.Length==0)
+ result=-1;
+ else
+ {
+ if(newVolume!=volume)
+ {
+ for(int i=0; i0)
+ {
+ for(int i=0; (i