lovr/src/core/zip.c

85 lines
2.6 KiB
C

#include "zip.h"
#include <string.h>
static uint16_t readu16(const uint8_t* p) { uint16_t x; memcpy(&x, p, sizeof(x)); return x; }
static uint32_t readu32(const uint8_t* p) { uint32_t x; memcpy(&x, p, sizeof(x)); return x; }
bool zip_open(zip_state* zip) {
const uint8_t* p = zip->data + zip->size - 22;
if (zip->size < 22 || readu32(p) != 0x06054b50) {
return false;
}
zip->count = readu16(p + 10);
zip->cursor = readu32(p + 16);
zip->base = 0;
if (zip->cursor + 4 > zip->size) {
return false;
}
// See if the central directory starts where the endOfCentralDirectory said it would.
// If it doesn't, then it might be a self-extracting archive with broken offsets (common).
// In this case, assume the central directory is directly adjacent to the endOfCentralDirectory,
// located at (offsetOfEndOfCentralDirectory (aka size - 22) - sizeOfCentralDirectory).
// If we find a central directory there, then compute a "base" offset that equals the difference
// between where it is and where it was supposed to be, and apply this offset to everything else.
if (readu32(zip->data + zip->cursor) != 0x02014b50) {
size_t offsetOfEndOfCentralDirectory = zip->size - 22;
size_t sizeOfCentralDirectory = readu32(p + 12);
size_t centralDirectoryOffset = offsetOfEndOfCentralDirectory - sizeOfCentralDirectory;
if (sizeOfCentralDirectory > offsetOfEndOfCentralDirectory || centralDirectoryOffset + 4 > zip->size) {
return false;
}
if (readu32(zip->data + centralDirectoryOffset) != 0x02014b50) {
return false;
}
zip->base = centralDirectoryOffset - zip->cursor;
zip->cursor = centralDirectoryOffset;
}
return true;
}
bool zip_next(zip_state* zip, zip_file* file) {
const uint8_t* p = zip->data + zip->cursor;
if (zip->cursor + 46 > zip->size || readu32(p) != 0x02014b50) {
return false;
}
file->mtime = readu16(p + 12);
file->mdate = readu16(p + 14);
file->csize = readu32(p + 20);
file->size = readu32(p + 24);
file->length = readu16(p + 28);
file->offset = readu32(p + 42) + zip->base;
file->name = (const char*) (p + 46);
zip->cursor += 46 + file->length + readu16(p + 30) + readu16(p + 32);
return zip->cursor < zip->size;
}
void* zip_load(zip_state* zip, size_t offset, bool* compressed) {
if (zip->size < 30 || offset > zip->size - 30) {
return NULL;
}
uint8_t* p = zip->data + offset;
if (readu32(p) != 0x04034b50) {
return NULL;
}
uint16_t compression = readu16(p + 8);
if (compression != 0 && compression != 8) {
return false;
}
*compressed = (compression == 8);
uint32_t skip = readu16(p + 26) + readu16(p + 28);
return p + 30 + skip;
}