using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace OpenVIII
{
public class ArchiveWorker : ArchiveBase
{
#region Fields
public Memory.Archive _path;
///
/// Generated File List
///
public string[] FileList;
private static ConcurrentDictionary> ArchiveCache =
new ConcurrentDictionary>
(new Dictionary> {
{ Memory.Archives.A_BATTLE, new ConcurrentDictionary() },
{ Memory.Archives.A_FIELD, new ConcurrentDictionary() },
{ Memory.Archives.A_MAGIC, new ConcurrentDictionary() },
{ Memory.Archives.A_MAIN, new ConcurrentDictionary() },
{ Memory.Archives.A_MENU, new ConcurrentDictionary() },
{ Memory.Archives.A_WORLD, new ConcurrentDictionary() }
});
///
/// prevent two threads from writing to cache at the same time.
///
private static object cachelock = new object();
private bool _compressed;
private uint _locationInFs;
private uint _unpackedFileSize;
private byte[] FI, FS, FL;
private bool isDir = false;
#endregion Fields
#region Constructors
///
/// Saves the active archive and file list.
///
/// Memory.Archive
/// If list generation is unneeded you can skip it by setting true
public ArchiveWorker(Memory.Archive path, bool skiplist = false)
{
if (Directory.Exists(path))
{
isDir = true;
}
_path = path;
if (!skiplist)
FileList = ProduceFileLists();
}
public ArchiveWorker(byte[] fI, byte[] fS, byte[] fL, bool skiplist = false)
{
FI = fI;
FS = fS;
FL = fL;
if (!skiplist)
FileList = ProduceFileLists();
}
#endregion Constructors
#region Methods
///
/// Generate archive worker and get file
///
/// which archive you want to read from
/// name of file you want to recieve
/// Uncompressed binary file data
public static byte[] GetBinaryFile(Memory.Archive archive, string fileName, bool cache = false)
{
ArchiveWorker tmp = new ArchiveWorker(archive, true);
return tmp.GetBinaryFile(fileName, cache);
}
public override ArchiveBase GetArchive(Memory.Archive archive) => new ArchiveWorker(GetBinaryFile(archive.FI), GetBinaryFile(archive.FS), GetBinaryFile(archive.FL)) { _path = archive };
///
/// GetBinary
///
///
///
public override byte[] GetBinaryFile(string fileName, bool cache = false)
{
if (string.IsNullOrWhiteSpace(fileName))
throw new FileNotFoundException("NO FILENAME");
if (FL != null && FL.Length > 0 && FI != null && FL.Length > 0 && FI != null && FL.Length > 0)
return FileInTwoArchives(fileName);
else
if (!isDir)
{
int loc = FindFile(ref fileName, new FileStream(_path.FL, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); //File.OpenRead(_path.FL));
// read file list
if (loc == -1)
{
Debug.WriteLine("ArchiveWorker: NO SUCH FILE!");
//throw new Exception("ArchiveWorker: No such file!");
}
else
return GetBinaryFile(fileName, loc, cache);
}
else
return GetBinaryFile(fileName, 0, cache);
throw new FileNotFoundException($"Searched {_path} and could not find {fileName}.", fileName);
}
//public Stream GetBinaryFileStream(string fileName, bool cache = false) => throw new NotImplementedException();
///
/// Get current file list for loaded archive.
///
public override string[] GetListOfFiles() => FileList;
public override Memory.Archive GetPath() => _path;
private static bool GetLine(TextReader tr, out string line)
{
line = tr.ReadLine();
if (!string.IsNullOrWhiteSpace(line))
{
line.TrimEnd('\0');
return true;
}
return false;
}
///
/// Give me three archives as bytes uncompressed please!
///
/// FileIndex
/// FileSystem
/// FileList
/// Filename of the file to get
///
///
/// Does the same thing as Get Binary file, but it reads from byte arrays in ram because the
/// data was already pulled from a file earlier.
///
private byte[] FileInTwoArchives(string filename)
{
int loc = FindFile(ref filename, new MemoryStream(FL, false));
if (loc == -1)
{
Debug.WriteLine("ArchiveWorker: NO SUCH FILE!");
return null;
//throw new Exception("ArchiveWorker: No such file!");
}
// get params from index
uint fsLen = BitConverter.ToUInt32(FI, loc * 12);
uint fSpos = BitConverter.ToUInt32(FI, (loc * 12) + 4);
bool compe = BitConverter.ToUInt32(FI, (loc * 12) + 8) != 0;
// copy binary data
byte[] file = new byte[fsLen];
Array.Copy(FS, fSpos, file, 0, file.Length);
return compe ? LZSS.DecompressAllNew(file) : file;
}
///
/// Search file list for line filename is on.
///
/// filename or path to search for
/// stream of text data to search in
/// -1 on error or >=0 on success.
private int FindFile(ref string filename, Stream stream)
{
filename = filename.TrimEnd('\0');
using (TextReader tr = new StreamReader(stream))
{
for (int i = 0; GetLine(tr, out string line); i++)
{
if (string.IsNullOrWhiteSpace(line))
{
Debug.WriteLine("ArchiveWorker::File entry is null. Returning -1");
break;
}
if (line.IndexOf(filename, StringComparison.OrdinalIgnoreCase) >= 0)
{
filename = line; //make sure the filename in dictionary is consistant.
return i;
}
}
}
Debug.WriteLine($"ArchiveWorker:: Filename {filename}, not found. returning -1");
return -1;
}
private byte[] GetBinaryFile(string fileName, int loc, bool cache)
{
if (ArchiveCache.TryGetValue(_path, out ConcurrentDictionary a) && a.TryGetValue(fileName, out byte[] b))
return b;
if (isDir)
{
if (FileList == null || FileList.Length == 0)
ProduceFileLists();
fileName = FileList.FirstOrDefault(x => x.IndexOf(fileName, StringComparison.OrdinalIgnoreCase) >= 0);
if (!string.IsNullOrWhiteSpace(fileName))
using (BinaryReader br = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
return br.ReadBytes(checked((int)br.BaseStream.Length));
}
}
byte[] temp = null;
//read index data
using (BinaryReader br = new BinaryReader(new FileStream(_path.FI, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))//File.OpenRead(_path.FI)))
{
br.BaseStream.Seek(loc * 12, SeekOrigin.Begin);
_unpackedFileSize = br.ReadUInt32(); //fs.Seek(4, SeekOrigin.Current);
_locationInFs = br.ReadUInt32();
_compressed = br.ReadUInt32() != 0;
}
//read binary data.
using (BinaryReader br = new BinaryReader(new FileStream(_path.FS, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))//File.OpenRead(_path.FS)))
{
br.BaseStream.Seek(_locationInFs, SeekOrigin.Begin);
temp = br.ReadBytes(_compressed ? br.ReadInt32() : (int)_unpackedFileSize);
}
temp = temp == null ? null : _compressed ? LZSS.DecompressAllNew(temp) : temp;
if (temp != null && cache && ArchiveCache.TryGetValue(_path, out a))
a.TryAdd(fileName, temp);
return temp;
}
///
/// Generate a file list from binary data.
///
/// raw File List
private string[] GetListOfFiles(byte[] fl) => ProduceFileLists(new MemoryStream(fl));
///
/// Generate a file list from raw text file.
///
///
private string[] ProduceFileLists()
{
if (isDir)
return Directory.GetFiles(_path, "*", SearchOption.AllDirectories).OrderBy(x => x.Length).ThenBy(x => x, StringComparer.OrdinalIgnoreCase).ToArray();
if (FL != null && FL.Length > 0)
return GetListOfFiles(FL);
return ProduceFileLists(new FileStream(_path.FL, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)).OrderBy(x => x.Length).ThenBy(x => x, StringComparer.OrdinalIgnoreCase).ToArray();
}
private string[] ProduceFileLists(Stream s)
{
using (StreamReader sr = new StreamReader(s, System.Text.Encoding.ASCII))
{
List fl = new List();
while (!sr.EndOfStream)
fl.Add(sr.ReadLine().Trim('\0', '\r', '\n'));
return fl.ToArray();
}
}
#endregion Methods
}
}