using System; using System.Collections.Generic; using System.Text; using System.IO; using MPQ.Updater; using CommonLang.IO; using System.Text.RegularExpressions; namespace MPQ.FileSystem { public class MPQFileSystem : IDisposable { private Dictionary> indexer = new Dictionary>(); private Dictionary mpq_files = new Dictionary(); /// /// 用于老文件存在,新目录已没有的文件 /// private Dictionary dir = new Dictionary(); public MPQFileSystem() { } /// /// 搜索并加载目录里的所有MPQ文件 /// /// /// public bool init(DirectoryInfo mpq_dir) { if (mpq_dir.Exists) { loadDir(new FileInfo(mpq_dir.FullName + Path.DirectorySeparatorChar + ".dir")); try { foreach (FileInfo file in mpq_dir.GetFiles()) { if (file.Extension.ToLower().EndsWith(MPQ.Updater.MPQUpdater.MPQ_EXT)) { if (load(file.FullName) == null) { return false; } } } foreach (DirectoryInfo sub_dir in mpq_dir.GetDirectories()) { if (!init(sub_dir)) { return false; } } } finally { dir.Clear(); } return true; } return false; } /// /// 将自动更新的MPQ加载到文件系统 /// /// /// public bool init(MPQUpdater updater) { loadDir(new FileInfo(updater.LocalSaveRoot.FullName + Path.DirectorySeparatorChar + ".dir")); try { foreach (FileInfo rmf in updater.GetAllFiles()) { if (rmf.FullName.ToLower().EndsWith(MPQUpdater.MPQ_EXT)) { if (load(rmf.FullName) == null) { return false; } } } } finally { dir.Clear(); } return true; } private void loadDir(FileInfo dir_file) { if (dir_file.Exists) { string[] lines = File.ReadAllLines(dir_file.FullName); foreach (var line in lines) { dir.Add(line.Trim(), line); } } } /// /// 加载单个MPQ文件到文件系统 /// /// /// public MPQStream load(string path) { FileInfo fileinfo = new FileInfo(Path.GetFullPath(path)); //Console.WriteLine("MPQFile::Load : " + fileinfo.FullName); List entries = new List(1000); MPQStream mpq_stream = new MPQStream(fileinfo); if (mpq_stream.loadEntrys(entries)) { mpq_files[fileinfo.FullName] = mpq_stream; foreach (MPQFileEntry e in entries) { if (dir.Count == 0 || dir.ContainsKey(e.Key)) { putEntry(e); } else { Console.WriteLine(string.Format("MPQFileSystem : ignore entry \"{0}\" not exist in .dir file!!!", e.Key)); } } return mpq_stream; } else { mpq_stream.Dispose(); throw new Exception("Cannot Init MPQ file : " + path); } } public void Dispose() { indexer.Clear(); foreach (MPQStream path in mpq_files.Values) { path.Dispose(); } mpq_files.Clear(); } virtual protected long hashCode(string data) { return data.GetHashCode(); } private bool putEntry(MPQFileEntry re) { re.hash = hashCode(re.key); Dictionary ets = null; // 首个HASH if (!indexer.TryGetValue(re.hash, out ets)) { ets = new Dictionary(1); ets[re.key] = re; indexer[re.hash] = ets; return true; } // 首个文件 MPQFileEntry exist = null; if (!ets.TryGetValue(re.key, out exist)) { ets[re.key] = re; return true; } // 如果当前文件较新,则更新 if (exist.f_date < re.f_date) { ets[re.key] = re; return true; } // 当前文件较老,忽略 return false; } private MPQFileEntry findEntry(long hash, String name) { Dictionary ets = null; if (indexer.TryGetValue(hash, out ets)) { MPQFileEntry ret = null; if (ets.TryGetValue(name, out ret)) { return ret; } } return null; } public MPQFileEntry findEntry(string path) { long hash = hashCode(path); return findEntry(hash, path); } public byte[] getEntryData(MPQFileEntry e) { if (e != null) { byte[] data = new byte[e.f_size]; e.fs.Read(e, 0, data, 0, data.Length); return data; } return null; } public byte[] getData(String name) { long hash = hashCode(name); MPQFileEntry e = findEntry(hash, name); byte[] ed = getEntryData(e); return ed; } public Stream openEntryStream(MPQFileEntry e) { if (e != null) { return new EntryStream(e); } return null; } public Stream openStream(String name) { long hash = hashCode(name); MPQFileEntry e = findEntry(hash, name); Stream ed = openEntryStream(e); return ed; } public List listEntrys() { List ret = new List(); foreach (Dictionary fe in indexer.Values) { foreach (MPQFileEntry e in fe.Values) { ret.Add(e); } } return ret; } public List listEntrys(string pettern) { Regex regex = new Regex(pettern); List ret = new List(); foreach (Dictionary fe in indexer.Values) { foreach (MPQFileEntry e in fe.Values) { if (regex.IsMatch(e.key)) { ret.Add(e); } } } return ret; } //---------------------------------------------------------------------------------------------------------------------- public class MPQStream : IDisposable { public static byte[] FS_HEAD_START = { (byte)'M', (byte)'F', (byte)'F', (byte)'S' }; public static byte[] FS_ENTRY_START = { (byte)'M', (byte)'F', (byte)'E', (byte)'T' }; public static byte[] FS_TRUNK_START = { (byte)'M', (byte)'F', (byte)'T', (byte)'K' }; public static byte[] FS_END = { (byte)'M', (byte)'F', (byte)'E', (byte)'D' }; public static byte[] VERSION = { 0, 0, 0, 1 }; private FileInfo info; private FileStream fis; private long trunk_start; public long TrunkStart { get { return trunk_start; } } public FileInfo MPQFile { get { return info; } } public MPQStream(FileInfo file) { this.info = file; this.fis = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read); } private bool headEquals(byte[] a, byte[] b) { for (int i = 0; i < a.Length; i++) { if (a[i] != b[i]) { return false; } } return true; } // public List ListEntrys() // { // List ret = new List(); // if (loadEntrys(ret)) // { // } // return ret; // } public bool loadEntrys(List entries) { lock (fis) { fis.Position = 0; BinaryReader bis = new BinaryReader(fis, Encoding.UTF8); byte[] head_trunk = IOUtil.ReadExpect(fis, MPQStream.FS_HEAD_START.Length); // head if (headEquals(head_trunk, MPQStream.FS_HEAD_START)) { head_trunk = IOUtil.ReadExpect(fis, MPQStream.VERSION.Length);// version long total_size = bis.ReadInt64(); head_trunk = IOUtil.ReadExpect(fis, MPQStream.FS_ENTRY_START.Length);// entry start if (headEquals(head_trunk, MPQStream.FS_ENTRY_START)) { int entry_count = bis.ReadInt32(); for (int i = 0; i < entry_count; i++) { MPQFileEntry re = new MPQFileEntry(); re.fs = this; re.load(bis); entries.Add(re); } head_trunk = IOUtil.ReadExpect(fis, MPQStream.FS_TRUNK_START.Length);// trunk start if (headEquals(head_trunk, MPQStream.FS_TRUNK_START)) { // record file trunk start this.trunk_start = fis.Position; return true; } } } } return false; } public void Dispose() { try { lock (fis) { fis.Close(); fis.Dispose(); } } catch (Exception err) { Console.WriteLine(err.Message + "\n" + err.StackTrace); } } public int Read(MPQFileEntry src, long src_pos, byte[] dst, int dst_pos, int length) { if (src.fs == this) { lock (fis) { try { fis.Position = trunk_start + src.f_start + src_pos; IOUtil.ReadToEnd(fis, dst, dst_pos, length); } catch (Exception err) { throw new Exception("MPQStream read error : " + err.Message, err); } } return length; } throw new Exception("MPQStream read error"); } } public class MPQFileEntry { internal static DateTime JAVA_START_DATE = new DateTime(1970, 1, 1, 0, 0, 0); internal long hash; // 文件名HASH //internal int index; // HASH对应所在Entry位置 internal String key; // 文件名 //internal int key_size; // 文件名长度 //internal String key_md5; // 文件名MD5 internal int f_start; // 文件内容开始位置 internal int f_size; // 文件内容尺寸 internal long f_date; // 文件日期(1970-1-1起始秒) //internal String f_md5; // 文件内容MD5 internal MPQFileSystem.MPQStream fs; public string Key { get { return key; } } public int Size { get { return f_size; } } public DateTime Date { get { return JAVA_START_DATE.AddSeconds(f_date); } } public override string ToString() { return key + "(" + f_size + ")"; } public byte[] getFileData() { byte[] data = new byte[this.f_size]; this.fs.Read(this, 0, data, 0, data.Length); return data; } internal void load(BinaryReader bis) { /* * hash = LittleIODeserialize.getLong (is); * index = LittleIODeserialize.getInt (is); * key = LittleIODeserialize.getString (is, "UTF-8"); * key_size = LittleIODeserialize.getInt (is); * key_md5 = LittleIODeserialize.getString (is, "UTF-8"); * f_start = LittleIODeserialize.getInt (is); * f_size = LittleIODeserialize.getInt (is); * f_date = LittleIODeserialize.getLong (is); * f_md5 = LittleIODeserialize.getString (is, "UTF-8"); */ this.hash = bis.ReadInt64(); bis.ReadInt32(); this.key = readUTF(bis); bis.ReadInt32(); readUTF(bis); this.f_start = bis.ReadInt32(); this.f_size = bis.ReadInt32(); this.f_date = bis.ReadInt64(); readUTF(bis); } private static string readUTF(BinaryReader bis) { int len = bis.ReadUInt16(); byte[] bytes = new byte[len]; int readed = bis.Read(bytes, 0, len); while (readed < len) { readed += bis.Read(bytes, readed, len - readed); } return Encoding.UTF8.GetString(bytes, 0, len); } public bool Equals(MPQFileEntry b) { if (!b.key.Equals(this.key)) return false; if (!b.f_size.Equals(this.f_size)) return false; if (!b.f_date.Equals(this.f_date)) return false; if (!b.f_start.Equals(this.f_start)) return false; return true; } } /// /// 外部读取用流 /// internal class EntryStream : Stream { private long pos = 0; private MPQFileEntry e; public EntryStream(MPQFileEntry entry) { this.e = entry; } public override long Position { get { return pos; } set { pos = value; } } public override long Length { get { return e.Size; } } public override bool CanRead { get { return true; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return false; } } public override int Read(byte[] buffer, int offset, int count) { long avaliable = e.Size - pos; if (avaliable > 0) { count = (int)Math.Min(avaliable, count); int readed = e.fs.Read(e, pos, buffer, offset, count); pos += readed; return readed; } else if (avaliable == 0) { return 0; } throw new IOException("EOF of MPQEntry"); } public override long Seek(long offset, SeekOrigin origin) { throw new NotImplementedException(); } public override void SetLength(long value) { throw new NotImplementedException(); } public override void Write(byte[] buffer, int offset, int count) { throw new NotImplementedException(); } public override void Flush() { } } } }