mirror of
https://github.com/crskycode/GARbro.git
synced 2026-06-18 17:04:32 +08:00
virtual file system preliminary implementation.
This commit is contained in:
@@ -49,8 +49,6 @@ namespace GameRes
|
||||
/// <summary>Archive contents.</summary>
|
||||
public ICollection<Entry> Dir { get { return m_dir; } }
|
||||
|
||||
public event EventHandler<OverwriteEventArgs> OverwriteNotify;
|
||||
|
||||
public ArcFile (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir)
|
||||
{
|
||||
m_arc = arc;
|
||||
@@ -66,11 +64,11 @@ namespace GameRes
|
||||
/// </returns>
|
||||
public static ArcFile TryOpen (string filename)
|
||||
{
|
||||
var info = new FileInfo (filename);
|
||||
if (info.Length < 4)
|
||||
var entry = VFS.FindFile (filename);
|
||||
if (entry.Size < 4)
|
||||
return null;
|
||||
var ext = new Lazy<string> (() => Path.GetExtension (filename).TrimStart ('.').ToLowerInvariant());
|
||||
var file = new ArcView (filename);
|
||||
var file = VFS.OpenView (entry);
|
||||
try
|
||||
{
|
||||
uint signature = file.View.ReadUInt32 (0);
|
||||
@@ -191,15 +189,6 @@ namespace GameRes
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create file corresponding to <paramref name="entry"/> within current directory and open
|
||||
/// it for writing.
|
||||
/// </summary>
|
||||
public Stream CreateFile (Entry entry)
|
||||
{
|
||||
return ArchiveFormat.CreateFile (entry.Name);
|
||||
}
|
||||
|
||||
public IFileSystem CreateFileSystem ()
|
||||
{
|
||||
if (m_interface.IsHierarchic)
|
||||
@@ -229,98 +218,4 @@ namespace GameRes
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class OverwriteEventArgs : EventArgs
|
||||
{
|
||||
public string Filename { get; set; }
|
||||
public bool Overwrite { get; set; }
|
||||
}
|
||||
|
||||
public class AppendStream : System.IO.Stream
|
||||
{
|
||||
private Stream m_base;
|
||||
private long m_start_pos;
|
||||
|
||||
public override bool CanRead { get { return true; } }
|
||||
public override bool CanSeek { get { return true; } }
|
||||
public override bool CanWrite { get { return true; } }
|
||||
public override long Length { get { return m_base.Length - m_start_pos; } }
|
||||
public override long Position
|
||||
{
|
||||
get { return m_base.Position - m_start_pos; }
|
||||
set { m_base.Position = Math.Max (m_start_pos+value, m_start_pos); }
|
||||
}
|
||||
|
||||
public AppendStream (System.IO.Stream file)
|
||||
{
|
||||
m_base = file;
|
||||
m_start_pos = m_base.Seek (0, SeekOrigin.End);
|
||||
}
|
||||
|
||||
public AppendStream (System.IO.Stream file, long offset)
|
||||
{
|
||||
m_base = file;
|
||||
m_start_pos = m_base.Seek (offset, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
public Stream BaseStream { get { return m_base; } }
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
m_base.Flush();
|
||||
}
|
||||
|
||||
public override long Seek (long offset, SeekOrigin origin)
|
||||
{
|
||||
if (SeekOrigin.Begin == origin)
|
||||
{
|
||||
offset = Math.Max (offset + m_start_pos, m_start_pos);
|
||||
}
|
||||
long position = m_base.Seek (offset, origin);
|
||||
if (position < m_start_pos)
|
||||
{
|
||||
m_base.Seek (m_start_pos, SeekOrigin.Begin);
|
||||
position = m_start_pos;
|
||||
}
|
||||
return position - m_start_pos;
|
||||
}
|
||||
|
||||
public override void SetLength (long length)
|
||||
{
|
||||
if (length < 0)
|
||||
length = 0;
|
||||
m_base.SetLength (length + m_start_pos);
|
||||
}
|
||||
|
||||
public override int Read (byte[] buffer, int offset, int count)
|
||||
{
|
||||
return m_base.Read (buffer, offset, count);
|
||||
}
|
||||
|
||||
public override int ReadByte ()
|
||||
{
|
||||
return m_base.ReadByte();
|
||||
}
|
||||
|
||||
public override void Write (byte[] buffer, int offset, int count)
|
||||
{
|
||||
m_base.Write (buffer, offset, count);
|
||||
}
|
||||
|
||||
public override void WriteByte (byte value)
|
||||
{
|
||||
m_base.WriteByte (value);
|
||||
}
|
||||
|
||||
bool disposed = false;
|
||||
protected override void Dispose (bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
m_base = null;
|
||||
disposed = true;
|
||||
base.Dispose (disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,16 +28,28 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using GameRes.Strings;
|
||||
|
||||
namespace GameRes
|
||||
{
|
||||
public interface IFileSystem : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Open file for reading.
|
||||
/// Returns entry corresponding to the given filename within filesystem.
|
||||
/// </summary>
|
||||
/// <exception cref="FileNotFoundException">File is not found.</exception>
|
||||
Entry FindFile (string filename);
|
||||
|
||||
/// <summary>
|
||||
/// Open file for reading as stream.
|
||||
/// </summary>
|
||||
Stream OpenStream (Entry entry);
|
||||
|
||||
Stream OpenSeekableStream (Entry entry);
|
||||
|
||||
/// <summary>
|
||||
/// Open file for reading as memory-mapped view.
|
||||
/// </summary>
|
||||
ArcView OpenView (Entry entry);
|
||||
|
||||
/// <summary>
|
||||
@@ -47,7 +59,7 @@ namespace GameRes
|
||||
|
||||
/// <summary>
|
||||
/// Recursively enumerates files in the current directory and its subdirectories.
|
||||
/// Subdirectory entries are omitted.
|
||||
/// Subdirectory entries are omitted from resulting set.
|
||||
/// </summary>
|
||||
IEnumerable<Entry> GetFilesRecursive ();
|
||||
|
||||
@@ -73,6 +85,15 @@ namespace GameRes
|
||||
set { Directory.SetCurrentDirectory (value); }
|
||||
}
|
||||
|
||||
public Entry FindFile (string filename)
|
||||
{
|
||||
var attr = File.GetAttributes (filename);
|
||||
if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
return new SubDirEntry (filename);
|
||||
else
|
||||
return EntryFromFileInfo (new FileInfo (filename));
|
||||
}
|
||||
|
||||
public IEnumerable<Entry> GetFiles ()
|
||||
{
|
||||
var info = new DirectoryInfo (CurrentDirectory);
|
||||
@@ -84,7 +105,7 @@ namespace GameRes
|
||||
}
|
||||
foreach (var file in info.EnumerateFiles())
|
||||
{
|
||||
if (0 != (file.Attributes & (FileAttributes.Hidden | FileAttributes.System)))
|
||||
if (0 != (file.Attributes & FileAttributes.System))
|
||||
continue;
|
||||
yield return EntryFromFileInfo (file);
|
||||
}
|
||||
@@ -113,6 +134,11 @@ namespace GameRes
|
||||
return File.OpenRead (entry.Name);
|
||||
}
|
||||
|
||||
public Stream OpenSeekableStream (Entry entry)
|
||||
{
|
||||
return OpenStream (entry);
|
||||
}
|
||||
|
||||
public ArcView OpenView (Entry entry)
|
||||
{
|
||||
return new ArcView (entry.Name);
|
||||
@@ -126,7 +152,10 @@ namespace GameRes
|
||||
|
||||
public class FlatArchiveFileSystem : IFileSystem
|
||||
{
|
||||
protected ArcFile m_arc;
|
||||
protected readonly ArcFile m_arc;
|
||||
protected readonly Dictionary<string, Entry> m_dir;
|
||||
|
||||
public ArcFile Source { get { return m_arc; } }
|
||||
|
||||
public virtual string CurrentDirectory
|
||||
{
|
||||
@@ -146,6 +175,11 @@ namespace GameRes
|
||||
public FlatArchiveFileSystem (ArcFile arc)
|
||||
{
|
||||
m_arc = arc;
|
||||
m_dir = new Dictionary<string, Entry> (arc.Dir.Count, StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach (var entry in arc.Dir)
|
||||
{
|
||||
m_dir.Add (entry.Name, entry);
|
||||
}
|
||||
}
|
||||
|
||||
public Stream OpenStream (Entry entry)
|
||||
@@ -153,11 +187,24 @@ namespace GameRes
|
||||
return m_arc.OpenEntry (entry);
|
||||
}
|
||||
|
||||
public Stream OpenSeekableStream (Entry entry)
|
||||
{
|
||||
return m_arc.OpenSeekableEntry (entry);
|
||||
}
|
||||
|
||||
public ArcView OpenView (Entry entry)
|
||||
{
|
||||
return m_arc.OpenView (entry);
|
||||
}
|
||||
|
||||
public virtual Entry FindFile (string filename)
|
||||
{
|
||||
Entry entry = null;
|
||||
if (!m_dir.TryGetValue (filename, out entry))
|
||||
throw new FileNotFoundException();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Entry> GetFiles ()
|
||||
{
|
||||
return m_arc.Dir;
|
||||
@@ -211,6 +258,16 @@ namespace GameRes
|
||||
set { ChDir (value); }
|
||||
}
|
||||
|
||||
public override Entry FindFile (string filename)
|
||||
{
|
||||
Entry entry = null;
|
||||
if (m_dir.TryGetValue (filename, out entry))
|
||||
return entry;
|
||||
if (m_dir.Keys.Any (n => n.StartsWith (filename + PathDelimiter)))
|
||||
return new SubDirEntry (filename);
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
static readonly Regex path_re = new Regex (@"\G[/\\]?([^/\\]+)([/\\])");
|
||||
|
||||
public override IEnumerable<Entry> GetFiles ()
|
||||
@@ -300,4 +357,188 @@ namespace GameRes
|
||||
m_cwd = new_path;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FileSystemStack : IDisposable
|
||||
{
|
||||
Stack<IFileSystem> m_fs_stack = new Stack<IFileSystem>();
|
||||
Stack<string> m_arc_name_stack = new Stack<string>();
|
||||
|
||||
public IEnumerable<IFileSystem> All { get { return m_fs_stack; } }
|
||||
|
||||
public IFileSystem Top { get { return m_fs_stack.Peek(); } }
|
||||
public int Count { get { return m_fs_stack.Count; } }
|
||||
public IEnumerable<string> ArcStack { get { return m_arc_name_stack; } }
|
||||
|
||||
public ArcFile CurrentArchive { get; private set; }
|
||||
private IFileSystem LastVisitedArc { get; set; }
|
||||
private string LastVisitedPath { get; set; }
|
||||
|
||||
public FileSystemStack ()
|
||||
{
|
||||
m_fs_stack.Push (new PhysicalFileSystem());
|
||||
}
|
||||
|
||||
public void ChDir (Entry entry)
|
||||
{
|
||||
if (entry is SubDirEntry)
|
||||
{
|
||||
if (1 == m_fs_stack.Count)
|
||||
{
|
||||
Top.CurrentDirectory = entry.Name;
|
||||
return;
|
||||
}
|
||||
if (".." == entry.Name && string.IsNullOrEmpty (Top.CurrentDirectory))
|
||||
{
|
||||
Pop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (entry.Name == LastVisitedPath && null != LastVisitedArc)
|
||||
{
|
||||
Push (LastVisitedPath, LastVisitedArc);
|
||||
return;
|
||||
}
|
||||
Flush();
|
||||
var arc = ArcFile.TryOpen (entry.Name);
|
||||
if (null == arc)
|
||||
throw new UnknownFormatException();
|
||||
try
|
||||
{
|
||||
Push (entry.Name, arc.CreateFileSystem());
|
||||
CurrentArchive = arc;
|
||||
}
|
||||
catch
|
||||
{
|
||||
arc.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void Push (string path, IFileSystem fs)
|
||||
{
|
||||
m_fs_stack.Push (fs);
|
||||
m_arc_name_stack.Push (path);
|
||||
}
|
||||
|
||||
private void Pop ()
|
||||
{
|
||||
if (m_fs_stack.Count > 1)
|
||||
{
|
||||
Flush();
|
||||
LastVisitedArc = m_fs_stack.Pop();
|
||||
LastVisitedPath = m_arc_name_stack.Pop();
|
||||
if (m_fs_stack.Count > 1 && m_fs_stack.Peek() is FlatArchiveFileSystem)
|
||||
CurrentArchive = (m_fs_stack.Peek() as FlatArchiveFileSystem).Source;
|
||||
else
|
||||
CurrentArchive = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush ()
|
||||
{
|
||||
if (LastVisitedArc != null)
|
||||
{
|
||||
LastVisitedArc.Dispose();
|
||||
LastVisitedArc = null;
|
||||
LastVisitedPath = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _disposed = false;
|
||||
public void Dispose ()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
Flush();
|
||||
foreach (var fs in m_fs_stack.Reverse())
|
||||
fs.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
GC.SuppressFinalize (this);
|
||||
}
|
||||
}
|
||||
|
||||
public static class VFS
|
||||
{
|
||||
private static FileSystemStack m_vfs = new FileSystemStack();
|
||||
|
||||
public static IFileSystem Top { get { return m_vfs.Top; } }
|
||||
public static bool IsVirtual { get { return m_vfs.Count > 1; } }
|
||||
public static int Count { get { return m_vfs.Count; } }
|
||||
|
||||
public static ArcFile CurrentArchive { get { return m_vfs.CurrentArchive; } }
|
||||
|
||||
private static string[] m_top_path = new string[1];
|
||||
|
||||
public static IEnumerable<string> FullPath
|
||||
{
|
||||
get
|
||||
{
|
||||
m_top_path[0] = Top.CurrentDirectory;
|
||||
if (1 == Count)
|
||||
return m_top_path;
|
||||
else
|
||||
return m_vfs.ArcStack.Concat (m_top_path);
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!value.Any())
|
||||
return;
|
||||
var new_vfs = new FileSystemStack();
|
||||
var desired = value.ToArray();
|
||||
for (int i = 0; i < desired.Length-1; ++i)
|
||||
new_vfs.ChDir (new_vfs.Top.FindFile (desired[i]));
|
||||
new_vfs.Top.CurrentDirectory = desired.Last();
|
||||
m_vfs.Dispose();
|
||||
m_vfs = new_vfs;
|
||||
}
|
||||
}
|
||||
|
||||
public static Entry FindFile (string filename)
|
||||
{
|
||||
if (".." == filename)
|
||||
return new SubDirEntry ("..");
|
||||
return m_vfs.Top.FindFile (filename);
|
||||
}
|
||||
|
||||
public static Stream OpenStream (Entry entry)
|
||||
{
|
||||
return m_vfs.Top.OpenStream (entry);
|
||||
}
|
||||
|
||||
public static Stream OpenSeekableStream (Entry entry)
|
||||
{
|
||||
return m_vfs.Top.OpenSeekableStream (entry);
|
||||
}
|
||||
|
||||
public static ArcView OpenView (Entry entry)
|
||||
{
|
||||
return m_vfs.Top.OpenView (entry);
|
||||
}
|
||||
|
||||
public static void ChDir (Entry entry)
|
||||
{
|
||||
m_vfs.ChDir (entry);
|
||||
}
|
||||
|
||||
public static void ChDir (string path)
|
||||
{
|
||||
m_vfs.ChDir (FindFile (path));
|
||||
}
|
||||
|
||||
public static void Flush ()
|
||||
{
|
||||
m_vfs.Flush();
|
||||
}
|
||||
|
||||
public static IEnumerable<Entry> GetFiles ()
|
||||
{
|
||||
return m_vfs.Top.GetFiles();
|
||||
}
|
||||
}
|
||||
|
||||
public class UnknownFormatException : FileFormatException
|
||||
{
|
||||
public UnknownFormatException () : base (garStrings.MsgUnknownFormat) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +261,12 @@ namespace GameRes
|
||||
public ResourceOptions Options { get; set; }
|
||||
}
|
||||
|
||||
public class OverwriteEventArgs : EventArgs
|
||||
{
|
||||
public string Filename { get; set; }
|
||||
public bool Overwrite { get; set; }
|
||||
}
|
||||
|
||||
public sealed class FormatCatalog
|
||||
{
|
||||
private static readonly FormatCatalog m_instance = new FormatCatalog();
|
||||
|
||||
@@ -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.1.7.90")]
|
||||
[assembly: AssemblyFileVersion ("1.1.7.90")]
|
||||
[assembly: AssemblyVersion ("1.1.9.91")]
|
||||
[assembly: AssemblyFileVersion ("1.1.9.91")]
|
||||
|
||||
11
GameRes/Strings/garStrings.Designer.cs
generated
11
GameRes/Strings/garStrings.Designer.cs
generated
@@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.18444
|
||||
// Runtime Version:4.0.30319.34209
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
@@ -104,5 +104,14 @@ namespace GameRes.Strings {
|
||||
return ResourceManager.GetString("MsgUnknownEncryption", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to file could not be opened as resource archive.
|
||||
/// </summary>
|
||||
public static string MsgUnknownFormat {
|
||||
get {
|
||||
return ResourceManager.GetString("MsgUnknownFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,4 +132,7 @@
|
||||
<data name="MsgUnknownEncryption" xml:space="preserve">
|
||||
<value>Unknown encryption scheme</value>
|
||||
</data>
|
||||
<data name="MsgUnknownFormat" xml:space="preserve">
|
||||
<value>file could not be opened as resource archive</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -132,4 +132,7 @@
|
||||
<data name="MsgUnknownEncryption" xml:space="preserve">
|
||||
<value>Неизвестный метод шифрования</value>
|
||||
</data>
|
||||
<data name="MsgUnknownFormat" xml:space="preserve">
|
||||
<value>файл не может быть открыт как архив ресурсов</value>
|
||||
</data>
|
||||
</root>
|
||||
Reference in New Issue
Block a user