Newer
Older
Epoc / zPakr / zPak.cs
@Ivan Dominguez Ivan Dominguez on 13 Feb 6 KB Initial upload
using System.Text;

namespace XWolf.Z
{
    public class zPak
    {
        private const string ZPAK_HEADER = "zPak!\u001a";
        private const string ZPAK_FOOTER = "/zPak";
        private class zPackEntry
        {
            public string Name { get; set; } = "";
            public long EntryPosition { get; set; } = 0;
            public long Position { get; set; } = 0;
            public long Length { get; set; } = 0;
        }

        private readonly List<zPackEntry> entries = [];
        private string? fileName;
        private Stream? stream;
        private long offset = -1;
        private bool writting = false;

        #region Constructors
        public zPak()
        {

        }

        public zPak(string name, bool autocreate = true)
        {
            Open(name, autocreate);
        }

        public zPak(Stream stream, bool autocreate = true)
        {
            Open(stream, autocreate);
        }
        #endregion

        #region Stream / File access
        public void Open(string name, bool autocreate = true)
        {
            fileName = name;
            if (File.Exists(fileName))
                OpenForRead(autocreate);
            else
                if (autocreate)
                OpenForWrite();
            else
                throw new IOException("zPak does not exists.");
        }

        private void OpenForRead(bool autocreate)
        {
            if (fileName == null)
                throw new Exception("Can't reopen stream");
            Close();
            writting = false;
            var file = new FileStream(fileName, FileMode.Open, FileAccess.Read);
            OpenStream(file, autocreate);
        }

        private void OpenForWrite()
        {
            if (fileName == null)
                throw new Exception("Can't reopen stream");
            Close();
            writting = true;
            var file = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
            OpenStream(file, true);
        }

        public void Open(Stream stream, bool autocreate = true)
        {
            fileName = null;
            OpenStream(stream, autocreate);
        }

        private void OpenStream(Stream stream, bool autocreate)
        {
            this.stream = stream;
            if (offset < 0)
            {
                ReadOffset(autocreate);
                ReadDirectory();
            }
        }

        public void Close()
        {
            if (stream == null)
                return;
            if (writting)
            {
                GetStream().Position = GetStream().Length;
                WriteLong(offset);
                WritePlainString(ZPAK_FOOTER);
            }
            if (stream != null)
            {
                stream.Close();
                stream = null;
            }
        }
        private Stream GetStream(bool forWrite = false)
        {
            if (stream == null)
                throw new IOException("Pak not opened.");
            if (forWrite && !stream.CanWrite)
                OpenForWrite();
            return stream;
        }
        #endregion

        #region Stream I/O

        private byte[] ReadBytes(int ctt)
        {
            byte[] buf = new byte[ctt];
            if (GetStream().Read(buf, 0, ctt) != ctt)
                throw new IOException("Unexpected end of file.");
            return buf;
        }

        private int ReadInt()
        {
            return BitConverter.ToInt32(ReadBytes(4), 0);
        }

        private long ReadLong()
        {
            return BitConverter.ToInt64(ReadBytes(8), 0);
        }

        private string ReadPlainString(int len)
        {
            var strbuf = ReadBytes(len);
            return Encoding.UTF8.GetString(strbuf, 0, strbuf.Length);
        }

        private string ReadString()
        {
            return ReadPlainString(ReadInt());
        }

        private void WriteBytes(byte[] bytes)
        {
            GetStream(true).Write(bytes);
        }

        private void WriteInt(int i)
        {
            WriteBytes(BitConverter.GetBytes(i));
        }

        private void WriteLong(long l)
        {
            WriteBytes(BitConverter.GetBytes(l));
        }

        private void WritePlainString(string str)
        {
            WriteBytes(Encoding.UTF8.GetBytes(str));
        }

        private void WriteString(string str)
        {
            WriteInt(str.Length);
            WritePlainString(str);
        }

        #endregion

        #region Pak header handling
        private void ReadOffset(bool autocreate)
        {
            if (GetStream().Length > 0)
            {
                if (GetStream().Length > ZPAK_HEADER.Length + ZPAK_FOOTER.Length + 8)
                {
                    GetStream().Position = GetStream().Length - ZPAK_FOOTER.Length;
                    if (ReadPlainString(8) == ZPAK_FOOTER)
                    {
                        GetStream().Position = GetStream().Length - (ZPAK_FOOTER.Length + 8);
                        offset = ReadLong();
                    }
                    else
                        PreparePakStub(autocreate);
                }
                else
                    PreparePakStub(autocreate);
            }
            else
                PreparePakStub(autocreate);
        }

        private void PreparePakStub(bool autocreate)
        {
            if (!autocreate)
                throw new IOException("Not a zPak.");
            offset = GetStream().Position = GetStream().Length;
            WritePlainString(ZPAK_HEADER);
        }

        private zPackEntry ReadEntry()
        {
            var entry = new zPackEntry
            {
                EntryPosition = ZPos,
                Name = ReadString(),
                Position = ZPos,
                Length = ReadLong()
            };
            return entry;
        }

        private void ReadDirectory()
        {
            entries.Clear();
            ZPos = 0;
            if (ReadPlainString(ZPAK_HEADER.Length) != ZPAK_HEADER)
                throw new Exception("Not a correct zPak file or corrupted");
            var end = GetStream().Length - (ZPAK_FOOTER.Length + 8);
            while (GetStream().Position < end)
            {
                var ent = ReadEntry();
                GetStream().Seek(ent.Length, SeekOrigin.Current);
                entries.Add(ent);
            }
        }

        private long ZPos
        {
            get { return GetStream().Position - offset; }
            set { GetStream().Position = value + offset; }
        }
        #endregion
    }

}