mirror of
https://github.com/ClassiCube/ClassiCube.git
synced 2025-09-29 00:23:31 -04:00
153 lines
5.3 KiB
C#
153 lines
5.3 KiB
C#
// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
|
|
using System;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Text;
|
|
|
|
namespace ClassicalSharp.Textures {
|
|
|
|
public struct ZipEntry {
|
|
public int CompressedDataSize, UncompressedDataSize;
|
|
public int LocalHeaderOffset, CentralHeaderOffset;
|
|
public uint Crc32;
|
|
public string Filename;
|
|
}
|
|
|
|
/// <summary> Extracts files from a stream that represents a .zip file. </summary>
|
|
public sealed class ZipReader {
|
|
|
|
public Action<string, byte[], ZipEntry> ProcessZipEntry;
|
|
public Func<string, bool> ShouldProcessZipEntry;
|
|
public ZipEntry[] entries;
|
|
int index;
|
|
|
|
static Encoding enc = Encoding.ASCII;
|
|
public void Extract(Stream stream) {
|
|
|
|
BinaryReader reader = new BinaryReader(stream);
|
|
reader.BaseStream.Seek(-22, SeekOrigin.End);
|
|
uint sig = reader.ReadUInt32();
|
|
if (sig != 0x06054b50) {
|
|
Utils.LogDebug("Comment in .zip file must be empty");
|
|
return;
|
|
}
|
|
int entriesCount, centralDirectoryOffset;
|
|
ReadEndOfCentralDirectory(reader, out entriesCount, out centralDirectoryOffset);
|
|
entries = new ZipEntry[entriesCount];
|
|
reader.BaseStream.Seek(centralDirectoryOffset, SeekOrigin.Begin);
|
|
|
|
// Read all the central directory entries
|
|
while (true) {
|
|
sig = reader.ReadUInt32();
|
|
if (sig == 0x02014b50) {
|
|
ReadCentralDirectory(reader, entries);
|
|
} else if (sig == 0x06054b50) {
|
|
break;
|
|
} else {
|
|
throw new NotSupportedException("Unsupported signature: " + sig.ToString("X8"));
|
|
}
|
|
}
|
|
|
|
// Now read the local file header entries
|
|
for (int i = 0; i < entriesCount; i++) {
|
|
ZipEntry entry = entries[i];
|
|
reader.BaseStream.Seek(entry.LocalHeaderOffset, SeekOrigin.Begin);
|
|
sig = reader.ReadUInt32();
|
|
if (sig != 0x04034b50)
|
|
throw new NotSupportedException("Unsupported signature: " + sig.ToString("X8"));
|
|
ReadLocalFileHeader(reader, entry);
|
|
}
|
|
entries = null;
|
|
index = 0;
|
|
}
|
|
|
|
void ReadLocalFileHeader(BinaryReader reader, ZipEntry entry) {
|
|
ushort versionNeeded = reader.ReadUInt16();
|
|
ushort flags = reader.ReadUInt16();
|
|
ushort compressionMethod = reader.ReadUInt16();
|
|
reader.ReadUInt32(); // last modified
|
|
reader.ReadUInt32(); // CRC 32
|
|
|
|
int compressedSize = reader.ReadInt32();
|
|
if (compressedSize == 0) compressedSize = entry.CompressedDataSize;
|
|
int uncompressedSize = reader.ReadInt32();
|
|
if (uncompressedSize == 0) uncompressedSize = entry.UncompressedDataSize;
|
|
ushort fileNameLen = reader.ReadUInt16();
|
|
ushort extraFieldLen = reader.ReadUInt16();
|
|
string fileName = enc.GetString(reader.ReadBytes(fileNameLen));
|
|
if (!ShouldProcessZipEntry(fileName)) return;
|
|
|
|
reader.ReadBytes(extraFieldLen);
|
|
if (versionNeeded > 20)
|
|
Utils.LogDebug("May not be able to properly extract a .zip enty with a version later than 2.0");
|
|
|
|
byte[] data = DecompressEntry(reader, compressionMethod, compressedSize, uncompressedSize);
|
|
if (data != null)
|
|
ProcessZipEntry(fileName, data, entry);
|
|
}
|
|
|
|
void ReadCentralDirectory(BinaryReader reader, ZipEntry[] entries) {
|
|
ZipEntry entry;
|
|
entry.CentralHeaderOffset = (int)(reader.BaseStream.Position - 4);
|
|
reader.ReadUInt16(); // OS
|
|
ushort versionNeeded = reader.ReadUInt16();
|
|
ushort flags = reader.ReadUInt16();
|
|
ushort compressionMethod = reader.ReadUInt16();
|
|
reader.ReadUInt32(); // last modified
|
|
uint crc32 = reader.ReadUInt32();
|
|
int compressedSize = reader.ReadInt32();
|
|
int uncompressedSize = reader.ReadInt32();
|
|
ushort fileNameLen = reader.ReadUInt16();
|
|
ushort extraFieldLen = reader.ReadUInt16();
|
|
|
|
ushort fileCommentLen = reader.ReadUInt16();
|
|
ushort diskNum = reader.ReadUInt16();
|
|
ushort internalAttributes = reader.ReadUInt16();
|
|
uint externalAttributes = reader.ReadUInt32();
|
|
int localHeaderOffset = reader.ReadInt32();
|
|
string fileName = enc.GetString(reader.ReadBytes(fileNameLen));
|
|
reader.ReadBytes(extraFieldLen);
|
|
reader.ReadBytes(fileCommentLen);
|
|
|
|
entry.CompressedDataSize = compressedSize;
|
|
entry.UncompressedDataSize = uncompressedSize;
|
|
entry.LocalHeaderOffset = localHeaderOffset;
|
|
entry.Filename = fileName;
|
|
entry.Crc32 = crc32;
|
|
entries[index++] = entry;
|
|
}
|
|
|
|
void ReadEndOfCentralDirectory(BinaryReader reader, out int entriesCount, out int centralDirectoryOffset) {
|
|
ushort diskNum = reader.ReadUInt16();
|
|
ushort diskNumStart = reader.ReadUInt16();
|
|
ushort diskEntries = reader.ReadUInt16();
|
|
entriesCount = reader.ReadUInt16();
|
|
int centralDirectorySize = reader.ReadInt32();
|
|
centralDirectoryOffset = reader.ReadInt32();
|
|
ushort commentLength = reader.ReadUInt16();
|
|
}
|
|
|
|
byte[] DecompressEntry(BinaryReader reader, ushort compressionMethod, int compressedSize, int uncompressedSize) {
|
|
if (compressionMethod == 0) { // Store/Raw
|
|
return reader.ReadBytes(uncompressedSize);
|
|
} else if (compressionMethod == 8) { // Deflate
|
|
byte[] data = new byte[uncompressedSize];
|
|
int index = 0, read = 0;
|
|
|
|
using (DeflateStream ds = new DeflateStream(reader.BaseStream, CompressionMode.Decompress, true)) {
|
|
while (index < uncompressedSize) {
|
|
read = ds.Read(data, index, data.Length - index);
|
|
if (read == 0) break;
|
|
index += read;
|
|
}
|
|
}
|
|
return data;
|
|
} else {
|
|
Utils.LogDebug("Unsupported .zip entry compression method: " + compressionMethod);
|
|
reader.ReadBytes(compressedSize);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|