// CoreUtil
// 
// Copyright (C) 2012-2014 Daiyuu Nobori. All Rights Reserved.
// Copyright (C) 2012-2014 SoftEther VPN Project at University of Tsukuba. All Rights Reserved.
// 
// License: The Apache License, Version 2.0
// https://www.apache.org/licenses/LICENSE-2.0
// 
// DISCLAIMER
// ==========
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
// 
// THIS SOFTWARE IS DEVELOPED IN JAPAN, AND DISTRIBUTED FROM JAPAN, UNDER
// JAPANESE LAWS. YOU MUST AGREE IN ADVANCE TO USE, COPY, MODIFY, MERGE, PUBLISH,
// DISTRIBUTE, SUBLICENSE, AND/OR SELL COPIES OF THIS SOFTWARE, THAT ANY
// JURIDICAL DISPUTES WHICH ARE CONCERNED TO THIS SOFTWARE OR ITS CONTENTS,
// AGAINST US (SOFTETHER PROJECT, SOFTETHER CORPORATION, DAIYUU NOBORI OR OTHER
// SUPPLIERS), OR ANY JURIDICAL DISPUTES AGAINST US WHICH ARE CAUSED BY ANY KIND
// OF USING, COPYING, MODIFYING, MERGING, PUBLISHING, DISTRIBUTING, SUBLICENSING,
// AND/OR SELLING COPIES OF THIS SOFTWARE SHALL BE REGARDED AS BE CONSTRUED AND
// CONTROLLED BY JAPANESE LAWS, AND YOU MUST FURTHER CONSENT TO EXCLUSIVE
// JURISDICTION AND VENUE IN THE COURTS SITTING IN TOKYO, JAPAN. YOU MUST WAIVE
// ALL DEFENSES OF LACK OF PERSONAL JURISDICTION AND FORUM NON CONVENIENS.
// PROCESS MAY BE SERVED ON EITHER PARTY IN THE MANNER AUTHORIZED BY APPLICABLE
// LAW OR COURT RULE.
// 
// USE ONLY IN JAPAN. DO NOT USE THIS SOFTWARE IN ANOTHER COUNTRY UNLESS YOU HAVE
// A CONFIRMATION THAT THIS SOFTWARE DOES NOT VIOLATE ANY CRIMINAL LAWS OR CIVIL
// RIGHTS IN THAT PARTICULAR COUNTRY. USING THIS SOFTWARE IN OTHER COUNTRIES IS
// COMPLETELY AT YOUR OWN RISK. THE SOFTETHER VPN PROJECT HAS DEVELOPED AND
// DISTRIBUTED THIS SOFTWARE TO COMPLY ONLY WITH THE JAPANESE LAWS AND EXISTING
// CIVIL RIGHTS INCLUDING PATENTS WHICH ARE SUBJECTS APPLY IN JAPAN. OTHER
// COUNTRIES' LAWS OR CIVIL RIGHTS ARE NONE OF OUR CONCERNS NOR RESPONSIBILITIES.
// WE HAVE NEVER INVESTIGATED ANY CRIMINAL REGULATIONS, CIVIL LAWS OR
// INTELLECTUAL PROPERTY RIGHTS INCLUDING PATENTS IN ANY OF OTHER 200+ COUNTRIES
// AND TERRITORIES. BY NATURE, THERE ARE 200+ REGIONS IN THE WORLD, WITH
// DIFFERENT LAWS. IT IS IMPOSSIBLE TO VERIFY EVERY COUNTRIES' LAWS, REGULATIONS
// AND CIVIL RIGHTS TO MAKE THE SOFTWARE COMPLY WITH ALL COUNTRIES' LAWS BY THE
// PROJECT. EVEN IF YOU WILL BE SUED BY A PRIVATE ENTITY OR BE DAMAGED BY A
// PUBLIC SERVANT IN YOUR COUNTRY, THE DEVELOPERS OF THIS SOFTWARE WILL NEVER BE
// LIABLE TO RECOVER OR COMPENSATE SUCH DAMAGES, CRIMINAL OR CIVIL
// RESPONSIBILITIES. NOTE THAT THIS LINE IS NOT LICENSE RESTRICTION BUT JUST A
// STATEMENT FOR WARNING AND DISCLAIMER.
// 
// READ AND UNDERSTAND THE 'WARNING.TXT' FILE BEFORE USING THIS SOFTWARE.
// SOME SOFTWARE PROGRAMS FROM THIRD PARTIES ARE INCLUDED ON THIS SOFTWARE WITH
// LICENSE CONDITIONS WHICH ARE DESCRIBED ON THE 'THIRD_PARTY.TXT' FILE.
// 
// 
// SOURCE CODE CONTRIBUTION
// ------------------------
// 
// Your contribution to SoftEther VPN Project is much appreciated.
// Please send patches to us through GitHub.
// Read the SoftEther VPN Patch Acceptance Policy in advance:
// http://www.softether.org/5-download/src/9.patch
// 
// 
// DEAR SECURITY EXPERTS
// ---------------------
// 
// If you find a bug or a security vulnerability please kindly inform us
// about the problem immediately so that we can fix the security problem
// to protect a lot of users around the world as soon as possible.
// 
// Our e-mail address for security reports is:
// softether-vpn-security [at] softether.org
// 
// Please note that the above e-mail address is not a technical support
// inquiry address. If you need technical assistance, please visit
// http://www.softether.org/ and ask your question on the users forum.
// 
// Thank you for your cooperation.
// 
// 
// NO MEMORY OR RESOURCE LEAKS
// ---------------------------
// 
// The memory-leaks and resource-leaks verification under the stress
// test has been passed before release this source code.


