diff --git a/GameRes/ArcFile.cs b/GameRes/ArcFile.cs index 59633b79..73064e6d 100644 --- a/GameRes/ArcFile.cs +++ b/GameRes/ArcFile.cs @@ -143,6 +143,16 @@ namespace GameRes return m_interface.OpenEntry (this, entry); } + /// + /// Open specified as memory-mapped view. + /// + public ArcView OpenView (Entry entry) + { + var stream = OpenEntry (entry); + uint size = stream.CanSeek ? (uint)stream.Length : entry.Size; + return new ArcView (stream, entry.Name, size); + } + /// /// Open specified as a seekable Stream. /// @@ -168,6 +178,14 @@ namespace GameRes return ArchiveFormat.CreateFile (entry.Name); } + public IFileSystem CreateFileSystem () + { + if (m_interface.IsHierarchic) + return new ArchiveFileSystem (this); + else + return new FlatArchiveFileSystem (this); + } + #region IDisposable Members bool disposed = false; diff --git a/GameRes/FileSystem.cs b/GameRes/FileSystem.cs new file mode 100644 index 00000000..5bbb806a --- /dev/null +++ b/GameRes/FileSystem.cs @@ -0,0 +1,303 @@ +//! \file FileSystem.cs +//! \date Fri Jun 05 15:32:27 2015 +//! \brief Gameres file system abstraction. +// +// Copyright (C) 2015 by morkt +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// 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. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace GameRes +{ + public interface IFileSystem : IDisposable + { + /// + /// Open file for reading. + /// + Stream OpenStream (Entry entry); + + ArcView OpenView (Entry entry); + + /// + /// Enumerates subdirectories and files in current directory. + /// + IEnumerable GetFiles (); + + /// + /// Recursively enumerates files in the current directory and its subdirectories. + /// Subdirectory entries are omitted. + /// + IEnumerable GetFilesRecursive (); + + string CurrentDirectory { get; set; } + } + + public class SubDirEntry : Entry + { + public override string Type { get { return "directory"; } } + + public SubDirEntry (string name) + { + Name = name; + Size = 0; + } + } + + public class PhysicalFileSystem : IFileSystem + { + public string CurrentDirectory + { + get { return Directory.GetCurrentDirectory(); } + set { Directory.SetCurrentDirectory (value); } + } + + public IEnumerable GetFiles () + { + var info = new DirectoryInfo (CurrentDirectory); + foreach (var subdir in info.EnumerateDirectories()) + { + if (0 != (subdir.Attributes & (FileAttributes.Hidden | FileAttributes.System))) + continue; + yield return new SubDirEntry (subdir.FullName); + } + foreach (var file in info.EnumerateFiles()) + { + if (0 != (file.Attributes & (FileAttributes.Hidden | FileAttributes.System))) + continue; + yield return EntryFromFileInfo (file); + } + } + + public IEnumerable GetFilesRecursive () + { + var info = new DirectoryInfo (CurrentDirectory); + foreach (var file in info.EnumerateFiles ("*", SearchOption.AllDirectories)) + { + if (0 != (file.Attributes & (FileAttributes.Hidden | FileAttributes.System))) + continue; + yield return EntryFromFileInfo (file); + } + } + + private Entry EntryFromFileInfo (FileInfo file) + { + var entry = FormatCatalog.Instance.CreateEntry (file.FullName); + entry.Size = (uint)Math.Min (file.Length, uint.MaxValue); + return entry; + } + + public Stream OpenStream (Entry entry) + { + return File.OpenRead (entry.Name); + } + + public ArcView OpenView (Entry entry) + { + return new ArcView (entry.Name); + } + + public void Dispose () + { + GC.SuppressFinalize (this); + } + } + + public class FlatArchiveFileSystem : IFileSystem + { + protected ArcFile m_arc; + + public virtual string CurrentDirectory + { + get { return ""; } + set + { + if (string.IsNullOrEmpty (value)) + return; + if (".." == value || "." == value) + return; + if ("\\" == value || "/" == value) + return; + throw new DirectoryNotFoundException(); + } + } + + public FlatArchiveFileSystem (ArcFile arc) + { + m_arc = arc; + } + + public Stream OpenStream (Entry entry) + { + return m_arc.OpenEntry (entry); + } + + public ArcView OpenView (Entry entry) + { + return m_arc.OpenView (entry); + } + + public virtual IEnumerable GetFiles () + { + return m_arc.Dir; + } + + public virtual IEnumerable GetFilesRecursive () + { + return m_arc.Dir; + } + + #region IDisposable Members + bool _arc_disposed = false; + + public void Dispose () + { + Dispose (true); + GC.SuppressFinalize (this); + } + + protected virtual void Dispose (bool disposing) + { + if (!_arc_disposed) + { + if (disposing) + { + m_arc.Dispose(); + } + _arc_disposed = true; + } + } + #endregion + } + + public class ArchiveFileSystem : FlatArchiveFileSystem + { + private string m_cwd; + + private string PathDelimiter { get; set; } + + private static readonly char[] m_path_delimiters = { '/', '\\' }; + + public ArchiveFileSystem (ArcFile arc) : base (arc) + { + m_cwd = ""; + PathDelimiter = "/"; + } + + public override string CurrentDirectory + { + get { return m_cwd; } + set { ChDir (value); } + } + + static readonly Regex path_re = new Regex (@"\G[/\\]?([^/\\]+)([/\\])"); + + public override IEnumerable GetFiles () + { + var root_dir = ""; + IEnumerable dir = m_arc.Dir; + if (!string.IsNullOrEmpty (m_cwd)) + { + root_dir = m_cwd+PathDelimiter; + dir = from entry in dir + where entry.Name.StartsWith (root_dir) + select entry; + if (!dir.Any()) + { + throw new DirectoryNotFoundException(); + } + } + var subdirs = new HashSet(); + foreach (var entry in dir) + { + var match = path_re.Match (entry.Name, root_dir.Length); + if (match.Success) + { + string name = match.Groups[1].Value; + if (subdirs.Add (name)) + { + PathDelimiter = match.Groups[2].Value; + yield return new SubDirEntry (root_dir+name); + } + } + else + { + yield return entry; + } + } + } + + public override IEnumerable GetFilesRecursive () + { + if (0 == m_cwd.Length) + return m_arc.Dir; + else + return from file in m_arc.Dir + where file.Name.StartsWith (m_cwd + PathDelimiter) + select file; + } + + private void ChDir (string path) + { + if (string.IsNullOrEmpty (path)) + return; + List cur_dir; + if (-1 != Array.IndexOf (m_path_delimiters, path[0])) + { + path = path.TrimStart (m_path_delimiters); + cur_dir = new List(); + } + else + { + cur_dir = m_cwd.Split (m_path_delimiters).ToList(); + } + var path_list = path.Split (m_path_delimiters); + foreach (var dir in path_list) + { + if ("." == dir) + { + continue; + } + else if (".." == dir) + { + if (0 == cur_dir.Count) + continue; + cur_dir.RemoveAt (cur_dir.Count-1); + } + else + { + cur_dir.Add (dir); + } + } + string new_path = string.Join (PathDelimiter, cur_dir); + if (0 != new_path.Length) + { + var entry = m_arc.Dir.FirstOrDefault (e => e.Name.StartsWith (new_path + PathDelimiter)); + if (null == entry) + throw new DirectoryNotFoundException(); + } + m_cwd = new_path; + } + } +} diff --git a/GameRes/GameRes.csproj b/GameRes/GameRes.csproj index 6a9b0b26..59cbe9d2 100644 --- a/GameRes/GameRes.csproj +++ b/GameRes/GameRes.csproj @@ -57,6 +57,7 @@ + diff --git a/GameRes/Properties/AssemblyInfo.cs b/GameRes/Properties/AssemblyInfo.cs index b27ba2fd..4d365e92 100644 --- a/GameRes/Properties/AssemblyInfo.cs +++ b/GameRes/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion ("1.0.5.6")] -[assembly: AssemblyFileVersion ("1.0.5.6")] +[assembly: AssemblyVersion ("1.1.0.7")] +[assembly: AssemblyFileVersion ("1.1.0.7")]