//! \file GameRes.cs //! \date Mon Jun 30 20:12:13 2014 //! \brief game resources browser. // // Copyright (C) 2014-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.IO; using System.Collections.Generic; using GameRes.Strings; namespace GameRes { /// /// Basic filesystem entry. /// public class Entry { public virtual string Name { get; set; } public virtual string Type { get; set; } public long Offset { get; set; } public uint Size { get; set; } public Entry () { Type = ""; Offset = -1; } /// /// Check whether entry lies within specified file bound. /// public bool CheckPlacement (long max_offset) { return Offset < max_offset && Size <= max_offset && Offset <= max_offset - Size; } } public class PackedEntry : Entry { public uint UnpackedSize { get; set; } public bool IsPacked { get; set; } } public abstract class IResource { /// Short tag sticked to resource (usually filename extension) public abstract string Tag { get; } /// Resource description (its source/inventor) public abstract string Description { get; } /// Resource type (image/archive/script) public abstract string Type { get; } /// First 4 bytes of the resource file as little-endian 32-bit integer, /// or zero if it could vary. public abstract uint Signature { get; } /// Whether resource creation is supported by implementation. public virtual bool CanWrite { get { return false; } } /// Signatures peculiar to the resource (the one above is also included here). public IEnumerable Signatures { get; protected set; } /// Filename extensions peculiar to the resource. public IEnumerable Extensions { get; protected set; } /// Resource settings suitable for serialization. public virtual ResourceScheme Scheme { get; set; } /// /// Create empty Entry that corresponds to implemented resource. /// public EntryType Create () where EntryType : Entry, new() { return new EntryType { Type = this.Type }; } protected IResource () { Extensions = new string[] { GetDefaultExtension() }; Signatures = new uint[] { this.Signature }; } protected string GetDefaultExtension () { var ext = Tag.ToLowerInvariant(); int slash = ext.IndexOf ('/'); if (slash != -1) ext = ext.Substring (0, slash); return ext; } public virtual ResourceOptions GetDefaultOptions () { return null; } public virtual ResourceOptions GetOptions (object widget) { return GetDefaultOptions(); } public virtual object GetCreationWidget () { return null; } public virtual object GetAccessWidget () { return null; } protected OptType GetOptions (ResourceOptions res_options) where OptType : ResourceOptions { var options = res_options as OptType; if (null == options) options = this.GetDefaultOptions() as OptType; return options; } protected OptType Query (string notice) where OptType : ResourceOptions { var args = new ParametersRequestEventArgs { Notice = notice }; FormatCatalog.Instance.InvokeParametersRequest (this, args); if (!args.InputResult) throw new OperationCanceledException(); return GetOptions (args.Options); } } public class ResourceOptions { } [Serializable] public class ResourceScheme { } public enum ArchiveOperation { Abort, Skip, Continue, } public delegate ArchiveOperation EntryCallback (int num, Entry entry, string description); public abstract class ArchiveFormat : IResource { public override string Type { get { return "archive"; } } /// /// Whether archive file system could contain subdirectories. /// public abstract bool IsHierarchic { get; } public abstract ArcFile TryOpen (ArcView view); /// /// Extract file referenced by into current directory. /// public void Extract (ArcFile file, Entry entry) { using (var input = OpenEntry (file, entry)) using (var output = CreateFile (entry.Name)) input.CopyTo (output); } /// /// Open file referenced by as Stream. /// public virtual Stream OpenEntry (ArcFile arc, Entry entry) { return arc.File.CreateStream (entry.Offset, entry.Size); } /// /// Create file corresponding to in current directory and open it /// for writing. Overwrites existing file, if any. /// static public Stream CreateFile (string filename) { filename = CreatePath (filename); if (File.Exists (filename)) { // query somehow whether to overwrite existing file or not. } return File.Create (filename); } static public string CreatePath (string filename) { string dir = Path.GetDirectoryName (filename); if (!string.IsNullOrEmpty (dir)) // check for malformed filenames { string root = Path.GetPathRoot (dir); if (!string.IsNullOrEmpty (root)) { dir = dir.Substring (root.Length); // strip root } string cwd = Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar; dir = Path.GetFullPath (dir); filename = Path.GetFileName (filename); // check whether filename would reside within current directory if (dir.StartsWith (cwd, StringComparison.OrdinalIgnoreCase)) { // path looks legit, create it Directory.CreateDirectory (dir); filename = Path.Combine (dir, filename); } } return filename; } /// /// Create resource within stream containing entries from the /// supplied and applying necessary . /// public virtual void Create (Stream file, IEnumerable list, ResourceOptions options = null, EntryCallback callback = null) { throw new NotImplementedException ("ArchiveFormat.Create is not implemented"); } public static bool IsSaneCount (int count) { return count > 0 && count < 0x20000; } } public delegate void ParametersRequestEventHandler (object sender, ParametersRequestEventArgs e); public class ParametersRequestEventArgs : EventArgs { /// /// String describing request nature (encryption key etc). /// public string Notice { get; set; } /// /// Return value from ShowDialog() /// public bool InputResult { get; set; } /// /// Archive-specific options set by InputWidget. /// public ResourceOptions Options { get; set; } } public class OverwriteEventArgs : EventArgs { public string Filename { get; set; } public bool Overwrite { get; set; } } public class InvalidFormatException : FileFormatException { public InvalidFormatException() : base(garStrings.MsgInvalidFormat) { } public InvalidFormatException (string msg) : base (msg) { } } public class UnknownEncryptionScheme : Exception { public UnknownEncryptionScheme() : base(garStrings.MsgUnknownEncryption) { } public UnknownEncryptionScheme (string msg) : base (msg) { } } public class InvalidEncryptionScheme : Exception { public InvalidEncryptionScheme() : base(garStrings.MsgInvalidEncryption) { } public InvalidEncryptionScheme (string msg) : base (msg) { } } public class FileSizeException : Exception { public FileSizeException () : base (garStrings.MsgFileTooLarge) { } public FileSizeException (string msg) : base (msg) { } } public class InvalidFileName : Exception { public string FileName { get; set; } public InvalidFileName (string filename) : this (filename, garStrings.MsgInvalidFileName) { } public InvalidFileName (string filename, string message) : base (message) { FileName = filename; } public InvalidFileName (string filename, Exception X) : this (filename, garStrings.MsgInvalidFileName, X) { } public InvalidFileName (string filename, string message, Exception X) : base (message, X) { FileName = filename; } } }