using SanAndreasUnity.Utilities; using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace SanAndreasUnity.Importing.Archive { public class ImageArchive : IArchive, IDisposable { private struct ImageArchiveEntry { public readonly UInt32 Offset; public readonly UInt32 Size; public UInt32 End { get { return Offset + Size; } } public readonly String Name; public ImageArchiveEntry(BinaryReader reader) { Offset = reader.ReadUInt32() << 11; var sizeSecond = (uint) reader.ReadUInt16(); var sizeFirst = (uint) reader.ReadUInt16(); Size = (sizeFirst != 0) ? sizeFirst << 11 : sizeSecond << 11; Name = reader.ReadString(24); } } public static ImageArchive Load(String filePath) { UnityEngine.Debug.Log("Loading image archive: " + filePath); return new ImageArchive(new FileStream(filePath, FileMode.Open, FileAccess.Read)); } private readonly Stream _stream; private readonly List _entries; private readonly Dictionary _fileDict; private readonly Dictionary> _extDict; public readonly String Version; public readonly UInt32 EntryCount; public int NumLoadedEntries => _entries.Count; private ImageArchive(Stream stream) { _stream = stream; var fileStream = _stream as FileStream; string archiveName = fileStream != null ? Path.GetFileName(fileStream.Name) : string.Empty; long archiveSize = fileStream?.Length ?? 0; var reader = new BinaryReader(stream); Version = new String(reader.ReadChars(4)); EntryCount = reader.ReadUInt32(); _entries = new List(); _fileDict = new Dictionary(StringComparer.InvariantCultureIgnoreCase); _extDict = new Dictionary>(StringComparer.InvariantCultureIgnoreCase); if (Version != "VER2") { UnityEngine.Debug.LogWarning($"Archive {archiveName} has unsupported version {Version}, entry count {EntryCount}, size {archiveSize}"); return; } for (var i = 0; i < EntryCount; ++i) { var entry = new ImageArchiveEntry(reader); if (_fileDict.ContainsKey(entry.Name)) { UnityEngine.Debug.LogWarning($"file entry (name: {entry.Name}, size: {entry.Size}) already exists in archive {archiveName}"); continue; } _entries.Add(entry); _fileDict.Add(entry.Name, entry); var ext = Path.GetExtension(entry.Name); if (ext == null) { UnityEngine.Debug.LogWarning("No file extension for: \"" + entry.Name + "\""); continue; } if (!_extDict.ContainsKey(ext)) { _extDict.Add(ext, new List()); } _extDict[ext].Add(entry.Name); } } public IEnumerable GetAllFiles() { return _entries.Select(e => e.Name); } public IEnumerable GetFileNamesWithExtension(string ext) { return _extDict.ContainsKey(ext) ? _extDict[ext] : Enumerable.Empty(); } public bool ContainsFile(string name) { return _fileDict.ContainsKey(name); } public string GetFileName(long offset) { if (_fileDict.Count == 0) return null; if (offset < _entries.First().Offset) return null; if (offset >= _entries.Last().End) return null; return _entries.FirstOrDefault(x => x.Offset <= offset && x.End > offset).Name; } public Stream ReadFile(String name) { var entry = _fileDict[name]; var stream = new FrameStream(_stream, entry.Offset, entry.Size); stream.Seek(0, SeekOrigin.Begin); return stream; } public void Dispose() { _stream.Dispose(); } } }