using System;
using System.Threading;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.Text;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Web.Mail;

namespace CoreUtil
{
	internal class HamCoreEntry : IComparable
	{
		public string FileName = "";
		public uint Size = 0;
		public uint SizeCompressed = 0;
		public uint Offset = 0;
		public byte[] Buffer = null;
		public long LastAccess = 0;

		public int CompareTo(object obj)
		{
			HamCoreEntry hc1, hc2;
			hc1 = this;
			hc2 = (HamCoreEntry)obj;

			return Str.StrCmpiRetInt(hc1.FileName, hc2.FileName);
		}
	}

	public class HamCoreBuilderFileEntry : IComparable<HamCoreBuilderFileEntry>
	{
		public string Name;
		public Buf RawData;
		public Buf CompressedData;
		public int Offset = 0;

		int IComparable<HamCoreBuilderFileEntry>.CompareTo(HamCoreBuilderFileEntry other)
		{
			return this.Name.CompareTo(other.Name);
		}
	}

	public class HamCoreBuilder
	{
		List<HamCoreBuilderFileEntry> fileList;
		public List<HamCoreBuilderFileEntry> FileList
		{
			get { return fileList; }
		}

		public bool IsFile(string name)
		{
			foreach (HamCoreBuilderFileEntry f in fileList)
			{
				if (f.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
				{
					return true;
				}
			}

			return false;
		}

		public bool DeleteFile(string name)
		{
			foreach (HamCoreBuilderFileEntry f in fileList)
			{
				if (f.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
				{
					fileList.Remove(f);
					return true;
				}
			}

			return false;
		}

		public HamCoreBuilder()
		{
			fileList = new List<HamCoreBuilderFileEntry>();
		}

		public void AddDir(string dirName)
		{
			dirName = IO.RemoteLastEnMark(dirName);

			DirEntry[] ee = IO.EnumDirEx(dirName);

			foreach (DirEntry e in ee)
			{
				if (e.IsFolder == false)
				{
					AddFile(e.FullPath, dirName);
				}
			}
		}

		public void AddFile(string fileName, string baseDirFileName)
		{
			string name = IO.GetRelativeFileName(fileName, baseDirFileName);

			AddFile(name, File.ReadAllBytes(fileName));
		}

		public void AddFile(string name, byte[] data)
		{
			if (IsFile(name))
			{
				throw new InvalidOperationException("fileName");
			}

			HamCoreBuilderFileEntry f = new HamCoreBuilderFileEntry();

			Console.Write("{0}: ", name);

			f.Name = name;
			f.RawData = new Buf(Util.CloneByteArray(data));
			Console.Write("{0} -> ", f.RawData.Size);
			f.CompressedData = new Buf(ZLib.Compress(f.RawData.ByteData));
			Console.WriteLine("{0}", f.CompressedData.Size);

			this.fileList.Add(f);
		}

		public void Build(string dstFileName)
		{
			Buf b = Build();

			IO.SaveFile(dstFileName, b.ByteData);
		}

		public Buf Build()
		{
			int z;
			Buf b;

			this.fileList.Sort();

			z = 0;

			z += HamCore.HamcoreHeaderSize;

			z += sizeof(int);

			foreach (HamCoreBuilderFileEntry f in this.fileList)
			{
				z += Str.ShiftJisEncoding.GetByteCount(f.Name) + sizeof(int);
				z += sizeof(int);
				z += sizeof(int);
				z += sizeof(int);
			}
			foreach (HamCoreBuilderFileEntry f in this.fileList)
			{
				f.Offset = z;
				z += (int)f.CompressedData.Size;
			}

			b = new Buf();
			b.Write(Str.ShiftJisEncoding.GetBytes(HamCore.HamcoreHeaderData));
			b.WriteInt((uint)this.fileList.Count);
			foreach (HamCoreBuilderFileEntry f in this.fileList)
			{
				b.WriteStr(f.Name, true);
				b.WriteInt(f.RawData.Size);
				b.WriteInt(f.CompressedData.Size);
				b.WriteInt((uint)f.Offset);
			}
			foreach (HamCoreBuilderFileEntry f in this.fileList)
			{
				b.Write(f.CompressedData.ByteData);
			}

			b.SeekToBegin();

			return b;
		}
	}

	public class HamCore
	{
		public const string HamcoreDirName = "@hamcore";
		public const string HamcoreHeaderData = "HamCore";
		public const int HamcoreHeaderSize = 7;
		public const long HamcoreCacheExpires = 5 * 60 * 1000;
		bool disableReadRawFile = false;
		public bool DisableReadRawFile
		{
			get { return disableReadRawFile; }
			set { disableReadRawFile = value; }
		}

		Dictionary<string, HamCoreEntry> list;

		IO hamcore_io;

		public HamCore(string filename)
		{
			init(filename);
		}

		public string[] GetFileNames()
		{
			List<string> ret = new List<string>();

			foreach (HamCoreEntry e in list.Values)
			{
				ret.Add(e.FileName);
			}

			return ret.ToArray();
		}

		void init(string filename)
		{
			filename = IO.InnerFilePath(filename);
			string filenameOnly = Path.GetFileName(filename);
			string filenameAlt = Path.Combine(Path.GetDirectoryName(filename), "_" + filenameOnly);

			try
			{
				IO.FileReplaceRename(filenameAlt, filename);
			}
			catch
			{
			}

			list = new Dictionary<string, HamCoreEntry>();

			try
			{
				hamcore_io = IO.FileOpen(filename);
			}
			catch
			{
				return;
			}

			try
			{
				byte[] header = hamcore_io.Read(HamcoreHeaderSize);
				byte[] header2 = Str.AsciiEncoding.GetBytes(HamcoreHeaderData);
				if (header == null || Util.CompareByte(header, header2) == false)
				{
					throw new SystemException();
				}

				uint num = 0;
				byte[] buf = hamcore_io.Read(Util.SizeOfInt32);
				num = Util.ByteToUInt(buf);
				uint i;
				for (i = 0; i < num; i++)
				{
					uint str_size;

					buf = hamcore_io.Read(Util.SizeOfInt32);
					str_size = Util.ByteToUInt(buf);
					if (str_size >= 1)
					{
						str_size--;
					}

					byte[] str_data = hamcore_io.Read((int)str_size);
					string tmp = Str.ShiftJisEncoding.GetString(str_data);

					HamCoreEntry c = new HamCoreEntry();
					c.FileName = tmp;

					buf = hamcore_io.Read(Util.SizeOfInt32);
					c.Size = Util.ByteToUInt(buf);

					buf = hamcore_io.Read(Util.SizeOfInt32);
					c.SizeCompressed = Util.ByteToUInt(buf);

					buf = hamcore_io.Read(Util.SizeOfInt32);
					c.Offset = Util.ByteToUInt(buf);

					list.Add(c.FileName.ToUpper(), c);
				}
			}
			catch
			{
				hamcore_io.Close();
			}
		}

		public Buf ReadHamcore(string name)
		{
			if (name[0] == '|')
			{
				name = name.Substring(1);
			}
			if (name[0] == '/' || name[0] == '\\')
			{
				name = name.Substring(1);
			}

			string filename = name;

			filename = filename.Replace("/", "\\");

			Buf b;

			if (this.disableReadRawFile == false)
			{
				try
				{
					b = Buf.ReadFromFile(HamcoreDirName + "\\" + filename);

					return b;
				}
				catch
				{
				}
			}

			lock (list)
			{
				HamCoreEntry c;
				string key = filename.ToUpper();

				b = null;

				if (list.ContainsKey(key))
				{
					c = list[key];

					if (c.Buffer != null)
					{
						b = new Buf(c.Buffer);
						b.SeekToBegin();
						c.LastAccess = Time.Tick64;
					}
					else
					{
						if (hamcore_io.Seek(SeekOrigin.Begin, (int)c.Offset))
						{
							byte[] data = hamcore_io.Read((int)c.SizeCompressed);

							int dstSize = (int)c.Size;
							byte[] buffer = ZLib.Uncompress(data, dstSize);

							c.Buffer = buffer;
							b = new Buf(buffer);
							b.SeekToBegin();
							c.LastAccess = Time.Tick64;
						}
					}
				}

				long now = Time.Tick64;
				foreach (HamCoreEntry cc in list.Values)
				{
					if (cc.Buffer != null)
					{
						if (((cc.LastAccess + HamcoreCacheExpires) < now) ||
							cc.FileName.StartsWith("Li", StringComparison.CurrentCultureIgnoreCase))
						{
							cc.Buffer = null;
						}
					}
				}
			}

			return b;
		}
	}

	public class DirEntry : IComparable<DirEntry>
	{
		internal bool folder;
		public bool IsFolder
		{
			get { return folder; }
		}
		internal string fileName;
		public string FileName
		{
			get { return fileName; }
		}
		internal string fullPath;
		public string FullPath
		{
			get { return fullPath; }
		}
		internal string relativePath;
		public string RelativePath
		{
			get { return relativePath; }
		}
		internal long fileSize;
		public long FileSize
		{
			get { return fileSize; }
		}
		internal DateTime createDate;
		public DateTime CreateDate
		{
			get { return createDate; }
		}
		internal DateTime updateDate;
		public DateTime UpdateDate
		{
			get { return updateDate; }
		}

		public int CompareTo(DirEntry other)
		{
			int i;
			i = Str.StrCmpiRetInt(this.fileName, other.fileName);
			if (i == 0)
			{
				i = Str.StrCmpRetInt(this.fileName, other.fileName);
			}

			return i;
		}

		public override string ToString()
		{
			return FileName;
		}
	};

	public class IO
	{
		public delegate bool CopyDirPreCopyDelegate(FileInfo srcFileInfo);
		public static void CopyDir(string srcDirName, string destDirName, CopyDirPreCopyDelegate preCopy, bool ignoreError, bool printStatus)
		{
			CopyDir(srcDirName, destDirName, preCopy, ignoreError, printStatus, false, false, false);
		}
		public static void CopyDir(string srcDirName, string destDirName, CopyDirPreCopyDelegate preCopy, bool ignoreError, bool printStatus,
			bool skipIfNoChange, bool deleteBom)
		{
			CopyDir(srcDirName, destDirName, preCopy, ignoreError, printStatus, skipIfNoChange, deleteBom, false);
		}
		public static void CopyDir(string srcDirName, string destDirName, CopyDirPreCopyDelegate preCopy, bool ignoreError, bool printStatus,
			bool skipIfNoChange, bool deleteBom, bool useTimeStampToCheckNoChange)
		{
			string[] files = Directory.GetFiles(srcDirName, "*", SearchOption.AllDirectories);

			foreach (string srcFile in files)
			{
				FileInfo info = new FileInfo(srcFile);

				string relativeFileName = IO.GetRelativeFileName(srcFile, srcDirName);
				string destFileName = Path.Combine(destDirName, relativeFileName);
				string destFileDirName = Path.GetDirectoryName(destFileName);

				if (preCopy != null)
				{
					if (preCopy(info) == false)
					{
						continue;
					}
				}

				try
				{
					if (Directory.Exists(destFileDirName) == false)
					{
						Directory.CreateDirectory(destFileDirName);
					}

					FileCopy(srcFile, destFileName, skipIfNoChange, deleteBom, useTimeStampToCheckNoChange);
				}
				catch
				{
					if (ignoreError == false)
					{
						throw;
					}
				}

				if (printStatus)
				{
					Con.WriteLine(relativeFileName);
				}
			}
		}

		public const string DefaultHamcoreFileName = "@hamcore.se2";

		static string hamcoreFileName = DefaultHamcoreFileName;
		public static string HamcoreFileName
		{
			get { return IO.hamcoreFileName; }
			set
			{
				lock (hamLockObj)
				{
					if (hamCore != null)
					{
						throw new ApplicationException();
					}

					IO.hamcoreFileName = value;
					tryToUseHamcore = false;
				}
			}
		}

		static bool tryToUseHamcore = true;
		static HamCore hamCore = null;
		static object hamLockObj = new object();
		public static HamCore HamCore
		{
			get
			{
				HamCore ret = null;

				lock (hamLockObj)
				{
					if (hamCore == null)
					{
						if (tryToUseHamcore)
						{
							if (hamCore == null)
							{
								try
								{
									ret = hamCore = new HamCore(hamcoreFileName);
								}
								catch
								{
									tryToUseHamcore = false;
								}
							}
						}
					}
				}

				return ret;
			}
		}

		string name;
		public string Name
		{
			get { return name; }
		}
		FileStream p;
		public FileStream InnerFileStream
		{
			get { return p; }
		}
		bool writeMode;
		public bool WriteMode
		{
			get { return writeMode; }
		}
		bool hamMode;
		public bool HamMode
		{
			get { return hamMode; }
		}
		Buf hamBuf;

		object lockObj;

		private IO()
		{
			name = "";
			p = null;
			writeMode = hamMode = false;
			lockObj = new object();
			hamBuf = null;
		}

		~IO()
		{
			Close();
		}

		public static void WriteAllTextWithEncoding(string fileName, string str, Encoding encoding)
		{
			WriteAllTextWithEncoding(fileName, str, encoding, false);
		}
		public static void WriteAllTextWithEncoding(string fileName, string str, Encoding encoding, bool appendBom)
		{
			fileName = InnerFilePath(fileName);

			byte[] data = encoding.GetBytes(str);
			byte[] bom = null;
			if (appendBom)
			{
				bom = Str.GetBOM(encoding);
			}

			data = Util.CombineByteArray(bom, data);

			File.WriteAllBytes(fileName, data);
		}

		public static string ReadAllTextWithAutoGetEncoding(string fileName)
		{
			fileName = InnerFilePath(fileName);

			byte[] data = File.ReadAllBytes(fileName);

			int bomSize;
			Encoding enc = Str.GetEncoding(data, out bomSize);
			if (enc == null)
			{
				enc = Encoding.Default;
			}

			data = Util.RemoveStartByteArray(data, bomSize);

			return enc.GetString(data);
		}

		public static IO CreateTempFileByExt(string ext)
		{
			return IO.FileCreate(CreateTempFileNameByExt(ext));
		}

		public static string CreateTempFileNameByExt(string ext)
		{
			if (Str.IsEmptyStr(ext))
			{
				ext = "tmp";
			}
			if (ext[0] == '.')
			{
				ext = ext.Substring(1);
			}

			while (true)
			{
				string newFilename;
				string fullPath;
				string randStr;

				randStr = Str.GenRandStr();
				newFilename = "__" + randStr + "." + ext;

				fullPath = CreateTempFileName(newFilename);

				if (IO.IsFileExists(fullPath) == false)
				{
					return fullPath;
				}
			}
		}

		public static IO CreateTempFile(string name)
		{
			return IO.FileCreate(CreateTempFileName(name));
		}

		public static string CreateTempFileName(string name)
		{
			return Path.Combine(Env.MyTempDir, name);
		}

		public static DirEntry[] EnumDirEx(string dirName)
		{
			List<DirEntry> list = new List<DirEntry>();

			enumDirEx(dirName, dirName, list);

			return list.ToArray();
		}
		static void enumDirEx(string dirName, string baseDirName, List<DirEntry> list)
		{
			string tmp = IO.InnerFilePath(dirName);

			string[] dirs = Directory.GetDirectories(tmp);
			foreach (string name in dirs)
			{
				string fullPath = name;
				DirectoryInfo info = new DirectoryInfo(fullPath);

				DirEntry e = new DirEntry();

				e.fileName = Path.GetFileName(name);
				e.fileSize = 0;
				e.createDate = info.CreationTimeUtc;
				e.folder = true;
				e.updateDate = info.LastWriteTimeUtc;
				e.fullPath = fullPath;
				e.relativePath = GetRelativeFileName(fullPath, baseDirName);

				list.Add(e);

				enumDirEx(fullPath, baseDirName, list);
			}

			string[] files = Directory.GetFiles(tmp);
			foreach (string name in files)
			{
				string fullPath = name;
				FileInfo info = new FileInfo(fullPath);

				DirEntry e = new DirEntry();

				e.fileName = Path.GetFileName(name);
				e.fileSize = info.Length;
				e.createDate = info.CreationTimeUtc;
				e.folder = false;
				e.updateDate = info.LastWriteTimeUtc;
				e.fullPath = fullPath;
				e.relativePath = GetRelativeFileName(fullPath, baseDirName);

				list.Add(e);
			}
		}

		public static DirEntry[] EnumDir(string dirName)
		{
			List<DirEntry> list = new List<DirEntry>();
			string tmp = IO.InnerFilePath(dirName);

			string[] dirs = Directory.GetDirectories(tmp);
			foreach (string name in dirs)
			{
				string fullPath = name;
				DirectoryInfo info = new DirectoryInfo(fullPath);

				DirEntry e = new DirEntry();

				e.fileName = Path.GetFileName(name);
				e.fileSize = 0;
				e.createDate = info.CreationTimeUtc;
				e.folder = true;
				e.updateDate = info.LastWriteTimeUtc;
				e.fullPath = fullPath;
				e.relativePath = GetRelativeFileName(fullPath, dirName);

				list.Add(e);
			}

			string[] files = Directory.GetFiles(tmp);
			foreach (string name in files)
			{
				string fullPath = name;
				FileInfo info = new FileInfo(fullPath);

				DirEntry e = new DirEntry();

				e.fileName = Path.GetFileName(name);
				e.fileSize = info.Length;
				e.createDate = info.CreationTimeUtc;
				e.folder = false;
				e.updateDate = info.LastWriteTimeUtc;
				e.fullPath = fullPath;
				e.relativePath = GetRelativeFileName(fullPath, dirName);

				list.Add(e);
			}

			list.Sort();

			return list.ToArray();
		}

		public static void FileReplaceRename(string oldName, string newName)
		{
			try
			{
				FileCopy(oldName, newName);
				FileDelete(oldName);
			}
			catch (Exception e)
			{
				throw e;
			}
		}

		public static void FileCopy(string oldName, string newName)
		{
			FileCopy(oldName, newName, false, false);
		}
		public static void FileCopy(string oldName, string newName, bool skipIfNoChange, bool deleteBom)
		{
			FileCopy(oldName, newName, skipIfNoChange, deleteBom, false);
		}
		public static void FileCopy(string oldName, string newName, bool skipIfNoChange, bool deleteBom, bool useTimeStampToCheckNoChange)
		{
			string tmp1 = InnerFilePath(oldName);
			string tmp2 = InnerFilePath(newName);

			if (useTimeStampToCheckNoChange && skipIfNoChange)
			{
				DateTime dt1, dt2;

				try
				{
					dt1 = Directory.GetLastWriteTimeUtc(tmp1);
					dt2 = Directory.GetLastWriteTimeUtc(tmp2);

					TimeSpan ts = dt2 - dt1;
					if (ts.TotalSeconds >= -5.0)
					{
						return;
					}
				}
				catch
				{
				}
			}

			if (skipIfNoChange || deleteBom)
			{
				byte[] srcData = File.ReadAllBytes(tmp1);
				byte[] destData = new byte[0];
				bool changed = true;
				int bomSize;

				Str.GetEncoding(srcData, out bomSize);
				if (bomSize >= 1)
				{
					srcData = Util.ExtractByteArray(srcData, bomSize, srcData.Length - bomSize);
				}

				if (skipIfNoChange)
				{
					try
					{
						FileStream fs = File.OpenRead(tmp2);
						long size = 0xffffffff;
						try
						{
							size = fs.Length;
						}
						finally
						{
							fs.Close();
						}

						if (size == srcData.Length || srcData.Length == 0)
						{
							destData = File.ReadAllBytes(tmp2);
						}
					}
					catch
					{
					}

					if (Util.CompareByte(srcData, destData))
					{
						changed = false;
					}
				}

				if (changed)
				{
					File.WriteAllBytes(tmp2, srcData);
					CopyFileTimestamp(tmp2, tmp1);
				}
			}
			else
			{
				File.Copy(tmp1, tmp2, true);
			}
		}

		public static void CopyFileTimestamp(string dstFileName, string srcFileName)
		{
			DateTime dt1 = File.GetCreationTimeUtc(srcFileName);
			DateTime dt2 = File.GetLastAccessTimeUtc(srcFileName);
			DateTime dt3 = File.GetLastWriteTimeUtc(srcFileName);

			File.SetCreationTimeUtc(dstFileName, dt1);
			File.SetLastAccessTimeUtc(dstFileName, dt2);
			File.SetLastWriteTimeUtc(dstFileName, dt3);
		}

		public static void SetFileTimestamp(string dstFileName, FileInfo fi)
		{
			File.SetCreationTimeUtc(dstFileName, fi.CreationTimeUtc);
			File.SetLastAccessTimeUtc(dstFileName, fi.LastAccessTimeUtc);
			File.SetLastWriteTimeUtc(dstFileName, fi.LastWriteTimeUtc);
		}

		static public byte[] ReadFile(string name)
		{
			IO io = FileOpen(name);
			try
			{
				int size = io.FileSize;
				byte[] ret = io.Read(size);
				return ret;
			}
			finally
			{
				io.Close();
			}
		}

		static public void SaveFile(string name, byte[] data)
		{
			SaveFile(name, data, 0, data.Length);
		}
		static public void SaveFile(string name, byte[] data, int offset, int size)
		{
			IO io = FileCreate(name);
			try
			{
				io.Write(data, offset, size);
			}
			finally
			{
				io.Close();
			}
		}

		static public string MakeSafeFileName(string src)
		{
			return src
				.Replace("..", "__")
				.Replace("/", "_")
				.Replace("\\", "_")
				.Replace("@", "_")
				.Replace("|", "_");
		}

		public static bool IsDirExists(string name)
		{
			string tmp = InnerFilePath(name);

			return Directory.Exists(tmp);
		}

		public static bool IsFileExists(string name)
		{
			string tmp = InnerFilePath(name);

			return File.Exists(tmp);
		}

		static void fileDeleteInner(string name)
		{
			string name2 = ConvertPath(name);

			File.Delete(name2);
		}
		public static void FileDelete(string name)
		{
			string tmp = InnerFilePath(name);

			fileDeleteInner(tmp);
		}

		public bool Seek(SeekOrigin mode, int offset)
		{
			lock (lockObj)
			{
				if (p != null)
				{
					try
					{
						p.Seek(offset, mode);

						return true;
					}
					catch
					{
						return false;
					}
				}
				else
				{
					return false;
				}
			}
		}

		public long FileSize64
		{
			get
			{
				lock (lockObj)
				{
					if (p != null)
					{
						return p.Length;
					}
					else
					{
						if (hamMode)
						{
							return (long)hamBuf.Size;
						}
					}

					return 0;
				}
			}
		}
		public int FileSize
		{
			get
			{
				long size64 = this.FileSize64;

				if (size64 >= 2147483647)
				{
					size64 = 2147483647;
				}

				return (int)size64;
			}
		}
		public static int GetFileSize(string name)
		{
			IO io = IO.FileOpen(name, false);
			try
			{
				return io.FileSize;
			}
			finally
			{
				io.Close();
			}
		}

		public byte[] ReadAll()
		{
			this.Seek(SeekOrigin.Begin, 0);
			int size = this.FileSize;

			byte[] data = new byte[size];
			this.Read(data, 0, size);

			this.Seek(SeekOrigin.Begin, 0);

			return data;
		}

		public byte[] Read(int size)
		{
			byte[] buf = new byte[size];
			bool ret = Read(buf, size);
			if (ret == false)
			{
				return null;
			}
			return buf;
		}
		public bool Read(byte[] buf, int size)
		{
			return Read(buf, 0, size);
		}
		public bool Read(byte[] buf, int offset, int size)
		{
			if (size == 0)
			{
				return true;
			}

			lock (lockObj)
			{
				if (this.HamMode)
				{
					byte[] ret = hamBuf.Read((uint)size);

					if (ret.Length != size)
					{
						return false;
					}

					Util.CopyByte(buf, offset, ret, 0, size);

					return true;
				}

				if (p != null)
				{
					try
					{
						int ret = p.Read(buf, offset, size);
						if (ret == size)
						{
							return true;
						}
						else
						{
							return false;
						}
					}
					catch
					{
						return false;
					}
				}
				else
				{
					return false;
				}
			}
		}

		public bool Write(byte[] buf)
		{
			return Write(buf, 0, buf.Length);
		}
		public bool Write(byte[] buf, int size)
		{
			return Write(buf, 0, size);
		}
		public bool Write(byte[] buf, int offset, int size)
		{
			if (writeMode == false)
			{
				return false;
			}
			if (size == 0)
			{
				return true;
			}

			lock (lockObj)
			{
				if (p != null)
				{
					try
					{
						p.Write(buf, offset, size);

						return true;
					}
					catch
					{
						return false;
					}
				}
				else
				{
					return false;
				}
			}
		}

		public bool CloseAndDelete()
		{
			string name = this.Name;

			Close();

			try
			{
				FileDelete(name);
				return true;
			}
			catch
			{
				return false;
			}
		}

		public void Close()
		{
			Close(false);
		}
		public void Close(bool noFlush)
		{
			lock (this.lockObj)
			{
				if (this.hamMode == false)
				{
					if (this.p != null)
					{
						if (this.writeMode && noFlush == false)
						{
							Flush();
						}

						this.p.Close();
					}

					this.p = null;
				}
			}
		}

		public void Flush()
		{
			try
			{
				lock (this.lockObj)
				{
					if (this.p != null)
					{
						this.p.Flush();
					}
				}
			}
			catch
			{
			}
		}

		static IO fileCreateInner(string name)
		{
			IO o = new IO();

			string name2 = ConvertPath(name);

			lock (o.lockObj)
			{
				o.p = File.Open(name2, FileMode.Create, FileAccess.ReadWrite, FileShare.Read);
				o.name = name2;
				o.writeMode = true;
			}

			return o;
		}

		public static IO FileCreate(string name)
		{
			name = InnerFilePath(name);

			return fileCreateInner(name);
		}

		static IO fileOpenInner(string name, bool writeMode, bool readLock)
		{
			IO o = new IO();

			string name2 = ConvertPath(name);

			lock (o.lockObj)
			{
				o.p = File.Open(name2, FileMode.Open, (writeMode ? FileAccess.ReadWrite : FileAccess.Read),
					(readLock ? FileShare.None : FileShare.Read));

				o.name = name2;
				o.writeMode = writeMode;
			}

			return o;
		}

		public static IO FileOpen(string name)
		{
			return FileOpen(name, false);
		}
		public static IO FileOpen(string name, bool writeMode)
		{
			return FileOpen(name, writeMode, false);
		}
		public static IO FileOpen(string name, bool writeMode, bool readLock)
		{
			name = InnerFilePath(name);

			if (name[0] == '|')
			{
				HamCore hc = IO.HamCore;

				Buf b = hc.ReadHamcore(name);
				if (b == null)
				{
					throw new FileNotFoundException();
				}

				IO o = new IO();
				o.name = name.Substring(1);
				o.hamMode = true;
				o.hamBuf = b;

				return o;
			}
			else
			{
				return fileOpenInner(name, writeMode, readLock);
			}
		}

		public static IO FileCreateOrAppendOpen(string name)
		{
			if (IsFileExists(name))
			{
				IO io = FileOpen(name, true);
				io.Seek(SeekOrigin.End, 0);
				return io;
			}
			else
			{
				return FileCreate(name);
			}
		}

		public static string GetRelativeFileName(string fileName, string baseDirName)
		{
			baseDirName = RemoteLastEnMark(baseDirName).Trim() + "\\";
			fileName = fileName.Trim();

			if (fileName.Length <= baseDirName.Length)
			{
				throw new ArgumentException("fileName, baseDirName");
			}

			if (fileName.StartsWith(baseDirName, StringComparison.InvariantCultureIgnoreCase) == false)
			{
				throw new ArgumentException("fileName, baseDirName");
			}

			return fileName.Substring(baseDirName.Length);
		}

		public static string RemoteLastEnMark(string path)
		{
			if (path == null)
			{
				path = "";
			}
			if (path.EndsWith("\\"))
			{
				path = path.Substring(0, path.Length - 1);
			}
			return path;
		}

		public static void FileRename(string oldName, string newName)
		{
			string tmp1 = InnerFilePath(oldName);
			string tmp2 = InnerFilePath(newName);

			File.Move(tmp1, tmp2);
		}

		public static void DeleteFilesAndSubDirsInDir(string dirName)
		{
			dirName = InnerFilePath(dirName);

			if (Directory.Exists(dirName) == false)
			{
				Directory.CreateDirectory(dirName);
				return;
			}

			string[] files = Directory.GetFiles(dirName);
			string[] dirs = Directory.GetDirectories(dirName);

			foreach (string file in files)
			{
				File.SetAttributes(file, FileAttributes.Normal);
				File.Delete(file);
			}

			foreach (string dir in dirs)
			{
				Directory.Delete(dir, true);
			}
		}

		public static bool DeleteDir(string dirName)
		{
			return DeleteDir(dirName, false);
		}
		public static bool DeleteDir(string dirName, bool deleteSubDirs)
		{
			try
			{
				Directory.Delete(InnerFilePath(dirName), deleteSubDirs);
				return true;
			}
			catch
			{
				return false;
			}
		}

		public static bool MakeDir(string dirName)
		{
			try
			{
				Directory.CreateDirectory(InnerFilePath(dirName));
				return true;
			}
			catch
			{
				return false;
			}
		}
		public static bool MakeDirIfNotExists(string dirName)
		{
			string path = InnerFilePath(dirName);

			if (Directory.Exists(path) == false)
			{
				Directory.CreateDirectory(path);

				return true;
			}

			return false;
		}

		public static string NormalizePath(string src)
		{
			bool first_double_slash = false;
			bool first_single_slash = false;
			string win32_drive_char = "";
			int i;
			string tmp;

			tmp = ConvertPath(src).Trim();

			if (tmp.StartsWith(".\\") || tmp.StartsWith("..\\") || tmp.StartsWith(".") || tmp.StartsWith(".."))
			{
				if (tmp.StartsWith(".."))
				{
					tmp = Env.CurrentDir + "/../" + tmp.Substring(2);
				}
				else
				{
					tmp = Env.CurrentDir + "/" + tmp;
				}
			}

			if (tmp.StartsWith("~/") || tmp.StartsWith("~\\"))
			{
				tmp = Env.HomeDir + "/" + tmp.Substring(2);
			}

			if (tmp.StartsWith("//") || tmp.StartsWith("\\\\"))
			{
				first_double_slash = true;
			}
			else
			{
				if (tmp.StartsWith("/") || tmp.StartsWith("\\"))
				{
					first_single_slash = true;
				}
			}

			if (tmp.Length >= 2)
			{
				if (tmp[1] == ':')
				{
					win32_drive_char = "" + tmp[0];
					tmp = tmp.Substring(2);
				}
			}

			if (tmp == "/" || tmp == "\\")
			{
				tmp = "";
			}

			char[] splitChars = { '/', '\\' };
			string[] t = tmp.Split(splitChars, StringSplitOptions.RemoveEmptyEntries);

			Stack<string> sk = new Stack<string>();

			for (i = 0; i < t.Length; i++)
			{
				string s = t[i];

				if (Str.StrCmpi(s, "."))
				{
					continue;
				}
				else if (Str.StrCmpi(s, ".."))
				{
					if (sk.Count >= 1 && (first_double_slash == false || sk.Count >= 2))
					{
						sk.Pop();
					}
				}
				else
				{
					sk.Push(s);
				}
			}

			tmp = "";

			if (first_double_slash)
			{
				tmp += "//";
			}
			else if (first_single_slash)
			{
				tmp += "/";
			}

			if (Str.IsEmptyStr(win32_drive_char) == false)
			{
				tmp = win32_drive_char + ":/" + tmp;
			}

			string[] sks = sk.ToArray();
			Array.Reverse(sks);
			for (i = 0; i < sks.Length; i++)
			{
				tmp += sks[i];
				if (i != (sks.Length - 1))
				{
					tmp += "/";
				}
			}

			tmp = ConvertPath(tmp);

			return tmp;
		}

		public static string ConvertPath(string path)
		{
			return path.Replace('/', '\\');
		}

		public static string ConbinePath(string dirname, string filename)
		{
			return CombinePath(dirname, filename);
		}
		public static string CombinePath(string dirname, string filename)
		{
			bool is_full_path;
			string filename_ident = NormalizePath(filename);

			is_full_path = false;

			if (filename_ident.StartsWith("\\") || filename_ident.StartsWith("/"))
			{
				is_full_path = true;
			}

			filename = filename_ident;

			if (filename.Length >= 2)
			{
				char c = filename[0];
				if (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))
				{
					if (filename[1] == ':')
					{
						is_full_path = true;
					}
				}
			}

			string tmp;

			if (is_full_path == false)
			{
				tmp = dirname;
				if (tmp.EndsWith("/") == false && tmp.EndsWith("\\") == false)
				{
					tmp += "/";
				}

				tmp += filename;
			}
			else
			{
				tmp = filename;
			}

			return NormalizePath(tmp);
		}

		public static string InnerFilePath(string src)
		{
			if (src[0] != '@')
			{
				return NormalizePath(src);
			}
			else
			{
				return CombinePath(Env.ExeFileDir, src.Substring(1));
			}
		}

		public static DateTime GetCreationTimeUtc(string filename)
		{
			return File.GetCreationTimeUtc(InnerFilePath(filename));
		}
		public static DateTime GetCreationTimeLocal(string filename)
		{
			return File.GetCreationTime(InnerFilePath(filename));
		}

		public static DateTime GetLastWriteTimeUtc(string filename)
		{
			return File.GetLastWriteTimeUtc(InnerFilePath(filename));
		}
		public static DateTime GetLastWriteTimeLocal(string filename)
		{
			return File.GetLastWriteTime(InnerFilePath(filename));
		}

		public static DateTime GetLastAccessTimeUtc(string filename)
		{
			return File.GetLastAccessTimeUtc(InnerFilePath(filename));
		}
		public static DateTime GetLastAccessTimeLocal(string filename)
		{
			return File.GetLastAccessTime(InnerFilePath(filename));
		}

		public static byte[] ReadFileData(string filename)
		{
			FileStream fs = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
			try
			{
				long size = fs.Length;
				int size2 = (int)Math.Min(size, int.MaxValue);
				byte[] ret = new byte[size2];
				fs.Read(ret, 0, size2);
				return ret;
			}
			finally
			{
				fs.Close();
			}
		}
	}
}
