SanAndreasUnity/Assets/Scripts/Importing/Archive/ImageArchive.cs
2021-01-31 16:45:55 +01:00

133 lines
No EOL
4.4 KiB
C#

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<ImageArchiveEntry> _entries;
private readonly Dictionary<String, ImageArchiveEntry> _fileDict;
private readonly Dictionary<String, List<String>> _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<ImageArchiveEntry>();
_fileDict = new Dictionary<string, ImageArchiveEntry>(StringComparer.InvariantCultureIgnoreCase);
_extDict = new Dictionary<string, List<string>>(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<string>());
}
_extDict[ext].Add(entry.Name);
}
}
public IEnumerable<string> GetAllFiles()
{
return _entries.Select(e => e.Name);
}
public IEnumerable<string> GetFileNamesWithExtension(string ext)
{
return _extDict.ContainsKey(ext) ? _extDict[ext] : Enumerable.Empty<string>();
}
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();
}
}
}