ArchiveWorker.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. namespace OpenVIII
  7. {
  8. public sealed class ArchiveWorker : ArchiveBase
  9. {
  10. #region Fields
  11. public readonly ArchiveBase FsArchive;
  12. #endregion Fields
  13. #region Constructors
  14. /// <summary>
  15. /// Saves the active archive and file list.
  16. /// </summary>
  17. /// <param name="archive">Memory.Archive</param>
  18. /// <param name="skipList">If list generation is unneeded you can skip it by setting true</param>
  19. private ArchiveWorker(Memory.Archive archive, bool skipList = false)
  20. {
  21. if (archive.IsDir)
  22. {
  23. Memory.Log.WriteLine($"{nameof(ArchiveWorker)}:: opening directory: {archive}");
  24. IsDir = true;
  25. }
  26. else
  27. Memory.Log.WriteLine($"{nameof(ArchiveWorker)}:: opening archiveFile: {archive}");
  28. Archive = archive;
  29. ParentPath = FindParentPath(archive);
  30. ArchiveBase tempArchive = null;
  31. if (ParentPath != null && ParentPath.Count > 0)
  32. foreach (var p in ParentPath)
  33. {
  34. if (tempArchive != null)
  35. {
  36. tempArchive = tempArchive.GetArchive(p);
  37. }
  38. else if (p.IsDir || p.IsFile)
  39. {
  40. tempArchive = ArchiveBase.Load(p);
  41. }
  42. }
  43. if (tempArchive != null)
  44. {
  45. tempArchive.GetArchive(archive, out var fi, out FsArchive, out var fl);
  46. ArchiveMap = new ArchiveMap(fi, fl, tempArchive.GetMaxSize(archive));
  47. }
  48. if (!skipList)
  49. GetListOfFiles();
  50. IsOpen = true;
  51. }
  52. private ArchiveWorker(Memory.Archive archive, StreamWithRangeValues fI, ArchiveBase fS, StreamWithRangeValues fL, bool skipList = false)
  53. {
  54. ArchiveMap = new ArchiveMap(fI, fL, fS.GetMaxSize(archive));
  55. Archive = archive;
  56. FsArchive = fS;
  57. //FS = null;
  58. if (!skipList)
  59. GetListOfFiles();
  60. IsOpen = true;
  61. }
  62. #endregion Constructors
  63. #region Methods
  64. /// <summary>
  65. /// Load Archive with out storing FS in byte[] works for archive that aren't compressed.
  66. /// </summary>
  67. /// <param name="path">Archive file archive.</param>
  68. /// <param name="fI">Stream containing the FI file</param>
  69. /// <param name="fS">Archive where the FS file is.</param>
  70. /// <param name="fL">Stream containing the FL file</param>
  71. /// <param name="skipList">Skip generating list of files</param>
  72. /// <returns>ArchiveWorker</returns>
  73. public static ArchiveBase Load(Memory.Archive path, StreamWithRangeValues fI, ArchiveBase fS, StreamWithRangeValues fL, bool skipList = false)
  74. {
  75. if (CacheTryGetValue(path, out var value))
  76. {
  77. return value;
  78. }
  79. value = new ArchiveWorker(path, fI, fS, fL, skipList);
  80. if (!value.IsOpen)
  81. value = null;
  82. if (CacheTryAdd(path, value))
  83. {
  84. }
  85. return value;
  86. }
  87. public static ArchiveBase Load(Memory.Archive path, bool skipList = false)
  88. {
  89. if (path.IsZZZ)
  90. return ArchiveZzz.Load(path, skipList);
  91. if (CacheTryGetValue(path, out var value))
  92. {
  93. return value;
  94. }
  95. value = new ArchiveWorker(path, skipList);
  96. if (!value.IsOpen)
  97. value = null;
  98. if (CacheTryAdd(path, value))
  99. {
  100. }
  101. return value;
  102. }
  103. public override ArchiveBase GetArchive(string fileName)
  104. {
  105. if (string.IsNullOrWhiteSpace(fileName))
  106. throw new FileNotFoundException("NO FILENAME");
  107. if (ArchiveMap != null && ArchiveMap.Count > 1)
  108. FindFile(ref fileName);
  109. else if (IsDir)
  110. {
  111. if (FileList == null || FileList.Length == 0)
  112. ProduceFileLists();
  113. fileName = FileList.FirstOrDefault(x => x.IndexOf(fileName, StringComparison.OrdinalIgnoreCase) >= 0);
  114. }
  115. else
  116. if (File.Exists(Archive.FL))
  117. using (var fs = new FileStream(Archive.FL, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  118. FindFile(ref fileName, fs);
  119. return GetArchive((Memory.Archive)fileName);
  120. }
  121. public override ArchiveBase GetArchive(Memory.Archive archive)
  122. {
  123. if (archive == Memory.Archives.ZZZ_MAIN || archive == Memory.Archives.ZZZ_OTHER)
  124. {
  125. var zzz = archive.ZZZ;
  126. if (FindFile(ref zzz) <= -1 || string.IsNullOrWhiteSpace(zzz)) return null;
  127. if (File.Exists(zzz))
  128. archive.SetFilename(zzz);
  129. return !CacheTryGetValue(archive, out var ab) ? ArchiveZzz.Load(zzz) : ab;
  130. }
  131. if (CacheTryGetValue(archive, out var value)) return value;
  132. GetArchive(archive, out var fI, out var fS, out StreamWithRangeValues fL);
  133. return fI == null || fS == null || fL == null ||
  134. fI.Length == 0 || fL.Length == 0
  135. ? null
  136. : new ArchiveWorker(archive, fI, fS, fL);
  137. }
  138. /// <summary>
  139. /// Get binary data
  140. /// </summary>
  141. /// <param name="fileName">filename you want</param>
  142. /// <param name="cache">if true store the data for later</param>
  143. /// <returns></returns>
  144. public override byte[] GetBinaryFile(string fileName, bool cache = false)
  145. {
  146. if (string.IsNullOrWhiteSpace(fileName))
  147. throw new FileNotFoundException("NO FILENAME");
  148. byte[] FileInTwoArchives()
  149. {
  150. //if (FS != null && FS.Length > 0)
  151. // return ArchiveMap.GetBinaryFile(filename, new MemoryStream(FS));
  152. //else
  153. return FsArchive != null ? ArchiveMap.GetBinaryFile(fileName, FsArchive.GetStreamWithRangeValues(Archive.FS)) : null;
  154. }
  155. if (ArchiveMap != null && ArchiveMap.Count > 0)
  156. {
  157. if (!cache) return FileInTwoArchives();
  158. var fileNameBackup = fileName;
  159. GetListOfFiles();
  160. fileName = FileList.OrderBy(x => fileName.Length).ThenBy(x => fileNameBackup, StringComparer.OrdinalIgnoreCase).FirstOrDefault(x => x.IndexOf(fileNameBackup, StringComparison.OrdinalIgnoreCase) >= 0);
  161. if (string.IsNullOrWhiteSpace(fileName)) throw new NullReferenceException($"{nameof(ArchiveWorker)}::{nameof(GetBinaryFile)} fileName ({fileNameBackup}) not found!");
  162. if (!LocalTryGetValue(fileName, out var value))
  163. {
  164. var buffer = FileInTwoArchives();
  165. if (LocalTryAdd(fileName, buffer))
  166. {
  167. Memory.Log.WriteLine($"{nameof(ArchiveWorker)}::{nameof(GetBinaryFile)}::{nameof(LocalTryAdd)} cached {fileName}");
  168. }
  169. return buffer;
  170. }
  171. Memory.Log.WriteLine($"{nameof(ArchiveWorker)}::{nameof(GetBinaryFile)}::{nameof(LocalTryGetValue)} read from cache {fileName}");
  172. return value;
  173. }
  174. if (!IsDir)
  175. {
  176. var loc = -1;
  177. if (File.Exists(Archive.FL))
  178. using (var fs = new FileStream(Archive.FL, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  179. loc = FindFile(ref fileName, fs); //File.OpenRead(archive.FL));
  180. // read file list
  181. if (loc == -1)
  182. {
  183. Debug.WriteLine($"ArchiveWorker: NO SUCH FILE! :: {Archive.FL}");
  184. //throw new Exception("ArchiveWorker: No such file!");
  185. }
  186. else
  187. return GetBinaryFile(fileName, loc, cache);
  188. }
  189. else
  190. return GetBinaryFile(fileName, 0, cache);
  191. Debug.WriteLine($"ArchiveWorker: NO SUCH FILE! :: Searched {Archive} and could not find {fileName}");
  192. return null;
  193. }
  194. public override Memory.Archive GetPath() => Archive;
  195. public override StreamWithRangeValues GetStreamWithRangeValues(string fileName) =>
  196. GetStreamWithRangeValues(fileName, null, 0);
  197. public override string ToString() => $"{Archive} :: {Used}";
  198. protected override int FindFile(ref string filename)
  199. {
  200. if (ArchiveMap != null && ArchiveMap.Count > 1)
  201. return base.FindFile(ref filename);
  202. if (IsDir)
  203. {
  204. var f = filename;
  205. filename = FileList.FirstOrDefault(x => x.IndexOf(f, StringComparison.OrdinalIgnoreCase) >= 0);
  206. return string.IsNullOrWhiteSpace(filename) ? -1 : 0;
  207. }
  208. using (var fs = new FileStream(Archive.FL, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  209. return FindFile(ref filename, fs); //File.OpenRead(archive.FL));
  210. }
  211. /// <summary>
  212. /// Generate a file list from raw text file.
  213. /// </summary>
  214. /// <see cref="https://stackoverflow.com/questions/12744725/how-do-i-perform-file-readalllines-on-a-file-that-is-also-open-in-excel"/>
  215. protected override string[] ProduceFileLists()
  216. {
  217. string[] r;
  218. if (Archive == null) return null;
  219. if (IsDir)
  220. return Directory.GetFiles(Archive, "*", SearchOption.AllDirectories).OrderBy(x => x.Length)
  221. .ThenBy(x => x, StringComparer.OrdinalIgnoreCase).ToArray();
  222. if ((r = base.ProduceFileLists()) != null)
  223. return r;
  224. if (!File.Exists(Archive.FL)) return null;
  225. using (var fs = new FileStream(Archive.FL, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  226. return ProduceFileLists(fs).OrderBy(x => x.Length).ThenBy(x => x, StringComparer.OrdinalIgnoreCase)
  227. .ToArray();
  228. }
  229. /// <summary>
  230. /// Search file list for line filename is on.
  231. /// </summary>
  232. /// <param name="filename">filename or archive to search for</param>
  233. /// <param name="stream">stream of text data to search in</param>
  234. /// <returns>-1 on error or &gt;=0 on success.</returns>
  235. private static int FindFile(ref string filename, Stream stream)
  236. {
  237. if (string.IsNullOrWhiteSpace(filename)) return -1;
  238. filename = filename.TrimEnd('\0');
  239. using (TextReader tr = new StreamReader(stream))
  240. {
  241. for (var i = 0; GetLine(tr, out var line); i++)
  242. {
  243. if (string.IsNullOrWhiteSpace(line))
  244. {
  245. Debug.WriteLine("ArchiveWorker::File entry is null. Returning -1");
  246. break;
  247. }
  248. if (line.IndexOf(filename, StringComparison.OrdinalIgnoreCase) < 0) continue;
  249. filename = line; //make sure the filename in dictionary is consistent.
  250. return i;
  251. }
  252. }
  253. Debug.WriteLine($"ArchiveWorker:: Filename {filename}, not found. returning -1");
  254. return -1;
  255. }
  256. private static bool GetLine(TextReader tr, out string line)
  257. {
  258. line = tr.ReadLine();
  259. if (string.IsNullOrWhiteSpace(line)) return false;
  260. line = line.TrimEnd('\0');
  261. return true;
  262. }
  263. private static IEnumerable<string> ProduceFileLists(Stream s)
  264. {
  265. using (var sr = new StreamReader(s, System.Text.Encoding.UTF8))
  266. {
  267. var fl = new List<string>();
  268. while (!sr.EndOfStream)
  269. fl.Add(sr.ReadLine()?.Trim('\0', '\r', '\n'));
  270. return fl.ToArray();
  271. }
  272. }
  273. private byte[] GetBinaryFile(string fileName, int loc, bool cache)
  274. {
  275. fileName = FileList.FirstOrDefault(x => x.IndexOf(fileName, StringComparison.OrdinalIgnoreCase) >= 0);
  276. if (LocalTryGetValue(fileName, out var b))
  277. {
  278. Memory.Log.WriteLine($"{nameof(ArchiveWorker)}::{nameof(GetBinaryFile)} :: read from cache: {fileName}");
  279. return b;
  280. }
  281. if (IsDir)
  282. {
  283. if (FileList == null || FileList.Length == 0)
  284. ProduceFileLists();
  285. if (!string.IsNullOrWhiteSpace(fileName))
  286. using (var br = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
  287. {
  288. Memory.Log.WriteLine($"{nameof(ArchiveWorker)}::{nameof(GetBinaryFile)} :: reading: {fileName}");
  289. var buffer = br.ReadBytes(checked((int)br.BaseStream.Length));
  290. if (cache && LocalTryAdd(fileName, buffer))
  291. {
  292. Memory.Log.WriteLine($"{nameof(ArchiveWorker)}::{nameof(GetBinaryFile)} :: cached: {fileName}");
  293. }
  294. return buffer;
  295. }
  296. }
  297. //read index data
  298. var fi = GetFi(loc);
  299. //read binary data.
  300. var temp = GetCompressedData(fi, out var _);
  301. Memory.Log.WriteLine($"{nameof(ArchiveWorker)}::{nameof(GetBinaryFile)} :: extracting: {fileName}");
  302. if (temp != null)
  303. switch (fi.CompressionType)
  304. {
  305. case CompressionType.None:
  306. break;
  307. case CompressionType.LZSS:
  308. LZSS.DecompressAllNew(temp, fi.UncompressedSize);
  309. break;
  310. case CompressionType.LZ4:
  311. temp = ArchiveMap.Lz4Uncompress(temp, fi.UncompressedSize);
  312. break;
  313. case CompressionType.LZSS_UnknownSize:
  314. LZSS.DecompressAllNew(temp, 0);
  315. break;
  316. case CompressionType.LZSS_LZSS:
  317. LZSS.DecompressAllNew(LZSS.DecompressAllNew(temp, fi.UncompressedSize), 0);
  318. break;
  319. default:
  320. throw new ArgumentOutOfRangeException();
  321. }
  322. if (temp != null && cache && LocalTryAdd(fileName, temp))
  323. {
  324. Memory.Log.WriteLine($"{nameof(ArchiveWorker)}::{nameof(GetBinaryFile)} :: cached: {fileName}");
  325. }
  326. return temp;
  327. }
  328. /// <summary>
  329. /// GetCompressedData reads data from file directly. This isn't used right now I tried to add checks but they aren't tested yet.
  330. /// </summary>
  331. /// <param name="fi"></param>
  332. /// <param name="size"></param>
  333. /// <param name="skipData"></param>
  334. /// <returns></returns>
  335. private byte[] GetCompressedData(FI fi, out int size, bool skipData = false)
  336. {
  337. byte[] temp = null;
  338. FileStream fs;
  339. using (var br = new BinaryReader(fs = new FileStream(Archive.FS, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
  340. {
  341. br.BaseStream.Seek(fi.Offset, SeekOrigin.Begin);
  342. var compressedSize = 0;
  343. var max = (int)(fs.Length - fs.Position);
  344. switch (fi.CompressionType)
  345. {
  346. case CompressionType.None:
  347. case CompressionType.LZSS_UnknownSize:
  348. compressedSize = fi.UncompressedSize;
  349. break;
  350. case CompressionType.LZSS:
  351. case CompressionType.LZSS_LZSS:
  352. if (fs.Length < 5) throw new InvalidDataException();
  353. compressedSize = br.ReadInt32();
  354. if (compressedSize > (max -= sizeof(int))) throw new InvalidDataException();
  355. break;
  356. case CompressionType.LZ4:
  357. break;
  358. default:
  359. throw new ArgumentOutOfRangeException();
  360. }
  361. // CompressionType = LZS then Compressed Size is defined. Else the Uncompressed size should work for
  362. size = compressedSize > 0 ? compressedSize : fi.UncompressedSize > max ? max : fi.UncompressedSize;
  363. if (size > max)
  364. {
  365. throw new InvalidDataException(
  366. $"{nameof(ArchiveWorker)}::{nameof(GetCompressedData)} Expected size ({size}) > ({max})");
  367. }
  368. if (!skipData)
  369. temp = br.ReadBytes(size);
  370. }
  371. return temp;
  372. }
  373. private FI GetFi(int loc)
  374. {
  375. using (var br = new BinaryReader(new FileStream(Archive.FI, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))//File.OpenRead(archive.FI)))
  376. {
  377. br.BaseStream.Seek(loc * 12, SeekOrigin.Begin);
  378. return Extended.ByteArrayToClass<FI>(br.ReadBytes(12));
  379. }
  380. }
  381. private StreamWithRangeValues GetStreamWithRangeValues(string fileName, FI inputFi, int size)
  382. {
  383. void msg() =>
  384. Memory.Log.WriteLine($"{nameof(ArchiveWorker)}::{nameof(GetStreamWithRangeValues)} stream: {fileName}");
  385. if (inputFi != null)
  386. {
  387. }
  388. if (Archive == null) return null;
  389. if (string.IsNullOrWhiteSpace(fileName))
  390. throw new FileNotFoundException("NO FILENAME");
  391. if (IsDir)
  392. {
  393. FindFile(ref fileName);
  394. msg();
  395. var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
  396. if (inputFi == null)
  397. return new StreamWithRangeValues(fs, 0, fs.Length);
  398. return new StreamWithRangeValues(fs, inputFi.Offset, size, inputFi.CompressionType, inputFi.UncompressedSize);
  399. }
  400. Debug.Assert(inputFi == null);
  401. var parentFs = Archive.FS;
  402. if (ArchiveMap != null && ArchiveMap.Count > 1)
  403. {
  404. var fi = ArchiveMap.FindString(ref fileName, out size);
  405. msg();
  406. if (FsArchive == null) return null;
  407. if (fi == null) return FsArchive.GetStreamWithRangeValues(fileName);
  408. var parentFi = FsArchive.ArchiveMap?.FindString(ref parentFs, out var _);
  409. return parentFi == null || parentFi.CompressionType == 0 || (FsArchive is ArchiveZzz)
  410. ? new StreamWithRangeValues(FsArchive.GetStreamWithRangeValues(parentFs), fi.Offset, size,
  411. fi.CompressionType, fi.UncompressedSize)
  412. : new StreamWithRangeValues(new MemoryStream(FsArchive.GetBinaryFile(parentFs, true), false),
  413. fi.Offset, size, fi.CompressionType, fi.UncompressedSize);
  414. }
  415. var loc = -1;
  416. if (File.Exists(Archive.FL))
  417. using (var fs = new FileStream(Archive.FL, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
  418. loc = FindFile(ref fileName, fs); //File.OpenRead(archive.FL));
  419. msg();
  420. // read file list
  421. if (loc == -1)
  422. {
  423. Debug.WriteLine($"ArchiveWorker: NO SUCH FILE! :: {Archive.FL}");
  424. //throw new Exception("ArchiveWorker: No such file!");
  425. }
  426. else
  427. {
  428. var fi = GetFi(loc);
  429. GetCompressedData(fi, out size, true);
  430. return new StreamWithRangeValues(new FileStream(parentFs, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), fi.Offset, size, fi.CompressionType, fi.UncompressedSize);
  431. }
  432. Debug.WriteLine($"ArchiveWorker: NO SUCH FILE! :: Searched {Archive} and could not find {fileName}");
  433. return null;
  434. }
  435. #endregion Methods
  436. }
  437. }