using System;
using System.Diagnostics;
using System.Text;
using System.Data;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Runtime.InteropServices;

namespace CHMStream
{
	/// <summary>
	/// Summary description for CHMFile.
	/// </summary>
	///
	public class CHMStream : IDisposable
	{
		public MemoryStream OpenStream(chmUnitInfo Info)
		{
			if (Info==null)
				return null;

			MemoryStream st=new MemoryStream();
			this.ExtractFile(Info,st);
			return st;
		}

		public MemoryStream OpenStream(string FileName)
		{
			chmUnitInfo info=this.GetFileInfo(FileName);
			if (info==null)
				return null;
			return OpenStream(info);
		}

		private string m_CHMFileName;
		public string CHMFileName
		{
			get
			{
				return m_CHMFileName;
			}
		}

		public CHMStream()
		{
		}

		public CHMStream(string CHMFileName)
		{
			OpenCHM(CHMFileName);
		}

		public void OpenCHM(string CHMFileName)
		{			
			m_CHMFileName=CHMFileName;
			FileInfo fi=new FileInfo(m_CHMFileName);
			Dir=fi.DirectoryName;
			m_CHMName=fi.Name;
			fi=null;			
			chm_open(m_CHMFileName);
		}

		private bool m_bCHMLoaded=false;
		public bool CHMLoaded
		{
			get
			{
				return m_bCHMLoaded;
			}
		}

		private string m_CHMName="";
		public string CHMName
		{
			get 
			{
				return m_CHMName;
			}
		}

		private string Dir="";
		private string m_FileFind="";
		private string m_FileFindLastPart="";
		private chmUnitInfo m_FileInfo=null;
		private int m_FileCount=0;
		public void FindFile(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status)
		{
			string LocalFile=Info.path;
			LocalFile=LocalFile.Replace("/",@"\");
			if (!LocalFile.StartsWith(@"\"))
				LocalFile=@"\"+LocalFile;
			LocalFile=LocalFile.ToLower();
							
			if (m_FileFind.Length<=LocalFile.Length)
			{				
				if (LocalFile.IndexOf(m_FileFind)==LocalFile.Length-m_FileFind.Length)
				{
					Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_SUCCESS;
					m_FileInfo=Info;
					return;
				}			
			}
			Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE;
		}

		public void FileCount(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status)
		{
			m_FileCount++;
			Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE;
		}

		private ArrayList m_FileList=null;
		private string m_strByExt="";
		public void FileList(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status)
		{			
			m_FileList.Add(Info.path);
			Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE;
		}

		public void FileListByExtension(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status)
		{			
			FileInfo fi=new FileInfo(Info.path);
			if (fi.Extension.ToLower()==m_strByExt.ToLower())
				m_FileList.Add(Info.path);
			Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE;
		}

		public void FindFileIndex(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status)
		{			
			if (m_FileCount==m_FileFindIndex)
			{
				m_FileInfo=Info;
				Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_SUCCESS;
			}
			else
			{
				m_FileCount++;
				Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE;
			}
		}

		public int GetFileCount()
		{
			if (!m_bCHMLoaded)
				return 0;

			m_FileCount=0;
			this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FileCount);			
			this.chm_enumerate(CHM_ENUMERATE.CHM_ENUMERATE_ALL);
			this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FileCount);
			return m_FileCount;
		}		

		public ArrayList GetFileList()
		{
			if (!m_bCHMLoaded)
				return null;

			m_FileList=null;
			m_FileList=new ArrayList(1000);
			this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FileList);			
			this.chm_enumerate(CHM_ENUMERATE.CHM_ENUMERATE_ALL);
			this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FileList);
			return m_FileList;
		}		

