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
}
}