		public ArrayList GetFileListByExtenstion(string Ext)
		{
			if (!m_bCHMLoaded)
				return null;

			m_FileList=null;
			m_FileList=new ArrayList(1000);
			m_strByExt=Ext;
			this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FileListByExtension);			
			this.chm_enumerate(CHM_ENUMERATE.CHM_ENUMERATE_ALL);
			this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FileListByExtension);
			return m_FileList;
		}		

		public chmUnitInfo GetFileInfo(string FileName)
		{
			if (!m_bCHMLoaded)
				return null;
			
			m_FileFind=FileName.ToLower().Replace("/",@"\");	

			// Remove all leading '..\'			
			do
			{				
				if (m_FileFind.StartsWith(@"..\"))
					m_FileFind=m_FileFind.Substring(3);					
				else 
					break;
			}
			while(true);

			if (!m_FileFind.StartsWith(@"\"))
				m_FileFind=@"\"+m_FileFind;
				
			string []parts=m_FileFind.Split('\\');
			m_FileFindLastPart=@"\"+parts[parts.GetUpperBound(0)];			

			this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FindFile);
			m_FileInfo=null;
			this.chm_enumerate(CHM_ENUMERATE.CHM_ENUMERATE_ALL);
			this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FindFile);
			return m_FileInfo;
		}		
		
		private int m_FileFindIndex=0;
		public chmUnitInfo GetFileInfo(int FileIndex)
		{
			if (!m_bCHMLoaded)
				return null;

			m_FileFindIndex=FileIndex;

			this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FindFileIndex);
			m_FileCount=0;
			m_FileInfo=null;
			this.chm_enumerate(CHM_ENUMERATE.CHM_ENUMERATE_ALL);
			this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FindFileIndex);
			return m_FileInfo;
		}

		public chmUnitInfo GetFileInfoByExtension(string Ext)
		{
			this.CHMFileFoundEvent+=new CHMStream.CHMFileFound(FindFileByExtension);
			m_FileInfo=null;
			m_FileFind=Ext.ToLower();
			this.chm_enumerate(CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_ALL);
			this.CHMFileFoundEvent-=new CHMStream.CHMFileFound(FindFileByExtension);
			return m_FileInfo;
		}

		public bool ExtractFile(string FileName, System.IO.Stream st)
		{
			if (!m_bCHMLoaded)
				return false;

			chmUnitInfo Info=GetFileInfo(FileName);
			return ExtractFile(Info,st);
		}

		public bool ExtractFile(chmUnitInfo Info, System.IO.Stream st)
		{		
			if (!m_bCHMLoaded)
				return false;

			if (Info==null)
				return false;
			else
			{
				chm_retrieve_object(Info,st,0,Info.length);				
			}
			return true;
		}

		public string ExtractTextFile(string FileName)
		{	
			if (!m_bCHMLoaded)
				return "CHM File not loaded";

			chmUnitInfo Info=GetFileInfo(FileName);
			return ExtractTextFile(Info);
		}

		public string ExtractTextFile(chmUnitInfo Info)
		{				
			if (!m_bCHMLoaded)
				return "CHM File not loaded";

			if (Info==null)
				return "";
			
			if (Info.path.Length>=2)
			{
				if (Info.path.Substring(0,2).CompareTo("/#")==0)
					return "";
				if (Info.path.Substring(0,2).CompareTo("/$")==0)
					return "";
			}

			MemoryStream st=new MemoryStream((int)Info.length);
			this.chm_retrieve_object(Info,st,0,Info.length);			

			if (st.Length==0)
				return "";

			string Text="";			
			
			ASCIIEncoding ascii=new ASCIIEncoding();			
			Text=ascii.GetString(st.ToArray(),0,50);

			// UTF Decoding
			if (Text.IndexOf("UTF-8")!=-1)
			{
				UTF8Encoding utf8 = new UTF8Encoding();
				Text=utf8.GetString(st.ToArray(),0,(int)st.Length);
			}
			else
				Text=ascii.GetString(st.ToArray(),0,(int)st.Length);
	
			return Text;
		}	
		
		public void FindFileByExtension(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status)
		{			
			if ((Info.path.StartsWith("::")) || (Info.path.StartsWith("#")) ||(Info.path.StartsWith("$")))
			{
				Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE;
				return;
			}			

			FileInfo Fi=new FileInfo(Info.path);
			if (Fi.Extension.ToLower()==m_FileFind)
			{
				Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_SUCCESS;
				m_FileInfo=Info;
			}
			else
				Status=CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE;
		}

		public bool GetCHMParts(string Url, ref string CHMFileName, ref string FileName, ref string Anchor)
		{
			Regex ParseURLRegEx= new Regex( @"ms-its:(?'CHMFile'.*)::(?'Topic'.*)", RegexOptions.IgnoreCase| RegexOptions.Singleline | RegexOptions.ExplicitCapture| RegexOptions.IgnorePatternWhitespace| RegexOptions.Compiled);

			// Parse URL - Get CHM Filename & Page Name
			//	Format 'ms-its:file name.chm::/topic.htm'
			if (ParseURLRegEx.IsMatch(Url))
			{
				Match m=ParseURLRegEx.Match(Url);
				CHMFileName=m.Groups["CHMFile"].Value;
				string Topic=m.Groups["Topic"].Value;
				int idx=Topic.IndexOf("#");
				if (idx>-1)
				{
					FileName=Topic.Substring(0,idx);
					Anchor=Topic.Substring(idx+1);
				}
				else
					FileName=Topic;
				return true;
			}
			return false;
		}

		private string m_TempDir="";		
		string ReplaceFileName(Match m)
		{
			string strReplace = m.ToString();

			// Process string.
			if (m.Groups["FileName"]==null)
				return strReplace;

			string FileName=m.Groups["FileName"].Value;
			string FileName2=FileName.Replace("/",@"\");
			int idx=FileName2.IndexOf("::");
			if (idx!=-1)
				FileName2=FileName2.Substring(idx+2);
			string []parts=FileName2.Split('\\');			
			string NewName=@"file://"+m_TempDir+parts[parts.GetUpperBound(0)];

			strReplace=strReplace.Replace(FileName,NewName);	
			return strReplace;
		}

		public ArrayList GetFileList(ref string Text, string TempDir)
		{			
			if (!m_bCHMLoaded)
				return null;

			m_TempDir=TempDir;

			ArrayList FilesList=new ArrayList();

			// Parse HTML for CCS, ima, etc			
			string regexContent=@"[\x2f a-zA-Z0-9\x5C\x2E\x28\x29\x23\x24\x25\x26\x27\x22\x21\x3F\x3E\x3D\x3C\x3B\x3A\x5B\x5D\x5E\x5F\x7D\x7C\x7B\x7E\x40\x2D\x2C\x2B\x2A]*\s*";
			string regexFileName=@"\s*=\s*[""|'](?'FileName'[^""^']*)[""|']\s*";

			Regex ScriptRegex = new Regex(@"<script[^>]*>.*</script>",
				RegexOptions.IgnoreCase
				| RegexOptions.Multiline
				| RegexOptions.Singleline
				| RegexOptions.IgnorePatternWhitespace
				| RegexOptions.Compiled);

			Regex XMLRegex = new Regex(@"<\?xml.*\?>",
				RegexOptions.IgnoreCase
				| RegexOptions.Multiline
				| RegexOptions.Singleline
				| RegexOptions.IgnorePatternWhitespace
				| RegexOptions.Compiled);

			Regex XMLRegex2 = new Regex(@"<xml[^>]*>.*</xml>",
				RegexOptions.IgnoreCase
				| RegexOptions.Multiline
				| RegexOptions.Singleline
				| RegexOptions.IgnorePatternWhitespace
				| RegexOptions.Compiled);

			Regex SRCRegex = new Regex( 
				@"src"+regexFileName,
				RegexOptions.IgnoreCase
				| RegexOptions.Multiline
				| RegexOptions.Singleline
				| RegexOptions.IgnorePatternWhitespace
				| RegexOptions.Compiled);

			Regex StyleSheetRegex = new Regex(				
				@"<link\s*"+regexContent+@"rel\s*=\s*[""|']stylesheet[""|']\s*"+regexContent + "href"+regexFileName,
				RegexOptions.IgnoreCase
				| RegexOptions.Multiline
				| RegexOptions.Singleline
				| RegexOptions.IgnorePatternWhitespace
				| RegexOptions.Compiled);
			
			// Remove Script Tags
			Text=ScriptRegex.Replace(Text,"");

			// Remove XML Tags
			Text=XMLRegex.Replace(Text,"");
			Text=XMLRegex2.Replace(Text,"");			


			StringBuilder s=new StringBuilder(Text);
			
			if (StyleSheetRegex.IsMatch(Text))
			{				
				Match m = StyleSheetRegex.Match(Text);
				while (m.Success)
				{										
					string FileName=m.Groups["FileName"].ToString();
					FilesList.Add(FileName);					
					m=m.NextMatch();
				}
				Text=StyleSheetRegex.Replace(Text,new MatchEvaluator(ReplaceFileName));
			}

			if (SRCRegex.IsMatch(Text))
			{								
				Match m = SRCRegex.Match(Text);
				while (m.Success)
				{					
					string FileName=m.Groups["FileName"].ToString();
					FilesList.Add(FileName);
					m=m.NextMatch();
				}
				Text=SRCRegex.Replace(Text,new MatchEvaluator(ReplaceFileName));
			}
			
			return FilesList;
		}

		public string GetHTMLAndFiles(string TempDir, string Url)
		{
			string HTMLText="";
			if (TempDir.EndsWith(@"\")) TempDir=TempDir.Substring(TempDir.Length-1);			
	
			// Delete Temp Directory
			if (Directory.Exists(TempDir))
				Directory.Delete(TempDir,true);

			// Create Temp Directory
			if (!Directory.Exists(TempDir))
				Directory.CreateDirectory(TempDir);

			if (!TempDir.EndsWith(@"\")) TempDir+=@"\";		

			string m_TopicName="";

			string m_CHMFile=CHMFileName;
			string Anchor="";
			if (!GetCHMParts(Url,ref m_CHMFile, ref m_TopicName, ref Anchor))
			{
				m_CHMFile=this.CHMFileName;
				m_TopicName=Url;
			}
				
			if (m_TopicName=="")
				return "#No TopicName defined in Url : "+ Url;		

			m_TopicName=m_TopicName.Replace("/",@"\");			
			if (!m_CHMFile.StartsWith(@"\"))
				m_CHMFile=this.Dir+@"\"+m_CHMFile;

			// Open CHM			
			CHMStream LocalCHM=this;
			
			if (this.CHMFileName.ToLower().CompareTo(m_CHMFile.ToLower())!=0)
				LocalCHM=new CHMStream(m_CHMFile);

			// Get HTML
			HTMLText=LocalCHM.ExtractTextFile(m_TopicName);
			if (HTMLText=="")
				return "#Failed to find Topic in CHM File : "+Url;

			HTMLText=GetFiles(TempDir, HTMLText, LocalCHM);

			return HTMLText;
		}

		public string GetFiles(string TempDir, string HTMLText, CHMStream chm)
		{
			return GetFiles(TempDir, HTMLText, chm,0);
		}

		public string GetFiles(string TempDir, string HTMLText, CHMStream  chm, int Level)
		{
			// Get FilesList & Extract Files to Temp Dir
			ArrayList FileList=chm.GetFileList(ref HTMLText, TempDir);
			if (FileList!=null)
			{
				foreach(object obj in FileList)
				{
					string FileName=(string)obj;

					string CHMFileName="";
					string TopicName="";					
					string Anchor="";
					CHMStream NewCHM=chm;					
					if (GetCHMParts(FileName,ref CHMFileName, ref TopicName, ref Anchor))
					{	
						NewCHM=new CHMStream(chm.Dir+@"\"+CHMFileName);
						if (!NewCHM.CHMLoaded)
							NewCHM=null;
						FileName=TopicName;
					}
					else
					{
						CHMFileName=chm.CHMFileName;
						NewCHM=chm;
						TopicName=FileName;
					}
					if (NewCHM==null)
						continue;

					if (((FileName.ToLower().EndsWith(".htm")) || (FileName.ToLower().EndsWith(".html"))) && (Level<2))
					{
						string HTMLText2=NewCHM.ExtractTextFile(FileName);
						FileInfo Fi=new FileInfo(FileName);
						string path=TempDir+Fi.Name;
						HTMLText2=GetFiles(TempDir,HTMLText2, chm, Level+1);

						if (File.Exists(path))
							File.Delete(path);

						StreamWriter st=new StreamWriter(path);
						st.WriteLine(HTMLText2);
						st.Close();
					}
					else
					{
						// Extract all other files as is						
						string FileName2=FileName.Replace("/",@"\");
						if (FileName2.Substring(0,1)==@"\")
							FileName2=FileName2.Substring(1);

						string []parts=FileName2.Split('\\');
						string path=TempDir+parts[parts.GetUpperBound(0)];
						if (File.Exists(path))
							File.Delete(path);
						System.IO.FileStream st=new FileStream(path,FileMode.CreateNew);
						NewCHM.ExtractFile(FileName2,st);
						st.Close();
					}				
				}
			}

			// return HTML string of main page
			return HTMLText;
		}
		
		#region CHMStream Enums
		// the two available spaces in a CHM file                      
		// N.B.: The format supports arbitrarily many spaces, but only 
		//       two appear to be used at present.                     
		public enum CHM_COMPRESSION { CHM_UNCOMPRESSED=0, CHM_COMPRESSED=1};

		// resolve a particular object from the archive 
		public enum CHM_RESOLVE { CHM_RESOLVE_SUCCESS=0, CHM_RESOLVE_FAILURE=1};
														  
		// retrieve part of an object from the archive 
		public enum CHM_ENUMERATE
		{
			None=0,
			CHM_ENUMERATE_NORMAL	=1,
			CHM_ENUMERATE_META		=2,
			CHM_ENUMERATE_SPECIAL	=4,
			CHM_ENUMERATE_FILES		=8,
			CHM_ENUMERATE_DIRS		=16,
			CHM_ENUMERATE_ALL		=31};

		public enum CHM_ENUMERATOR
		{ 
			CHM_ENUMERATOR_FAILURE	=0, 
			CHM_ENUMERATOR_SUCCESS	=2, 
			CHM_ENUMERATOR_CONTINUE =1
		};
		#endregion

		#region Internal Parameters
		private int ffs(int val)
		{
			int bit=1;
			int idx=1;
			while (bit != 0  &&  (val & bit) == 0)
			{
				bit <<= 1;
				++idx;
			}
			if (bit == 0)
				return 0;
			else
				return idx;
		}

		// names of sections essential to decompression 
		private const string _CHMU_RESET_TABLE = @"::DataSpace/Storage/MSCompressed/Transform/{7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable";
		private const string _CHMU_LZXC_CONTROLDATA = @"::DataSpace/Storage/MSCompressed/ControlData";
		private const string _CHMU_CONTENT = @"::DataSpace/Storage/MSCompressed/Content";
		private const string _CHMU_SPANINFO =	@"::DataSpace/Storage/MSCompressed/SpanInfo";

	    private UInt64              dir_offset=0;
		private UInt64              dir_len=0;    
		private UInt64              data_offset=0;
		private Int32               index_root=0;
		private Int32               index_head=0;
		private UInt32              block_len=0;     		
		
		private chmUnitInfo			rt_unit;
		private chmUnitInfo			cn_unit;
		private chmLzxcResetTable	reset_table;
		private bool				compression_enabled=false;

		// LZX control data 
		private int					window_size=0;
		private UInt32              reset_interval=0;
		private UInt32              reset_blkcount=0;
		private BinaryReader		st=null;

		// decompressor state 
		private lzw		lzx_state;		
		private int                 lzx_last_block=0;
		#endregion

		#region Open CHM Stream
		private bool CheckSig(string Sig1, char[] Sig2)
		{
			int  i=0;
			foreach(char ch in Sig1.ToCharArray())
			{
				if(ch!=Sig2[i])
					return false;
				i++;
			}
			return true;	
		}

		// open an ITS archive 
		private bool chm_open(string filename)
		{
			chmItsfHeader        itsfHeader=new chmItsfHeader();
			chmItspHeader        itspHeader=new chmItspHeader();
			chmUnitInfo          uiSpan=new chmUnitInfo();
			chmUnitInfo          uiLzxc=new chmUnitInfo();
			chmLzxcControlData   ctlData=new chmLzxcControlData();
			
			m_bCHMLoaded=false;
			if (!File.Exists(filename))
				return false;

			st=new BinaryReader(File.OpenRead(filename));
			if (st==null)
				return false;

			// read and verify header 			
			if (itsfHeader.Read_itsf_header(st)==0)
			{
				st.Close();
				return false;
			}
			st.BaseStream.Seek((long)itsfHeader.dir_offset,SeekOrigin.Begin);			

			// stash important values from header 
			dir_offset  = itsfHeader.dir_offset;
			dir_len     = itsfHeader.dir_len;
			data_offset = itsfHeader.data_offset;

			// now, read and verify the directory header chunk 
			if (itspHeader.Read_itsp_header(st)==0)
			{
				st.Close();
				return false;
			}

			// grab essential information from ITSP header 
			dir_offset += (UInt64)itspHeader.header_len;
			dir_len    -= (UInt64)itspHeader.header_len;
			index_root  = itspHeader.index_root;
			index_head  = itspHeader.index_head;
			block_len   = itspHeader.block_len;

			// if the index root is -1, this means we don't have any PMGI blocks.
			// as a result, we must use the sole PMGL block as the index root
	     
			if (index_root == -1)
				index_root = index_head;

			compression_enabled=true;

			// prefetch most commonly needed unit infos 
			// if (CHM_RESOLVE.CHM_RESOLVE_SUCCESS != chm_resolve_object(_CHMU_SPANINFO, ref uiSpan) 
			//  || uiSpan.space == CHM_COMPRESSION.CHM_COMPRESSED || 
			if (CHM_RESOLVE.CHM_RESOLVE_SUCCESS != chm_resolve_object(_CHMU_RESET_TABLE, ref rt_unit)    
				|| rt_unit.space == CHM_COMPRESSION.CHM_COMPRESSED                        
				|| CHM_RESOLVE.CHM_RESOLVE_SUCCESS != chm_resolve_object(_CHMU_CONTENT,ref cn_unit)    
				|| cn_unit.space == CHM_COMPRESSION.CHM_COMPRESSED                        
				|| CHM_RESOLVE.CHM_RESOLVE_SUCCESS != chm_resolve_object(_CHMU_LZXC_CONTROLDATA, ref uiLzxc)                
				|| uiLzxc.space == CHM_COMPRESSION.CHM_COMPRESSED)
			{
				compression_enabled=false;
				// st.Close();				
				// return false;
			}

			// try to read span 
			// N.B.: we've already checked that uiSpan is in the uncompressed section,
			//        so this should not require attempting to decompress, which may
			//        rely on having a valid "span"
		     
			if (compression_enabled)
			{		
				reset_table=new chmLzxcResetTable();
				st.BaseStream.Seek((long)((long)data_offset + (long)rt_unit.start),SeekOrigin.Begin);
				if (reset_table.Read_lzxc_reset_table(st)!=1)
				{
					compression_enabled=false;
				}
			}

			if (compression_enabled)
			{			
				// read control data 
				ctlData=new chmLzxcControlData();
				st.BaseStream.Seek((long)((long)data_offset + (long)uiLzxc.start),SeekOrigin.Begin);
				if (ctlData.Read_lzxc_control_data(st)!=1)
				{
					compression_enabled=false;
				}			

				window_size = (int)ctlData.windowSize;
				reset_interval = ctlData.resetInterval;
				try
				{
					reset_blkcount = (uint)(reset_interval    /
						(window_size / 2) *
						ctlData.windowsPerReset);
				}
				catch(Exception)
				{
					reset_blkcount=0;
				}
			}

			m_bCHMLoaded=true;

			return true;
		}
		#endregion

		#region Close CHM Stream
		// close an ITS archive 
		private  void chm_close()
		{
			if (!m_bCHMLoaded)
				return;

			st.Close();
			lzx_state.LZXteardown();
			lzx_state=null;
		}
		#endregion
			
		#region Find File in CHM Stream
		// resolve a particular object from the archive 
		private CHMStream.CHM_RESOLVE chm_resolve_object(string objPath, ref chmUnitInfo ui)
		{     			
			Int32 curPage;
	
			// starting page 
			curPage = index_root;

			// until we have either returned or given up 
			while (curPage != -1)
			{
				st.BaseStream.Seek((long)((long)dir_offset + (long)(curPage*block_len)),SeekOrigin.Begin);
				
				char[] sig=st.ReadChars(4);
				st.BaseStream.Seek(-4,SeekOrigin.Current);
				if (CheckSig("PMGL",sig))
				{
					chmPmglHeader PmglHeader=new chmPmglHeader();
					if (PmglHeader.Read_pmgl_header(st)==1)
					{
						// scan block 
						ui=PmglHeader.FindObject(st,block_len,objPath);
						if (ui== null) 
							return CHMStream.CHM_RESOLVE.CHM_RESOLVE_FAILURE;

						// parse entry and return 
						return CHMStream.CHM_RESOLVE.CHM_RESOLVE_SUCCESS;
					}
				}	
				else if (CheckSig("PMGI",sig))
				{			
					chmPmgiHeader pmgiHeader=new chmPmgiHeader();
					pmgiHeader.Read_pmgi_header(st);
					curPage = pmgiHeader._chm_find_in_PMGI(st, block_len, objPath);
				}				
				else
					// else, we are confused.  give up. 
					return CHMStream.CHM_RESOLVE.CHM_RESOLVE_FAILURE;
			}

			// didn't find anything.  fail. 
			return CHMStream.CHM_RESOLVE.CHM_RESOLVE_FAILURE;
		}
		#endregion

		#region Extract File from CHM Stream

		//	* utility methods for dealing with compressed data		 
		// get the bounds of a compressed block.  return 0 on failure 			
		private int _chm_get_cmpblock_bounds(System.IO.BinaryReader st, UInt64 block, ref UInt64 start, ref UInt64 len)
		{
			// for all but the last block, use the reset table 
			if (block < reset_table.block_count-1)
			{
				// unpack the start address 				
				st.BaseStream.Seek((long)data_offset + (long)rt_unit.start + (long)reset_table.table_offset + (long)(block*8),SeekOrigin.Begin);
				start=st.ReadUInt64();
				len=st.ReadUInt64();
			}

			// for the last block, use the span in addition to the reset table 
			else
			{				
				// unpack the start address 
				st.BaseStream.Seek((long)data_offset + (long)rt_unit.start + (long)reset_table.table_offset + (long)(block*8),SeekOrigin.Begin);
				start=st.ReadUInt64();
				len = reset_table.compressed_len;
			}

			// compute the length and absolute start address 
			len -= start;
			start += data_offset + cn_unit.start;

			return 1;
		}

		// decompress the block.  must have lzx_mutex. 
		private ulong _chm_decompress_block(UInt64 block, System.IO.Stream OutBuffer)
		{
			// byte []cbuffer = new byte(reset_table.block_len + 6144);
			ulong cmpStart=0;                                    // compressed start  
			ulong cmpLen=0;                                       // compressed len    
			UInt32 blockAlign = (UInt32)(block % reset_blkcount); // reset intvl. aln. 			
			
			// check if we need previous blocks 
			if (blockAlign != 0)			
			{
				/* fetch all required previous blocks since last reset */
				for (UInt32 i = blockAlign; i > 0; i--)
				{
					UInt32 curBlockIdx = (UInt32)(block-i);
					
					/* check if we most recently decompressed the previous block */
					if ((ulong)lzx_last_block != curBlockIdx)
					{
						if ((curBlockIdx % reset_blkcount)==0)
						{
							lzx_state.LZXreset();
						}
						
						_chm_get_cmpblock_bounds(st,curBlockIdx, ref cmpStart, ref cmpLen);
						st.BaseStream.Seek((long)cmpStart,SeekOrigin.Begin);						
						if (lzx_state.LZXdecompress(st,OutBuffer, ref cmpLen, ref reset_table.block_len) != lzw.DECR_OK)
							return (Int64)0;
					}
					lzx_last_block = (int)(curBlockIdx);
				}
			}
			else
			{
				if ((block % reset_blkcount)==0)
				{
					lzx_state.LZXreset();
				}		
			}

			// decompress the block we actually want 
			if (_chm_get_cmpblock_bounds(st, block, ref cmpStart, ref cmpLen)==0)
				return 0;

			st.BaseStream.Seek((long)cmpStart,SeekOrigin.Begin);
							
			if (lzx_state.LZXdecompress(st, OutBuffer, ref cmpLen,ref reset_table.block_len) != lzw.DECR_OK)
				return (Int64)0;

			lzx_last_block = (int)block;

			// XXX: modify LZX routines to return the length of the data they
			// * decompressed and return that instead, for an extra sanity check.
		     return reset_table.block_len;
		}

		// grab a region from a compressed block 
		private ulong _chm_decompress_region(Stream buf, ulong  start, ulong len)
		{
			ulong nBlock, nOffset;
			ulong nLen;
			ulong gotLen;
			// byte [] ubuffer=null;

			if (len <= 0)
				return (Int64)0;

			// figure out what we need to read 
			nBlock = start / reset_table.block_len;
			nOffset = start % reset_table.block_len;
			nLen = len;
			if (nLen > (reset_table.block_len - nOffset))
				nLen = reset_table.block_len - nOffset;

			// data request not satisfied, so... start up the decompressor machine 			
			if (lzx_state==null)
			{
				int window_size = ffs(this.window_size) - 1;
				lzx_last_block = -1;

				lzx_state=new lzw();
				lzx_state.LZXinit(window_size);
			}

			// decompress some data 
			MemoryStream ms=new MemoryStream((int)reset_table.block_len+6144);			
			gotLen = _chm_decompress_block(nBlock, ms);
			if (gotLen < nLen)
				nLen = gotLen;	
			
			// memcpy(buf, ubuffer+nOffset, (unsigned int)nLen);
			ms.Position=(long)nOffset;
			for(ulong i=0;i<nLen;i++)
				buf.WriteByte((byte)ms.ReadByte());
			buf.Flush();
			return nLen;
		}

		// retrieve (part of) an object 
		private ulong chm_retrieve_object(chmUnitInfo ui, Stream buf, ulong addr, ulong len)
		{
			// starting address must be in correct range 
			if (addr < 0  ||  addr >= (ulong)ui.length)
				return (Int64)0;

			// clip length 
			if (addr + (ulong)len > (ulong)ui.length)
				len = (ulong)ui.length - (ulong)addr;

			// if the file is uncompressed, it's simple 
			if (ui.space == CHMStream.CHM_COMPRESSION.CHM_UNCOMPRESSED)
			{
				// read data 
				long FilePos=st.BaseStream.Position;				
				st.BaseStream.Seek((long)((long)data_offset + (long)ui.start + (long)addr),SeekOrigin.Begin);
				// byte [] buffer=st.ReadBytes((int)len);
				buf.Write(st.ReadBytes((int)len),0,(int) len);
				st.BaseStream.Seek(FilePos,SeekOrigin.Begin);
				return (ulong)len;
			}

			// else if the file is compressed, it's a little trickier 
			else // ui->space == CHM_COMPRESSED 
			{			
				if (lzx_state!=null)
				{
					lzx_state.LZXteardown();
					lzx_state=null;
				}
				ulong swath=0, total=0;
				do 
				{
					if (!compression_enabled)
						return total;

					// swill another mouthful 
					swath = _chm_decompress_region(buf, ui.start + addr, len);

					// if we didn't get any... 
					if (swath == 0)
					{
						Trace.Assert((total!=ui.length),"De-compress failed","Length Required = "+ui.length.ToString()+" Length returned = "+total.ToString());
						return total;
					}

					// update stats 
					total += swath;
					len -= swath;
					addr += swath;
				} while (len != 0);		
				lzx_state=null;

				Trace.Assert((len!=ui.length),"De-compress failed","Length Required = "+ui.length.ToString()+" Length returned = "+len.ToString());
				return len;
			}
		}
		#endregion
	
		#region Enumerate functions				
		// Enumerate the objects in the .chm archive 
		// Use delegate to handle callback

		public delegate void CHMFileFound(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status);
		public event CHMFileFound CHMFileFoundEvent;

		public void OnFileFound(chmUnitInfo Info, ref CHMStream.CHM_ENUMERATOR Status)
		{
			if (CHMFileFoundEvent!=null)
				CHMFileFoundEvent(Info,ref Status);
		}
		private int chm_enumerate(CHM_ENUMERATE what)
		{
			Int32 curPage;

			// buffer to hold whatever page we're looking at 			
			chmPmglHeader header;
			uint end=0;
			uint cur=0;			

			// the current ui 
			chmUnitInfo ui= new chmUnitInfo();
			CHMStream.CHM_ENUMERATE flag=CHMStream.CHM_ENUMERATE.None;

			// starting page 
			curPage = index_head;

			// until we have either returned or given up 
			while (curPage != -1)
			{
				st.BaseStream.Seek((long)((long)dir_offset + (long)(curPage*block_len)),SeekOrigin.Begin);

				// figure out start and end for this page 
				cur = (uint)st.BaseStream.Position;
				
				header=new chmPmglHeader();
				if (header.Read_pmgl_header(st)==0)				
					return 0;

				end = (uint)(st.BaseStream.Position + block_len - (header.free_space)- chmPmglHeader._CHM_PMGL_LEN);

				// loop over this page 
				while (st.BaseStream.Position < end)
				{
					if (header._chm_parse_PMGL_entry(st,ref ui)==0)
						return 0;

					// check for DIRS 
					if (ui.length == 0  &&  ((what & CHM_ENUMERATE.CHM_ENUMERATE_DIRS)==0))
						continue;

					// check for FILES 
					if (ui.length != 0  &&  ((what & CHM_ENUMERATE.CHM_ENUMERATE_FILES)==0))
						continue;

					// check for NORMAL vs. META 
					if (ui.path[0] == '/')
					{
						// check for NORMAL vs. SPECIAL 
						if (ui.path.Length>2)
						{
							if (ui.path[1] == '#'  ||  ui.path[1] == '$')
								flag = CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_SPECIAL;
							else
								flag = CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_NORMAL;
						}					
						else
							flag = CHMStream.CHM_ENUMERATE.CHM_ENUMERATE_META;
						if ((what & flag)==0)
							continue;
					}

					// call the enumerator 
					{
						CHMStream.CHM_ENUMERATOR status = CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE;
						OnFileFound(ui,ref status);
													
						switch (status)
						{
							case CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_FAILURE:  
								return 0;

							case CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_CONTINUE: 
								break;

							case CHMStream.CHM_ENUMERATOR.CHM_ENUMERATOR_SUCCESS:  
								return 1;

							default:
								break;
						}
					}
				}

				// advance to next page 
				curPage = header.block_next;
			}

			return 1;
		}
		#endregion

		#region IDisposable Members

		private bool disposed=false;
		public void Dispose()
		{
			Dispose(true);
			// This object will be cleaned up by the Dispose method.
			// Therefore, you should call GC.SupressFinalize to
			// take this object off the finalization queue 
			// and prevent finalization code for this object
			// from executing a second time.
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Dispose(bool disposing) executes in two distinct scenarios. 
		/// If disposing equals true, the method has been called directly 
		/// or indirectly by a user's code. Managed and unmanaged resources 
		/// can be disposed. 
		/// If disposing equals false, the method has been called by the 
		/// runtime from inside the finalizer and you should not reference  
		/// other objects. Only unmanaged resources can be disposed.
		/// </summary>
		/// <param name="disposing">disposing flag</param>
		private void Dispose(bool disposing)
		{
			// Check to see if Dispose has already been called.
			if(!this.disposed)
			{
				// If disposing equals true, dispose all managed 
				// and unmanaged resources.
				if(disposing)
				{
					// Dispose managed resources.					
				}
			}
			disposed = true;         
		}

		#endregion
	}

	#region Structures used by CHM Storage
	public class BaseStructure
	{
		public bool CheckSig(string Sig1, char[] Sig2)
		{
			int i=0;
			foreach(char ch in Sig1.ToCharArray())
			{
				if (ch!=Sig2[i])
					return false;
				i++;
			}
			return true;
		}

		// skip a compressed dword 
		public void skip_cword(BinaryReader st)
		{		
			byte b=0;
			while ((b=st.ReadByte())>= 0x80);			
		}

		// skip the data from a PMGL entry 
		public void _chm_skip_PMGL_entry_data(BinaryReader st)
		{
			skip_cword(st);
			skip_cword(st);
			skip_cword(st);
		}

		// parse a compressed dword 
		public UInt64 _chm_parse_cword(BinaryReader st)
		{
			UInt64 accum = 0;
			byte temp=0;			
			while ((temp=st.ReadByte()) >= 0x80)
			{
				accum <<= 7;
				accum += (ulong)(temp & 0x7f);
			}

			return (accum << 7) + temp;
		}

		// parse a utf-8 string into an ASCII char buffer 
		public int _chm_parse_UTF8(BinaryReader st, UInt64 count, ref string path)
		{
			UTF8Encoding utf8=new UTF8Encoding();
			path=utf8.GetString(st.ReadBytes((int)count),0,(int)count);							
			return 1;
		}
	}

	public class chmUnitInfo
	{
		public UInt64		start=0;
		public UInt64		length=0;
		public CHMStream.CHM_COMPRESSION space=CHMStream.CHM_COMPRESSION.CHM_UNCOMPRESSED;
		public string		path="";
	}

	// structure of ITSF headers 						
	public class chmItsfHeader : BaseStructure
	{
		public const int _CHM_ITSF_V2_LEN=0x58;
		public const int _CHM_ITSF_V3_LEN=0x60;

		public char[]	signature=null;				//  0 (ITSF) 
		public Int32	version=0;					//  4 
		public Int32	header_len=0;				//  8 
		public Int32	unknown_000c=0;				//  c 
		public UInt32	last_modified=0;			// 10 
		public UInt32	lang_id=0;					// 14 			
		public Guid		dir_uuid;					// 18 
		public Guid		stream_uuid;				// 28 
		public UInt64	unknown_offset=0;			// 38 
		public UInt64	unknown_len=0;				// 40 
		public UInt64	dir_offset=0;				// 48 
		public UInt64	dir_len=0;					// 50 
		public UInt64	data_offset=0;				// 58 (Not present before V3) 

		public int Read_itsf_header(BinaryReader st)
		{							
			signature=st.ReadChars(4);
			if (CheckSig("ITSF",signature)==false)
				return 0;
			
			version=st.ReadInt32();
			header_len=st.ReadInt32();
			unknown_000c=st.ReadInt32();
			last_modified=st.ReadUInt32();
			lang_id=st.ReadUInt32();
			dir_uuid=new Guid(st.ReadBytes(16));
			stream_uuid=new Guid(st.ReadBytes(16));			
			unknown_offset=st.ReadUInt64();
			unknown_len=st.ReadUInt64();
			dir_offset=st.ReadUInt64();
			dir_len=st.ReadUInt64();

			if (version==2)
			{
				if (header_len != chmItsfHeader._CHM_ITSF_V2_LEN)
					return 0;
			}
			else if (version==3)
			{
				if (header_len != chmItsfHeader._CHM_ITSF_V3_LEN)
					return 0;
			}
			else return 0;

			if (version==3)
				data_offset=st.ReadUInt64();
			else
				data_offset = dir_offset + dir_len;

			return 1;
		}
	}

	// structure of ITSP headers 
	public class chmItspHeader : BaseStructure
	{
		const int CHM_ITSP_V1_LEN=0x54;
	
		public char[]	signature=null;		//  0 (ITSP) 
		public Int32    version=0;         
		public Int32    header_len=0;      
		public Int32    unknown_000c=0;    
		public UInt32   block_len=0;       
		public Int32    blockidx_intvl=0;  
		public Int32    index_depth=0;     
		public Int32    index_root=0;      		
		public Int32    index_head=0;			
		public Int32    unknown_0024=0;    
		public Int32    num_blocks=0;    
		public Int32    unknown_002c=0;    
		public UInt32	lang_id=0;         
		public Guid		system_uuid;
		public Guid		unknown_0044;

		public int Read_itsp_header(BinaryReader st)
		{							
			signature=st.ReadChars(4);		//  0 (ITSP) 			
			if (CheckSig("ITSP",signature)==false)
				return 0;

			version=st.ReadInt32();
			header_len=st.ReadInt32();
		
			if (header_len!=CHM_ITSP_V1_LEN)
				return 0;

			unknown_000c=st.ReadInt32();
			block_len=st.ReadUInt32();
			blockidx_intvl=st.ReadInt32();
			index_depth=st.ReadInt32();
			index_root=st.ReadInt32();			
			index_head=st.ReadInt32();				
			unknown_0024=st.ReadInt32();
			num_blocks=st.ReadInt32();
			unknown_002c=st.ReadInt32();
			lang_id=st.ReadUInt32();
			system_uuid=new Guid(st.ReadBytes(16));
			unknown_0044=new Guid(st.ReadBytes(16));

			return 1;
		}
	}

	public class chmPmglHeader : BaseStructure
	{
		public const int _CHM_PMGL_LEN=0x14;
		public char[]	signature=null;           //  0 (PMGL) 
		public UInt32	free_space=0;             //  4 
		public UInt32	unknown_0008=0;           //  8 
		public Int32	block_prev=0;             //  c 
		public Int32	block_next=0;             // 10 

		public int Read_pmgl_header(BinaryReader st)
		{
			signature=st.ReadChars(4);
			if (CheckSig("PMGL",signature)==false)
				return 0;

			free_space=st.ReadUInt32();
			unknown_0008=st.ReadUInt32();
			block_prev=st.ReadInt32();
			block_next=st.ReadInt32();
			return 1;
		}

		// parse a PMGL entry into a chmUnitInfo struct; return 1 on success. 
		public int _chm_parse_PMGL_entry(BinaryReader st, ref chmUnitInfo ui)
		{
			UInt64 strLen;

			// parse str len 
			strLen = _chm_parse_cword(st);			

			// parse path 			
			if (_chm_parse_UTF8(st, strLen, ref ui.path)==0)
				return 0;

			// parse info 
			ui.space  = (CHMStream.CHM_COMPRESSION)_chm_parse_cword(st);
			ui.start  = _chm_parse_cword(st);
			ui.length = _chm_parse_cword(st);
			return 1;
		}

		public chmUnitInfo FindObject(BinaryReader st, UInt32 block_len, string objPath)
		{	
			UInt32 end = (UInt32)st.BaseStream.Position+ block_len - free_space - _CHM_PMGL_LEN;			

			// now, scan progressively 
			chmUnitInfo FoundObject=new chmUnitInfo();

			while (st.BaseStream.Position < end)
			{				
				_chm_parse_PMGL_entry(st,ref FoundObject);
				if (FoundObject.path.ToLower().CompareTo(objPath.ToLower())==0)
					return FoundObject;
			}
			FoundObject=null;

			return null;
		}
	}

	public class chmPmgiHeader : BaseStructure
	{
		public const int _CHM_PMGI_LEN=0x8;

		public char[]	signature=null;           //  0 (PMGL) 
		public UInt32	free_space=0;             //  4 			

		public int Read_pmgi_header(BinaryReader st)
		{
			signature=st.ReadChars(4);
		
			if ((signature[0]!='P') || (signature[1]!='M') || (signature[2]!='G') || (signature[3]!='I'))
				return 0;

			free_space=st.ReadUInt32();				
			return 1;
		}

		public Int32 _chm_find_in_PMGI(BinaryReader st, UInt32 block_len, string objPath)
		{		     			
			int page=-1;
			UInt64 strLen;
			string buffer="";
			uint end = (uint)st.BaseStream.Position + block_len - free_space - _CHM_PMGI_LEN;

			// now, scan progressively 
			while (st.BaseStream.Position < end)
			{
				// grab the name 
				strLen = _chm_parse_cword(st);
				buffer="";
				if (_chm_parse_UTF8(st, strLen, ref buffer)==0)
					return -1;

				// check if it is the right name 
				if (buffer.ToLower().CompareTo(objPath.ToLower())>0)
					return page;

				// load next value for path 
				page = (int)_chm_parse_cword(st);
			}
			return page;
		}
	}

	public class chmLzxcResetTable:BaseStructure
	{		
		public UInt32      version=0;
		public UInt32      block_count=0;
		public UInt32      unknown=0;
		public UInt32      table_offset=0;
		public UInt64      uncompressed_len=0;
		public UInt64      compressed_len=0;
		public UInt64      block_len=0;     
		
		public int Read_lzxc_reset_table(BinaryReader st)
		{
			version=st.ReadUInt32();  
			block_count=st.ReadUInt32();  
			unknown=st.ReadUInt32();  
			table_offset=st.ReadUInt32();  
			uncompressed_len=st.ReadUInt64();  
			compressed_len=st.ReadUInt64();  
			block_len=st.ReadUInt64();  
  				
			// check structure 
			if (version != 2)
				return 0;
			else
				return 1;
		}
	}
	
	// structure of LZXC control data block 
	public class chmLzxcControlData:BaseStructure
	{
		public const int _CHM_LZXC_MIN_LEN=0x18;
		public const int _CHM_LZXC_V2_LEN=0x1c; 

		public UInt32   size=0;                   //  0        
		public char[]	signature=null;			  //  4 (LZXC) 
		public UInt32   version=0;                //  8        
		public UInt32   resetInterval=0;          //  c        
		public UInt32   windowSize=0;             // 10        
		public UInt32   windowsPerReset=0;        // 14        
		public UInt32   unknown_18=0;             // 18        
	
		public int Read_lzxc_control_data(BinaryReader st)
		{
			size=st.ReadUInt32();
			signature=st.ReadChars(4);

			if (CheckSig("LZXC",signature)==false)
				return 0;

			version=st.ReadUInt32();
			resetInterval=st.ReadUInt32();
			windowSize=st.ReadUInt32();
			windowsPerReset=st.ReadUInt32();			

			if (size>=_CHM_LZXC_V2_LEN)
				unknown_18=st.ReadUInt32();
			else
				unknown_18 = 0;

			if (version == 2)
			{
				resetInterval *= 0x8000;
				windowSize *= 0x8000;				
			}
			if (windowSize == 0  ||  resetInterval == 0)
				return 0;

			// for now, only support resetInterval a multiple of windowSize/2 
			if (windowSize == 1)
				return 0;
			if ((resetInterval % (windowSize/2)) != 0)
				return 0;
	
			return 1;
		}
	}
	#endregion

	#region LZW Decoder

	internal class lzx_bits 
	{
		public UInt32 bb=0;
		public int bl=0;			
	}

	internal class lzw
	{
		public lzw()
		{		
		}

		/* $Id: lzx.c,v 1.5 2002/10/09 01:16:33 jedwin Exp $ */
		/***************************************************************************
		*                        lzx.c - LZX decompression routines               *
		*                           -------------------                           *
		*                                                                         *
		*  maintainer: Jed Wing <jedwin@ugcs.caltech.edu>                         *
		*  source:     modified lzx.c from cabextract v0.5                        *
		*  notes:      This file was taken from cabextract v0.5, which was,       *
		*              itself, a modified version of the lzx decompression code   *
		*              from unlzx.                                                *
		*                                                                         *
		*  platforms:  In its current incarnation, this file has been tested on   *
		*              two different Linux platforms (one, redhat-based, with a   *
		*              2.1.2 glibc and gcc 2.95.x, and the other, Debian, with    *
		*              2.2.4 glibc and both gcc 2.95.4 and gcc 3.0.2).  Both were *
		*              Intel x86 compatible machines.                             *
		***************************************************************************/

		/***************************************************************************
		*                                                                         *
		*   This program is free software; you can redistribute it and/or modify  *
		*   it under the terms of the GNU General Public License as published by  *
		*   the Free Software Foundation; either version 2 of the License, or     *
		*   (at your option) any later version.  Note that an exemption to this   *
		*   license has been granted by Stuart Caie for the purposes of           *
		*   distribution with CHMFile.  This does not, to the best of my           *
		*   knowledge, constitute a change in the license of this (the LZX) code  *
		*   in general.                                                           *
		*                                                                         *
		***************************************************************************/

		/* some constants defined by the LZX specification */
		private  const int  LZX_MIN_MATCH                = 2;
		private  const int  LZX_MAX_MATCH                = 257;
		private  const int  LZX_NUM_CHARS                = 256;
		private  const int  LZX_BLOCKTYPE_INVALID        = 0;   /* also blocktypes 4-7 invalid */
		private  const int  LZX_BLOCKTYPE_VERBATIM       = 1;
		private  const int  LZX_BLOCKTYPE_ALIGNED        = 2;
		private  const int  LZX_BLOCKTYPE_UNCOMPRESSED   = 3;
		private  const int  LZX_PRETREE_NUM_ELEMENTS     = 20;
		private  const int  LZX_ALIGNED_NUM_ELEMENTS     = 8;   /* aligned offset tree #elements */
		private  const int  LZX_NUM_PRIMARY_LENGTHS      = 7;   /* this one missing from spec! */
		private  const int  LZX_NUM_SECONDARY_LENGTHS    = 249; /* length tree #elements */

		/* LZX huffman defines: tweak tablebits as desired */
		private  const int  LZX_PRETREE_MAXSYMBOLS  = LZX_PRETREE_NUM_ELEMENTS;
		private  const int  LZX_PRETREE_TABLEBITS   = 6;
		private  const int  LZX_MAINTREE_MAXSYMBOLS = LZX_NUM_CHARS + 50*8;
		private  const int  LZX_MAINTREE_TABLEBITS  = 12;
		private  const int  LZX_LENGTH_MAXSYMBOLS   = LZX_NUM_SECONDARY_LENGTHS+1;
		private  const int  LZX_LENGTH_TABLEBITS    = 12;
		private  const int  LZX_ALIGNED_MAXSYMBOLS  = LZX_ALIGNED_NUM_ELEMENTS;
		private  const int  LZX_ALIGNED_TABLEBITS   = 7;
		private  const int  LZX_LENTABLE_SAFETY	  = 64; /* we allow length table decoding overruns */

		public	 const int  DECR_OK				= 0;
		public	 const int  DECR_DATAFORMAT		= 1;
		public	 const int  DECR_ILLEGALDATA	= 2;
		public	 const int  DECR_NOMEMORY		= 3;

		private  byte[] window;         /* the actual decoding window              */
		private  ulong window_size;     /* window size (32Kb through 2Mb)          */
		private  ulong actual_size;     /* window size when it was first allocated */
		private  ulong window_posn;     /* current offset within the window        */
		private  ulong R0, R1, R2;      /* for the LRU offset system               */
		private  UInt32 main_elements;   /* number of main tree elements            */
		private  int   header_read;     /* have we started decoding at all yet?    */
		private  UInt32 block_type;      /* type of this block                      */
		private  ulong block_length;    /* uncompressed length of this block       */
		private  ulong block_remaining; /* uncompressed bytes still left to decode */
		private  ulong frames_read;     /* the number of CFDATA blocks processed   */
		private  long  intel_filesize;  /* magic header value used for transform   */
		private  long  intel_curpos;    /* current offset in transform space       */
		private  int   intel_started;   /* have we seen any translatable data yet? */


		private  uint [] PRETREE_table = new uint[(1<<(6)) + (((20))<<1)]; 
		private  byte [] PRETREE_len = new byte [((20)) + (64)];

		private  uint [] MAINTREE_table= new uint[(1<<(12)) + (((256) + 50*8)<<1)]; 
		private  byte [] MAINTREE_len = new byte [((256) + 50*8) + (64)];

		private  uint [] LENGTH_table= new uint[(1<<(12)) + (((249)+1)<<1)]; 
		private  byte [] LENGTH_len = new byte [((249)+1) + (64)];

		private  uint [] ALIGNED_table= new uint[(1<<(7)) + (((8))<<1)]; 
		private  byte [] ALIGNED_len = new byte [((8)) + (64)];		
		private System.IO.BinaryReader BitSource=null;
		private System.IO.Stream OutputStream=null;

		/* LZX decruncher */

		/* Microsoft's LZX document and their implementation of the
		* com.ms.util.cab Java package do not concur.
		*
		* In the LZX document, there is a table showing the correlation between
		* window size and the number of position slots. It states that the 1MB
		* window = 40 slots and the 2MB window = 42 slots. In the implementation,
		* 1MB = 42 slots, 2MB = 50 slots. The actual calculation is 'find the
		* first slot whose position base is equal to or more than the required
		* window size'. This would explain why other tables in the document refer
		* to 50 slots rather than 42.
		*
		* The constant NUM_PRIMARY_LENGTHS used in the decompression pseudocode
		* is not defined in the specification.
		*
		* The LZX document does not state the uncompressed block has an
		* uncompressed length field. Where does this length field come from, so
		* we can know how large the block is? The implementation has it as the 24
		* bits following after the 3 blocktype bits, before the alignment
		* padding.
		*
		* The LZX document states that aligned offset blocks have their aligned
		* offset huffman tree AFTER the main and length trees. The implementation
		* suggests that the aligned offset tree is BEFORE the main and length
		* trees.
		*
		* The LZX document decoding algorithm states that, in an aligned offset
		* block, if an extra_bits value is 1, 2 or 3, then that number of bits
		* should be read and the result added to the match offset. This is
		* correct for 1 and 2, but not 3, where just a huffman symbol (using the
		* aligned tree) should be read.
		*
		* Regarding the E8 preprocessing, the LZX document states 'No translation
		* may be performed on the last 6 bytes of the input block'. This is
		* correct.  However, the pseudocode provided checks for the *E8 leader*
		* up to the last 6 bytes. If the leader appears between -10 and -7 bytes
		* from the end, this would cause the next four bytes to be modified, at
		* least one of which would be in the last 6 bytes, which is not allowed
		* according to the spec.
		*
		* The specification states that the huffman trees must always contain at
		* least one element. However, many CAB files contain blocks where the
		* length tree is completely empty (because there are no matches), and
		* this is expected to succeed.
		*/

		/* LZX uses what it calls 'position slots' to represent match offsets.
		* What this means is that a small 'position slot' number and a small
		* offset from that slot are encoded instead of one large offset for
		* every match.
		* - position_base is an index to the position slot bases
		* - extra_bits states how many bits of offset-from-base data is needed.
		*/
		private byte [] extra_bits = {
										 0,  0,  0,  0,  1,  1,  2,  2,  3,  3,  4,  4,  5,  5,  6,  6,
										 7,  7,  8,  8,  9,  9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14,
										 15, 15, 16, 16, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17,
										 17, 17, 17
									 };

		private ulong [] position_base = {
											 0,       1,       2,      3,      4,      6,      8,     12,     16,     24,     32,       48,      64,      96,     128,     192,
											 256,     384,     512,    768,   1024,   1536,   2048,   3072,   4096,   6144,   8192,    12288,   16384,   24576,   32768,   49152,
											 65536,   98304,  131072, 196608, 262144, 393216, 524288, 655360, 786432, 917504, 1048576, 1179648, 1310720, 1441792, 1572864, 1703936,
											 1835008, 1966080, 2097152
										 };
		
		private UInt32 ReadUInt16()
		{
			UInt32 rc=0;
			UInt32 Byte1=0;
			UInt32 Byte2=0;
			try
			{				
				Byte1=BitSource.ReadByte();
				Byte2=BitSource.ReadByte();
			}
			catch(Exception)
			{
			}			
			rc=(Byte2<<8)+Byte1;
			return rc;
		}

		public bool LZXinit(int WindowSize)
		{			
			ulong wndsize = (ulong)(1 << WindowSize);
			int i, posn_slots;

			/* LZX supports window sizes of 2^15 (32Kb) through 2^21 (2Mb) */
			/* if a previously allocated window is big enough, keep it     */
			if (WindowSize< 15 || WindowSize> 21) return false;

			/* allocate state and associated window */		
			window = new byte[wndsize];
			if (window==null)
			{					
				return false;
			}

			actual_size = wndsize;
			window_size = wndsize;

			/* calculate required position slots */
			if (WindowSize == 20) posn_slots = 42;
			else if (WindowSize== 21) posn_slots = 50;
			else posn_slots = WindowSize << 1;

			/** alternatively **/
			/* posn_slots=i=0; while (i < wndsize) i += 1 << extra_bits[posn_slots++]; */

			/* initialize other state */
			R0  =  R1  = R2 = 1;
			main_elements   = (uint)(LZX_NUM_CHARS + (posn_slots << 3));
			header_read     = 0;
			frames_read     = 0;
			block_remaining = 0;
			block_type      = LZX_BLOCKTYPE_INVALID;
			intel_curpos    = 0;
			intel_started   = 0;
			window_posn     = 0;

			/* initialise tables to 0 (because deltas will be applied to them) */
			for (i = 0; i < LZX_MAINTREE_MAXSYMBOLS; i++) MAINTREE_len[i] = 0;
			for (i = 0; i < LZX_LENGTH_MAXSYMBOLS; i++)   LENGTH_len[i]   = 0;			

			return true;
		}

		public void LZXteardown()
		{
			window=null;			
		}

		public int LZXreset()
		{
			R0  =  R1  = R2 = 1;
			header_read     = 0;
			frames_read     = 0;
			block_remaining = 0;
			block_type      = LZX_BLOCKTYPE_INVALID;
			intel_curpos    = 0;
			intel_started   = 0;
			window_posn     = 0;

			for (int i = 0; i < LZX_MAINTREE_MAXSYMBOLS + LZX_LENTABLE_SAFETY; i++) MAINTREE_len[i] = 0;
			for (int i = 0; i < LZX_LENGTH_MAXSYMBOLS + LZX_LENTABLE_SAFETY; i++)   LENGTH_len[i]   = 0;

			return DECR_OK;
		}


		/* Bitstream reading macros:
		*
		* INIT_BITSTREAM    should be used first to set up the system
		* READ_BITS(var,n)  takes N bits from the buffer and puts them in var
		*
		* ENSURE_BITS(n)    ensures there are at least N bits in the bit buffer
		* PEEK_BITS(n)      extracts (without removing) N bits from the bit buffer
		* REMOVE_BITS(n)    removes N bits from the bit buffer
		*
		* These bit access routines work by using the area beyond the MSB and the
		* LSB as a free source of zeroes. This avoids having to mask any bits.
		* So we have to know the bit width of the bitbuffer variable. This is
		* sizeof(ulong) * 8, also defined as ULONG_BITS
		*/

		/* number of bits in ulong. Note: This must be at multiple of 16, and at
		* least 32 for the bitbuffer code to work (ie, it must be able to ensure
		* up to 17 bits - that's adding 16 bits when there's one bit left, or
		* adding 32 bits when there are no bits left. The code should work fine
		* for machines where ulong >= 32 bits.
		*/
		private int ULONG_BITS()
		{
			int rc=(System.Runtime.InteropServices.Marshal.SizeOf(typeof(System.UInt32))<<3);
			return rc;
		}

		/* make_decode_table(nsyms, nbits, length[], table[])
		*
		* This function was coded by David Tritscher. It builds a fast huffman
		* decoding table out of just a canonical huffman code lengths table.
		*
		* nsyms  = total number of symbols in this huffman tree.
		* nbits  = any symbols with a code length of nbits or less can be decoded
		*          in one lookup of the table.
		* length = A table to get code lengths from [0 to syms-1]
		* table  = The table to fill up with decoded symbols and pointers.
		*
		* Returns 0 for OK or 1 for error
		*/

		private int make_decode_table(ulong nsyms, byte nbits, ref byte [] length, ref UInt32[] table) 
		{
			ulong sym;
			ulong leaf;
			byte bit_num = 1;
			ulong fill;
			ulong pos         = 0; /* the current position in the decode table */
			ulong table_mask  = (ulong)(1 << nbits);
			ulong bit_mask    = table_mask >> 1; /* don't do 0 length codes */
			ulong next_symbol = bit_mask; /* base of allocation for long codes */

			/* fill entries for codes short enough for a direct mapping */
			while (bit_num <= nbits) 
			{
				for (sym = 0; sym < nsyms; sym++) 
				{
					if (length[sym] == bit_num) 
					{
						leaf = pos;

						if((pos += bit_mask) > table_mask) return 1; /* table overrun */

						/* fill all possible lookups of this symbol with the symbol itself */
						fill = bit_mask;
						while (fill-- > 0) table[leaf++] = (uint)sym;
					}
				}
				bit_mask >>= 1;
				bit_num++;
			}

			/* if there are any codes longer than nbits */
			if (pos != table_mask) 
			{
				/* clear the remainder of the table */
				for (sym = pos; sym < table_mask; sym++) table[sym] = 0;

				/* give ourselves room for codes to grow by up to 16 more bits */
				pos <<= 16;
				table_mask <<= 16;
				bit_mask = 1 << 15;

				while (bit_num <= 16) 
				{
					for (sym = 0; sym < nsyms; sym++) 
					{
						if (length[sym] == bit_num) 
						{
							leaf = pos >> 16;
							for (fill = 0; fill < (ulong)(bit_num - nbits); fill++) 
							{
								/* if this path hasn't been taken yet, 'allocate' two entries */
								if (table[leaf] == 0) 
								{
									table[(next_symbol << 1)] = 0;
									table[(next_symbol << 1) + 1] = 0;
									table[leaf] = (uint)next_symbol++;
								}
								/* follow the path and select either left or right for next bit */
								leaf = table[leaf] << 1;
								if (((pos >> (byte)(15-fill)) & 1)==1) 
									leaf++;
							}
							table[leaf] = (uint)sym;

							if ((pos += bit_mask) > table_mask) 
								return 1; /* table overflow */
						}
					}
					bit_mask >>= 1;
					bit_num++;
				}
			}

			/* full table? */
			if (pos == table_mask) 
				return 0;

			/* either erroneous table, or all elements are 0 - let's find out. */
			for (sym = 0; sym < nsyms; sym++) if (length[(uint)sym]!=0) 
												  return 1;

			return 0;
		}		

		private int lzx_read_lens(byte []lens, ulong first, ulong last, ref lzx_bits lb) 
		{
			ulong i,j, x,y;
			int z;
			
			UInt32 bitbuf = lb.bb;
			int bitsleft = lb.bl;			

			UInt32 [] hufftbl=null;			

			for (x = 0; x < 20; x++) 
			{
				do
				{
				while (bitsleft < (4)) 
				{ 
					bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
					bitsleft += 16; 
				} 
					y = (bitbuf >> (ULONG_BITS()- (4))); 
					bitbuf <<= 4; 
					bitsleft -= 4; 
				}
				while (false);
				PRETREE_len[x] = (byte)y;
			}	
			if (make_decode_table( 20, 6, ref PRETREE_len, ref PRETREE_table)!=0)
				return 2;			

			for (x = first; x < last; ) 
			{
				do
				{
				while (bitsleft < 16) 
				{ 
					bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
					bitsleft += 16; 
				} 
					hufftbl = PRETREE_table; 
					if ((i = hufftbl[((ulong)bitbuf >> (ULONG_BITS()- 6))]) >= 20) 
					{ 
						j = (ulong)(1 << (byte)(ULONG_BITS()- ((6)))); 
						do 
						{
							j >>= 1; 
							i <<= 1; 
							if ((bitbuf & j)!=0)
								i|=1;
							else
								i|=0;

							if (j==0) 
							{
								return (2); 
							} 
						} 
						while ((i = hufftbl[i]) >= 20); 
					} 
					z = (int)i;
					j = PRETREE_len[z]; 
					bitbuf <<= (byte)j;
					bitsleft -= (int)j; 
				}
				while (false);

				if (z == 17) 
				{
					do
					{
					while (bitsleft < (4)) 
					{ 
						bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
						bitsleft += 16; 
					} 
						y = (bitbuf >> (ULONG_BITS()- (4))); 
						bitbuf <<= 4;
						bitsleft -= 4; 
					}
					while(false);
					y += 4;
				
					while ((y--)!=0) 
						lens[x++] = 0;
				}
				else if (z == 18) 
				{					
					do
					{
					while (bitsleft < (5)) 
					{ 
						bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
						bitsleft += 16;
					} 
						(y) = (bitbuf >> (ULONG_BITS()- (5))); 
						bitbuf <<= 5;
						bitsleft -= 5; 
					}
					while (false);
				
					y += 20;

					while ((y--)!=0) 
						lens[x++] = 0;
				}
				else if (z == 19) 
				{
					do
					{
					while (bitsleft < (1)) 
					{
						bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
						bitsleft += 16; 
					} 
						y = (bitbuf >> (ULONG_BITS()- (1))); 
						bitbuf <<= 1;
						bitsleft -= 1; 
					}
					while(false);
					y += 4;
					do
					{
					while (bitsleft < (16)) 
					{ 
						bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
						bitsleft += 16; 							
					} 
						hufftbl = (PRETREE_table); 
						if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 6))]) >= 20) 
						{ 
							j = (ulong)1 << (byte)(ULONG_BITS()- 6); 
							do 
							{ 
								j >>= 1; 
								i <<= 1; 
								if ((bitbuf & j)==0)
									i|=0;
								else
									i|=1;
								if (j==0) 
								{ 
									return (2); 
								} 
							} 
							while ((i = hufftbl[i]) >= 20); 
						} 
						z = (int)i;
						j = PRETREE_len[z]; 					

						bitbuf <<= (byte)j;
						bitsleft -= (int)j; 
					}
					while(false);
					z = lens[x] - z; 
					if (z < 0) 
						z += 17;

					while ((y--)!=0) 
						lens[x++] = (byte)z;
				}
				else 
				{
					z = lens[x] - z; 
					if (z < 0) 
						z += 17;
					lens[x++] = (byte)z;
				}
			}
			lb.bb = bitbuf;
			lb.bl = bitsleft;
			return 0;
		}

		public int LZXdecompress(System.IO.BinaryReader inpos, System.IO.Stream outpos, ref ulong inlen, ref ulong outlen) 
		{
			BitSource=inpos;
			OutputStream=outpos;
			
			long endinp = BitSource.BaseStream.Position+(long)inlen;						
			ulong runsrc, rundest;
			UInt32 [] hufftbl; /* used in READ_HUFFSYM macro as chosen decoding table */

			UInt32 bitbuf;
			int bitsleft;
			ulong match_offset, i,j,k; /* ijk used in READ_HUFFSYM macro */
			lzx_bits lb; /* used in READ_LENGTHS macro */
			lb=new lzx_bits();			

			int togo = (int)outlen, this_run, main_element, aligned_bits;
			int match_length, length_footer, extra, verbatim_bits;

			bitsleft = 0; 
			bitbuf = 0; 

			/* read header if necessary */
			if (header_read==0) 
			{
				i = j = 0;	
				do
				{
				while (bitsleft < (1)) 
				{ 
					bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
					bitsleft += 16; 
				} 
					k = (bitbuf >> (ULONG_BITS()- (1))); 
					bitbuf <<= 1;
					bitsleft -= 1;
				}
				while(false);

				if (k!=0)	
				{
					do
					{
					while (bitsleft < (16)) 
					{ 
						bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() -16 -  bitsleft); 
						bitsleft += 16; 
					} 
						i = (bitbuf >> (ULONG_BITS()- (16))); 
						bitbuf <<= 16;
						bitsleft -= 1;
					}
					while(false);

					do
					{
					while (bitsleft < (16)) 
					{ 
						bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
						bitsleft += 16; 							
					} 
						j = (bitbuf >> (ULONG_BITS()- (16))); 
						bitbuf <<= 16;
						bitsleft -= 16;					
					}
					while(false);
				}
				intel_filesize = (long)((i << 16) | j); 
				header_read = 1;
			}

			/* main decoding loop */
			while (togo > 0) 
			{
				if (block_remaining == 0) 
				{
					if (block_type == (3)) 
					{
						if ((block_length & 1)!=0) 
							BitSource.ReadByte();
						bitsleft = 0; 
						bitbuf = 0; 
					}

					do 
					{ 
					while (bitsleft < (3)) 
					{ 
						bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
						bitsleft += 16; 
					} 
						(block_type) = (uint)(bitbuf >> (ULONG_BITS()- (3))); 
						bitbuf <<= 3; 
						bitsleft -= 3; 
					} 
					while (false);

					do 
					{ 
					while (bitsleft < (16)) 
					{ 
						bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
						bitsleft += 16; 
					} 
						(i) = (bitbuf >> (ULONG_BITS()- (16))); 
						bitbuf <<= 16;  
						bitsleft -= 16; 
					} 
					while (false);

					do 
					{ 
					while (bitsleft < (8)) 
					{ 
						bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
						bitsleft += 16; 
					} 
						(j) = (bitbuf >> (ULONG_BITS()- (8))); 
						bitbuf <<= 8; 
						bitsleft -= 8; 
					} 
					while (false);
					block_remaining = block_length = (i << 8) | j;

					switch (block_type) 
					{
						case (LZX_BLOCKTYPE_ALIGNED):
							for (i = 0; i < 8; i++) 
							{ 
								do 
								{ 
								while (bitsleft < (3)) 
								{ 
									bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
									bitsleft += 16; 
								} 
									(j) = (bitbuf >> (ULONG_BITS()- (3))); 
									bitbuf <<= 3; 
									bitsleft -= 3; 
								} 
								while (false); 
								(ALIGNED_len)[i] = (byte)j; 
							}
							if (make_decode_table( 8, 7, ref ALIGNED_len, ref ALIGNED_table)!=0) 
							{ 
								return (2); 
							}

							do 
							{ 
								lb.bb = bitbuf; 
								lb.bl = bitsleft; 								
								if (lzx_read_lens(MAINTREE_len,0,256,ref lb)!=0) 
								{ 
									return (2); 
								} 
								bitbuf = lb.bb; 
								bitsleft = lb.bl; 								
							} 
							while (false);
							do 
							{ 
								lb.bb = bitbuf; 
								lb.bl = bitsleft; 
								if (lzx_read_lens(MAINTREE_len,256,main_elements,ref lb)!=0) 
								{ 
									return (2); 
								} 
								bitbuf = lb.bb; 
								bitsleft = lb.bl; 
							} 
							while (false);

							if (make_decode_table( (256 + 50*8), 12, ref MAINTREE_len, ref MAINTREE_table)!=0) 
							{ 
								return (2); 
							}
							if (MAINTREE_len[0xE8] != 0) intel_started = 1;

							do 
							{ 
								lb.bb = bitbuf; 
								lb.bl = bitsleft; 
								if (lzx_read_lens(LENGTH_len,0,249,ref lb)!=0) 
								{ 
									return (2); 
								} 
								bitbuf = lb.bb; 
								bitsleft = lb.bl; 
							} 
							while (false);
							if (make_decode_table( (249+1), 12, ref LENGTH_len, ref LENGTH_table)!=0) 
							{ 
								return (2); 
							}
							break;							
		                    
						case (LZX_BLOCKTYPE_VERBATIM):
							do 
							{ 
								lb.bb = bitbuf; 
								lb.bl = bitsleft; 								
								if (lzx_read_lens(MAINTREE_len,0,256,ref lb)!=0) 
								{ 
									return (2); 
								} 
								bitbuf = lb.bb; 
								bitsleft = lb.bl; 								
							} 
							while (false);
							do 
							{ 
								lb.bb = bitbuf; 
								lb.bl = bitsleft; 								
								if (lzx_read_lens(MAINTREE_len,256,main_elements,ref lb)!=0) 
								{ 
									return (2); 
								} 
								bitbuf = lb.bb; 
								bitsleft = lb.bl; 
							} 
							while (false);

							if (make_decode_table( (256 + 50*8), 12, ref MAINTREE_len, ref MAINTREE_table)!=0) 
							{ 
								return (2); 
							}
							if (MAINTREE_len[0xE8] != 0) intel_started = 1;

							do 
							{ 
								lb.bb = bitbuf; 
								lb.bl = bitsleft; 
								if (lzx_read_lens(LENGTH_len,0,249,ref lb)!=0) 
								{ 
									return (2); 
								} 
								bitbuf = lb.bb; 
								bitsleft = lb.bl; 
							} 
							while (false);
							if (make_decode_table( (249+1), 12, ref LENGTH_len, ref LENGTH_table)!=0) 
							{ 
								return (2); 
							}
							break;

						case (LZX_BLOCKTYPE_UNCOMPRESSED):
							intel_started = 1; 
							while (bitsleft < (16)) 
							{ 
								bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - bitsleft); 
								bitsleft += 16;
							} 
							if (bitsleft > 16)
							{
								BitSource.BaseStream.Seek(-2,System.IO.SeekOrigin.Current);								
							}
							R0 = (ulong)(BitSource.ReadByte()+(BitSource.ReadByte()<<8)+(BitSource.ReadByte()<<16)+(BitSource.ReadByte()<<24));
							R1 = (ulong)(BitSource.ReadByte()+(BitSource.ReadByte()<<8)+(BitSource.ReadByte()<<16)+(BitSource.ReadByte()<<24));
							R2 = (ulong)(BitSource.ReadByte()+(BitSource.ReadByte()<<8)+(BitSource.ReadByte()<<16)+(BitSource.ReadByte()<<24));
							break;

						default:
							return (DECR_ILLEGALDATA);
					}
				}

				/* buffer exhaustion check */
				if (BitSource.BaseStream.Position > (long) endinp) 
				{
					/* it's possible to have a file where the next run is less than
					* 16 bits in size. In this case, the READ_HUFFSYM() macro used
					* in building the tables will exhaust the buffer, so we should
					* allow for this, but not allow those accidentally read bits to
					* be used (so we check that there are at least 16 bits
					* remaining - in this boundary case they aren't really part of
					* the compressed data)
					*/
					if (BitSource.BaseStream.Position> (long)(endinp+2) || bitsleft < 16) 
						return DECR_ILLEGALDATA;
				}

				while ((this_run = (int)block_remaining) > 0 && togo > 0) 
				{
					if (this_run > togo)
						this_run = togo;

					togo -= this_run;
					block_remaining -= (ulong)this_run;

					/* apply 2^x-1 mask */
					window_posn &= window_size - 1;

					/* runs can't straddle the window wraparound */
					if ((window_posn + (ulong)this_run) > window_size)
						return DECR_DATAFORMAT;

					switch (block_type) 
					{
						case LZX_BLOCKTYPE_VERBATIM:
							while (this_run > 0) 
							{
								do 
								{ 
								while (bitsleft < (16)) 
								{ 
									bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
									bitsleft += 16; 
								} 
									hufftbl = MAINTREE_table; 
									if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 12))]) >= 256 + 50*8) 
									{ 
										j = (ulong)(1 << (ULONG_BITS()- 12)); 
										do 
										{ 
											j >>= 1; 
											i <<= 1; 
											if ((bitbuf & j)!=0) 
												i|=1; 
											else 
												i|=0; 
											if (j==0) 
											{ 
												return (2); 
											} 
										} 
										while ((i = hufftbl[i]) >= (((256) + 50*8))); 
									} 
									j = MAINTREE_len[main_element = (int)i]; 
									bitbuf <<= (byte)j; 
									bitsleft -= (byte)j; 
								} 
								while (false);

								if (main_element < (256)) 
								{	                            
									window[window_posn++] = (byte)main_element;
									this_run--;
								}
								else 
								{	                            
									main_element -= (256);

									match_length = main_element & (7);
									if (match_length == (7)) 
									{
										do 
										{ 
										while (bitsleft < (16)) 
										{ 
											bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
											bitsleft += 16; 
										} 
											hufftbl = (LENGTH_table); 
											if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 12))]) >= (((249)+1))) 
											{ 
												j = (ulong)(1 << (ULONG_BITS()- ((12)))); 
												do 
												{ 
													j >>= 1; 
													i <<= 1; 
													if ((bitbuf & j)!=0) 
														i|=1; 
													else 
														i|=0; 
													if (j==0) 
													{ 
														return (2); 
													} 
												} 
												while ((i = hufftbl[i]) >= (((249)+1))); 
											} 
											j = LENGTH_len[(length_footer) = (int)i]; 
											bitbuf <<= (byte)j; 
											bitsleft -= (byte)j; 
										} 
										while (false);

										match_length += length_footer;
									}
									match_length += (2);

									match_offset = (ulong)(main_element >> 3);

									if (match_offset > 2) 
									{	                                
										if (match_offset != 3) 
										{
											extra = extra_bits[match_offset];
											do 
											{ 
											while (bitsleft < (extra)) 
											{ 
												bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
												bitsleft += 16; 
											} 
												verbatim_bits = (int)(bitbuf >> (ULONG_BITS()- (extra))); 
												bitbuf <<= extra; 
												bitsleft -= extra; 
											} 
											while (false);
											match_offset = position_base[match_offset] - 2 + (ulong)verbatim_bits;
										}
										else 
										{
											match_offset = 1;
										}
	                            
										R2 = R1; R1 = R0; R0 = match_offset;
									}
									else if (match_offset == 0) 
									{
										match_offset = R0;
									}
									else if (match_offset == 1) 
									{
										match_offset = R1;
										R1 = R0; R0 = match_offset;
									}
									else  
									{
										match_offset = R2;
										R2 = R0; R0 = match_offset;
									}

									rundest = window_posn;
									// rundest= window+window_posn
									runsrc  = rundest - match_offset;								
									window_posn += (ulong)match_length;
									this_run -= match_length;
	                        
									// runsrc < window
									while ((runsrc<0) && (match_length-- > 0))
									{
										window[rundest++]=window[runsrc+window_size];
										// *rundest++ = *(runsrc + window_size); 
										runsrc++;
									}
	                        
									while (match_length-- > 0) 
									{
										window[rundest++]=window[runsrc++];
										// *rundest++ = *runsrc++;
									}
								}
							}
							break;

						case LZX_BLOCKTYPE_ALIGNED:
							while (this_run > 0) 
							{
								do 
								{ 
								while (bitsleft < (16)) 
								{ 
									bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
									bitsleft += 16; 									
								} 
									hufftbl = MAINTREE_table; 
									if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 12))]) >= (((256) + 50*8))) 
									{ 
										j = (ulong)1 << (ULONG_BITS()- ((12))); 
										do 
										{ 
											j >>= 1; 
											i <<= 1; 
											if ((bitbuf & j)!=0) 
												i|=1; 
											else 
												i|=0; 
											if (j==0) 
											{ 
												return (2); 
											} 
										} 
										while ((i = hufftbl[i]) >= (((256) + 50*8))); 
									} 
									j = MAINTREE_len[(main_element) = (int)i]; 
									bitbuf <<= (int)j; 
									bitsleft -= (int)j; 
								} 
								while (false);

								if (main_element < (256)) 
								{                            
									window[window_posn++] = (byte)main_element;
									this_run--;
								}
								else 
								{                            
									main_element -= (256);
									match_length = main_element & (7);
									if (match_length == (7)) 
									{
										do 
										{ 
										while (bitsleft < (16)) 
										{ 
											bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
											bitsleft += 16; 
										} 
											hufftbl = LENGTH_table; 
											if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 12))]) >= (((249)+1))) 
											{ 
												j = (ulong) 1 << (ULONG_BITS()- ((12))); 
												do 
												{ 
													j >>= 1; 
													i <<= 1; 
													if ((bitbuf & j)!=0)
														i|=1;
													else
														i|=0;

													if (j==0) 
													{ 
														return (2); 
													} 
												} 
												while ((i = hufftbl[i]) >= (((249)+1))); 
											} 
											j = LENGTH_len[length_footer = (int)i]; 
											bitbuf <<= (int)j; 
											bitsleft -= (int)j; 
										} 
										while (false);
										match_length += length_footer;
									}
									match_length += (2);

									match_offset = (ulong)(main_element >> 3);

									if (match_offset > 2) 
									{                                
										extra = extra_bits[match_offset];
										match_offset = position_base[match_offset] - 2;
										if (extra > 3) 
										{                                    
											extra -= 3;
											do 
											{ 
											while (bitsleft < (extra)) 
											{	
												bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
												bitsleft += 16; 
											} 
												verbatim_bits = (int)(bitbuf >> (ULONG_BITS()- (extra))); 
												bitbuf <<= extra; 
												bitsleft -= extra; 
											} 
											while (false);
											match_offset += (ulong)(verbatim_bits << 3);
											do 
											{ 
											while (bitsleft < (16)) 
											{ 
												bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
												bitsleft += 16; 
											} 
												hufftbl = (ALIGNED_table); 
												if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 7))]) >= 8) 
												{ 
													j = (ulong)1 << (ULONG_BITS()- ((7))); 
													do 
													{ 
														j >>= 1; 
														i <<= 1; 
														if ((bitbuf & j)!=0)
															i|=1;
														else
															i|=0;
														if (j==0) 
														{ 
															return (2); 
														} 
													} 
													while ((i = hufftbl[i]) >= (((8)))); 
												} 

												j = (ALIGNED_len)[(aligned_bits) = (int)i]; 
												bitbuf <<= (int)j; 
												bitsleft -= (int)j; 
											} 
											while (false);
											match_offset += (ulong)aligned_bits;
										}
										else if (extra == 3)	
										{                                    
											do 
											{ 
											while (bitsleft < (16)) 
											{ 
												bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
												bitsleft += 16; 
											} 
												hufftbl = (ALIGNED_table); 
												if ((i = hufftbl[(bitbuf >> (ULONG_BITS()- 7))]) >= 8) 
												{ 
													j = (ulong)1 << (ULONG_BITS()- ((7))); 
													do 
													{ 
														j >>= 1; 
														i <<= 1; 
														if ((bitbuf & j)!=0)
															i|=1;
														else
															i|=0;
														if (j!=0) 
														{ 
															return (2); 
														} 
													} 
													while ((i = hufftbl[i]) >= 8); 
												} 
												j = (ALIGNED_len)[(aligned_bits) = (int)i]; 
												bitbuf <<= (int)j; 
												bitsleft -= (int)j; 
											} 
											while (false);
											match_offset += (ulong)aligned_bits;
										}
										else if (extra > 0) 
										{                                     
											do 
											{ 
											while (bitsleft < (extra)) 
											{ 
												bitbuf |= (UInt32)ReadUInt16() << (ULONG_BITS() - 16 - bitsleft); 
												bitsleft += 16; 
											} 
												(verbatim_bits) = (int)(bitbuf >> (int)(ULONG_BITS()- (extra))); 
												bitbuf <<= extra;
												bitsleft -= extra; 
											} 
											while (false);
											match_offset += (ulong)verbatim_bits;
										}
										else  
										{                                    
											match_offset = 1;
										}
                                
										R2 = R1; R1 = R0; R0 = match_offset;
									}
									else if (match_offset == 0) 
									{
										match_offset = R0;
									}
									else if (match_offset == 1) 
									{
										match_offset = R1;
										R1 = R0; R0 = match_offset;
									}
									else  
									{
										match_offset = R2;
										R2 = R0; R0 = match_offset;
									}

									rundest = window_posn;
									runsrc  = rundest - match_offset;
									window_posn += (ulong)match_length;
									this_run -= match_length;
	                            
									while ((runsrc<0) && (match_length-- > 0))
									{
										// *rundest++ = *(runsrc + window_size); runsrc++;
										window[rundest++]=window[runsrc + window_size];
										runsrc++;
									}
	                            
									while (match_length-- > 0) 
									{
										// *rundest++ = *runsrc++;
										window[rundest++]=window[runsrc++];
									}
								}
							}
							break;

						case LZX_BLOCKTYPE_UNCOMPRESSED:
							if ((BitSource.BaseStream.Position + (long)this_run) > (long)endinp) 
								return (2);

							// memcpy(window + window_posn, inposCount, this_run);
							for(i=0; i<(ulong)this_run;i++)
							{
								window[window_posn+i]=BitSource.ReadByte();
							}
							window_posn += (ulong)this_run;
							break;

						default:
							return DECR_ILLEGALDATA; /* might as well */
					}

				}
			}

			if (togo != 0) return DECR_ILLEGALDATA;

			// memcpy(outpos, window + ((!window_posn) ? window_size : window_posn) - outlen, (size_t) outlen);
			ulong start=0;
			if (window_posn==0)
				start=(ulong)window_size;
			else
				start=(ulong)window_posn;
			
			start-=(ulong)outlen;

			long Pos=OutputStream.Position;
			for(i=0;i<(ulong)outlen;i++)
			{	
				OutputStream.WriteByte(window[start+i]);
			}
			OutputStream.Seek(Pos,System.IO.SeekOrigin.Begin);
			
			/* intel E8 decoding */
			if ((frames_read++ < 32768) && intel_filesize != 0) 
			{
				if (outlen <= 6 || (intel_started==0)) 
				{
					intel_curpos += (long)outlen;
				}
				else 
				{
					// UBYTE *data    = outpos;
					long dataend = OutputStream.Position + (int)outlen - 10;
					long curpos    = intel_curpos;
					long filesize  = intel_filesize;
					long abs_off, rel_off;

					intel_curpos = (long)curpos + (long)outlen;

					while (OutputStream.Position < dataend) 
					{
						if (OutputStream.ReadByte() != 0xE8) 
						{ 
							curpos++; 
							continue; 
						}

						abs_off = (long)(OutputStream.ReadByte() | (OutputStream.ReadByte() <<8) | (OutputStream.ReadByte() <<16) | (OutputStream.ReadByte() <<24));
						if (abs_off < filesize)
						{
							if (abs_off >= 0)
								rel_off = (long)(abs_off - curpos);
							else 
								rel_off = (long)abs_off + filesize;
							OutputStream.WriteByte((byte)(rel_off  & 0x000000ff));
							OutputStream.WriteByte((byte)((rel_off & 0x0000ff00)>>8));
							OutputStream.WriteByte((byte)((rel_off & 0x00ff0000)>>16));
							OutputStream.WriteByte((byte)((rel_off & 0xff000000)>>24));							
						}
						curpos += 5;
					}
				}
			}			
			
			return DECR_OK;
		}		
	}
	#endregion
}