2004-03-03 00:56:51 +01:00
|
|
|
// Zip archiving on top of ZLib.
|
|
|
|
//
|
2004-08-24 19:29:54 +02:00
|
|
|
// Copyright (c) 2003 Jan Wassenberg
|
2004-03-03 00:56:51 +01:00
|
|
|
//
|
|
|
|
// This program is free software; you can redistribute it and/or
|
|
|
|
// modify it under the terms of the GNU General Public License as
|
|
|
|
// published by the Free Software Foundation; either version 2 of the
|
|
|
|
// License, or (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful, but
|
|
|
|
// WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
// General Public License for more details.
|
|
|
|
//
|
|
|
|
// Contact info:
|
|
|
|
// Jan.Wassenberg@stud.uni-karlsruhe.de
|
|
|
|
// http://www.stud.uni-karlsruhe.de/~urkt/
|
|
|
|
|
2004-08-11 22:21:42 +02:00
|
|
|
|
2004-05-08 03:11:51 +02:00
|
|
|
#include "precompiled.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
#include <time.h>
|
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
#include "lib.h"
|
2005-08-12 19:06:53 +02:00
|
|
|
#include "../res.h"
|
2005-01-27 21:00:47 +01:00
|
|
|
#include "byte_order.h"
|
2005-12-28 21:29:22 +01:00
|
|
|
#include "allocators.h"
|
2005-03-18 23:18:34 +01:00
|
|
|
#include "timer.h"
|
2006-01-23 21:05:09 +01:00
|
|
|
#include "self_test.h"
|
|
|
|
#include "archive.h"
|
|
|
|
#include "zip.h"
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
|
2004-08-10 17:56:04 +02:00
|
|
|
// Zip file data structures and signatures
|
2006-01-23 21:05:09 +01:00
|
|
|
static const u32 cdfh_magic = FOURCC_LE('P','K','\1','\2');
|
|
|
|
static const u32 lfh_magic = FOURCC_LE('P','K','\3','\4');
|
|
|
|
static const u32 ecdr_magic = FOURCC_LE('P','K','\5','\6');
|
2004-05-13 15:52:48 +02:00
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
enum ZipCompressionMethod
|
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
ZIP_CM_NONE = 0,
|
|
|
|
ZIP_CM_DEFLATE = 8
|
2005-12-28 21:29:22 +01:00
|
|
|
};
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
#pragma pack(push, 1)
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
struct LFH
|
|
|
|
{
|
|
|
|
u32 magic;
|
|
|
|
u16 x1; // version needed
|
|
|
|
u16 flags;
|
|
|
|
u16 method;
|
2006-01-23 21:05:09 +01:00
|
|
|
u32 fat_mtime; // last modified time (DOS FAT format)
|
2005-12-28 21:29:22 +01:00
|
|
|
u32 crc;
|
|
|
|
u32 csize;
|
|
|
|
u32 ucsize;
|
|
|
|
u16 fn_len;
|
2006-01-23 21:05:09 +01:00
|
|
|
u16 e_len;
|
2005-12-28 21:29:22 +01:00
|
|
|
};
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
const size_t LFH_SIZE = 30;
|
|
|
|
cassert(sizeof(LFH) == LFH_SIZE);
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
|
|
|
|
struct CDFH
|
|
|
|
{
|
|
|
|
u32 magic;
|
|
|
|
u32 x1; // versions
|
|
|
|
u16 flags;
|
|
|
|
u16 method;
|
2006-01-23 21:05:09 +01:00
|
|
|
u32 fat_mtime; // last modified time (DOS FAT format)
|
2005-12-28 21:29:22 +01:00
|
|
|
u32 crc;
|
|
|
|
u32 csize;
|
|
|
|
u32 ucsize;
|
|
|
|
u16 fn_len;
|
|
|
|
u16 e_len;
|
|
|
|
u16 c_len;
|
|
|
|
u32 x2; // spanning
|
|
|
|
u32 x3; // attributes
|
|
|
|
u32 lfh_ofs;
|
|
|
|
};
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
const size_t CDFH_SIZE = 46;
|
|
|
|
cassert(sizeof(CDFH) == CDFH_SIZE);
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
|
|
|
|
struct ECDR
|
|
|
|
{
|
|
|
|
u32 magic;
|
|
|
|
u8 x1[6]; // multiple-disk support
|
|
|
|
u16 cd_entries;
|
|
|
|
u32 cd_size;
|
|
|
|
u32 cd_ofs;
|
|
|
|
u16 comment_len;
|
|
|
|
};
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
const size_t ECDR_SIZE = 22;
|
|
|
|
cassert(sizeof(ECDR) == ECDR_SIZE);
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
#pragma pack(pop)
|
2005-12-28 21:29:22 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
//
|
|
|
|
// timestamp conversion: DOS FAT <-> Unix time_t
|
|
|
|
//
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
static time_t time_t_from_FAT(u32 fat_timedate)
|
|
|
|
{
|
|
|
|
const uint fat_time = bits(fat_timedate, 0, 15);
|
|
|
|
const uint fat_date = bits(fat_timedate, 16, 31);
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
struct tm t; // struct tm format:
|
|
|
|
t.tm_sec = bits(fat_time, 0,4) * 2; // [0,59]
|
|
|
|
t.tm_min = bits(fat_time, 5,10); // [0,59]
|
|
|
|
t.tm_hour = bits(fat_time, 11,15); // [0,23]
|
|
|
|
t.tm_mday = bits(fat_date, 0,4); // [1,31]
|
|
|
|
t.tm_mon = bits(fat_date, 5,8) - 1; // [0,11]
|
|
|
|
t.tm_year = bits(fat_date, 9,15) + 80; // since 1900
|
|
|
|
t.tm_isdst = -1; // unknown - let libc determine
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
// otherwise: totally bogus, and at the limit of 32-bit time_t
|
|
|
|
debug_assert(t.tm_year < 138);
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
time_t ret = mktime(&t);
|
|
|
|
if(ret == (time_t)-1)
|
|
|
|
debug_warn("mktime failed");
|
|
|
|
return ret;
|
|
|
|
}
|
2005-12-28 21:29:22 +01:00
|
|
|
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
static u32 FAT_from_time_t(time_t time)
|
2004-08-10 17:56:04 +02:00
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
// (values are adjusted for DST)
|
|
|
|
struct tm* t = localtime(&time);
|
|
|
|
|
|
|
|
u16 fat_time = 0;
|
|
|
|
fat_time |= (t->tm_sec/2); // 5
|
|
|
|
fat_time |= (t->tm_min) << 5; // 6
|
|
|
|
fat_time |= (t->tm_hour) << 11; // 5
|
|
|
|
|
|
|
|
u16 fat_date = 0;
|
|
|
|
fat_date |= (t->tm_mday); // 5
|
|
|
|
fat_date |= (t->tm_mon+1) << 5; // 4
|
|
|
|
fat_date |= (t->tm_year-80) << 9; // 7
|
2004-05-13 15:52:48 +02:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
u32 fat_timedate = u32_from_u16(fat_date, fat_time);
|
|
|
|
return fat_timedate;
|
2004-05-13 15:52:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// za_*: Zip archive handling
|
|
|
|
// passes the list of files in an archive to lookup.
|
|
|
|
//
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
|
2004-12-18 15:45:04 +01:00
|
|
|
// scan for and return a pointer to a Zip record, or 0 if not found.
|
|
|
|
// <start> is the expected position; we scan from there until EOF for
|
|
|
|
// the given ID (fourcc). <record_size> (includes ID field) bytes must
|
|
|
|
// remain before EOF - this makes sure the record is completely in the file.
|
|
|
|
// used by z_find_ecdr and z_extract_cdfh.
|
2006-01-23 21:05:09 +01:00
|
|
|
static const u8* za_find_id(const u8* buf, size_t size, const void* start, u32 magic, size_t record_size)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
ssize_t bytes_left = (ssize_t)((buf+size) - (u8*)start - record_size);
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
const u8* p = (const u8*)start;
|
2004-12-18 15:45:04 +01:00
|
|
|
// don't increment function argument directly,
|
|
|
|
// so we can warn the user if we had to scan.
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-12-18 15:45:04 +01:00
|
|
|
while(bytes_left-- >= 0)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2004-12-18 15:45:04 +01:00
|
|
|
// found it
|
2005-12-28 21:29:22 +01:00
|
|
|
if(*(u32*)p == magic)
|
2004-12-18 15:45:04 +01:00
|
|
|
{
|
|
|
|
#ifndef NDEBUG
|
|
|
|
if(p != start)
|
2005-10-19 08:29:55 +02:00
|
|
|
debug_warn("archive damaged, but still found next record.");
|
2004-12-18 15:45:04 +01:00
|
|
|
#endif
|
|
|
|
return p;
|
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-12-18 15:45:04 +01:00
|
|
|
p++;
|
|
|
|
// be careful not to increment before comparison;
|
2005-12-28 21:29:22 +01:00
|
|
|
// magic may already be found at <start>.
|
2004-05-18 02:38:39 +02:00
|
|
|
}
|
|
|
|
|
2004-12-18 15:45:04 +01:00
|
|
|
// passed EOF, didn't find it.
|
2006-01-23 21:05:09 +01:00
|
|
|
// note: do not warn - this happens in the initial ECDR search at
|
|
|
|
// EOF if the archive contains a comment field.
|
2004-03-03 00:56:51 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-11-08 00:00:32 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
static LibError za_find_ecdr_impl(File* f, size_t max_scan_amount, ECDR* dst_ecdr)
|
2004-11-08 00:00:32 +01:00
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
const size_t file_size = f->fc.size;
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
// scan the last 66000 bytes of file for ecdr_id signature
|
|
|
|
// (the Zip archive comment field - up to 64k - may follow ECDR).
|
|
|
|
// if the zip file is < 66000 bytes, scan the whole file.
|
|
|
|
size_t scan_amount = MIN(max_scan_amount, file_size);
|
|
|
|
const off_t ofs = (off_t)(file_size - scan_amount);
|
|
|
|
FileIOBuf buf = FILE_BUF_ALLOC;
|
|
|
|
RETURN_ERR(file_io(f, ofs, scan_amount, &buf));
|
2004-12-07 02:19:10 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
LibError ret;
|
|
|
|
const u8* start = (const u8*)buf;
|
|
|
|
const ECDR* ecdr = (const ECDR*)za_find_id(start, scan_amount, start, ecdr_magic, ECDR_SIZE);
|
|
|
|
if(ecdr)
|
|
|
|
{
|
|
|
|
*dst_ecdr = *ecdr;
|
|
|
|
ret = ERR_OK;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
ret = ERR_CORRUPTED;
|
2004-12-18 15:45:04 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
file_buf_free(buf);
|
2004-12-07 02:19:10 +01:00
|
|
|
return ret;
|
2004-11-08 00:00:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
// read the current CDFH. if a valid file, return its filename and ZLoc.
|
|
|
|
// return -1 on error (output params invalid), or 0 on success.
|
|
|
|
// called by za_enum_files, which passes the output to lookup.
|
|
|
|
static LibError za_extract_cdfh(const CDFH* cdfh,
|
|
|
|
ArchiveEntry* ent, size_t& ofs_to_next_cdfh)
|
2005-12-28 21:29:22 +01:00
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
// extract fields from CDFH
|
|
|
|
const u16 zip_method = read_le16(&cdfh->method);
|
|
|
|
const u32 fat_mtime = read_le32(&cdfh->fat_mtime);
|
|
|
|
const u32 csize = read_le32(&cdfh->csize);
|
|
|
|
const u32 ucsize = read_le32(&cdfh->ucsize);
|
|
|
|
const u16 fn_len = read_le16(&cdfh->fn_len);
|
|
|
|
const u16 e_len = read_le16(&cdfh->e_len);
|
|
|
|
const u16 c_len = read_le16(&cdfh->c_len);
|
|
|
|
const u32 lfh_ofs = read_le32(&cdfh->lfh_ofs);
|
|
|
|
const char* fn = (const char*)cdfh+CDFH_SIZE; // not 0-terminated!
|
|
|
|
|
|
|
|
CompressionMethod method;
|
|
|
|
if(zip_method == ZIP_CM_NONE)
|
|
|
|
method = CM_NONE;
|
|
|
|
else if(zip_method == ZIP_CM_DEFLATE)
|
|
|
|
method = CM_DEFLATE;
|
|
|
|
// .. compression method is unknown/unsupported
|
|
|
|
else
|
|
|
|
WARN_RETURN(ERR_UNKNOWN_CMETHOD);
|
|
|
|
// .. it's a directory entry (we only want files)
|
|
|
|
if(!csize && !ucsize)
|
|
|
|
return ERR_NOT_FILE; // don't warn - we just ignore these
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
// write out entry data
|
|
|
|
ent->atom_fn = file_make_unique_fn_copy(fn, fn_len);
|
|
|
|
ent->ofs = lfh_ofs;
|
|
|
|
ent->csize = csize;
|
|
|
|
ent->ucsize = (off_t)ucsize;
|
|
|
|
ent->mtime = time_t_from_FAT(fat_mtime);
|
|
|
|
ent->method = method;
|
|
|
|
ent->flags = ZIP_LFH_FIXUP_NEEDED;
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
// offset to where next CDFH should be (caller will scan for it)
|
|
|
|
ofs_to_next_cdfh = CDFH_SIZE + fn_len + e_len + c_len;
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
return ERR_OK;
|
2005-12-28 21:29:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-12-11 23:23:55 +01:00
|
|
|
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
// find corresponding LFH, needed to calculate file offset
|
|
|
|
// (its extra field may not match that reported by CDFH!).
|
|
|
|
void zip_fixup_lfh(File* f, ArchiveEntry* ent)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
// improbable that this will be in cache - if this file had already
|
|
|
|
// been read, it would have been fixed up. only in cache if this
|
|
|
|
// file is in the same block as a previously read file (i.e. both small)
|
|
|
|
FileIOBuf buf = FILE_BUF_ALLOC;
|
|
|
|
file_io(f, ent->ofs, LFH_SIZE, &buf);
|
|
|
|
const LFH* lfh = (const LFH*)buf;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
debug_assert(lfh->magic == lfh_magic);
|
|
|
|
const size_t fn_len = read_le16(&lfh->fn_len);
|
|
|
|
const size_t e_len = read_le16(&lfh->e_len);
|
2004-12-18 15:45:04 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
ent->ofs += (off_t)(LFH_SIZE + fn_len + e_len);
|
|
|
|
// LFH doesn't have a comment field!
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
file_buf_free(buf);
|
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
LibError zip_populate_archive(Archive* a, File* f)
|
|
|
|
{
|
|
|
|
LibError ret;
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
// check if it's even a Zip file.
|
|
|
|
// the VFS blindly opens files when mounting; it needs to open
|
|
|
|
// all archives, but doesn't know their extension (e.g. ".pk3").
|
|
|
|
const size_t file_size = f->fc.size;
|
|
|
|
// if smaller than this, it's definitely bogus
|
|
|
|
if(file_size < LFH_SIZE+CDFH_SIZE+ECDR_SIZE)
|
|
|
|
WARN_RETURN(ERR_CORRUPTED);
|
2005-03-29 08:27:35 +02:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
// find "End of Central Dir Record" in file.
|
|
|
|
ECDR ecdr;
|
|
|
|
// early out: check expected case (ECDR at EOF; no file comment)
|
|
|
|
ret = za_find_ecdr_impl(f, ECDR_SIZE, &ecdr);
|
|
|
|
// second try: scan last 66000 bytes of file
|
|
|
|
// (the Zip archive comment field - up to 64k - may follow ECDR).
|
|
|
|
// if the zip file is < 66000 bytes, scan the whole file.
|
|
|
|
if(ret < 0)
|
|
|
|
ret = za_find_ecdr_impl(f, 66000u, &ecdr);
|
|
|
|
CHECK_ERR(ret);
|
|
|
|
const uint cd_entries = (uint)read_le16(&ecdr.cd_entries);
|
|
|
|
const off_t cd_ofs = (off_t)read_le32(&ecdr.cd_ofs);
|
|
|
|
const size_t cd_size = (size_t)read_le32(&ecdr.cd_size);
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-08-24 19:29:54 +02:00
|
|
|
// call back with number of entries in archives (an upper bound
|
|
|
|
// for valid files; we're not interested in the directory entries).
|
|
|
|
// we'd have to scan through the central dir to count them out; we'll
|
|
|
|
// just skip them and waste a bit of preallocated memory.
|
2006-01-23 21:05:09 +01:00
|
|
|
RETURN_ERR(archive_allocate_entries(a, cd_entries));
|
|
|
|
|
|
|
|
// iterate through Central Directory
|
|
|
|
FileIOBuf buf = FILE_BUF_ALLOC;
|
|
|
|
RETURN_ERR(file_io(f, cd_ofs, cd_size, &buf));
|
|
|
|
ret = ERR_OK;
|
|
|
|
const CDFH* cdfh = (const CDFH*)buf;
|
|
|
|
size_t ofs_to_next_cdfh = 0;
|
|
|
|
for(uint i = 0; i < cd_entries; i++)
|
2004-12-18 15:45:04 +01:00
|
|
|
{
|
2005-12-28 21:29:22 +01:00
|
|
|
// scan for next CDFH (at or beyond current cdfh position)
|
2006-01-23 21:05:09 +01:00
|
|
|
cdfh = (const CDFH*)((u8*)cdfh + ofs_to_next_cdfh);
|
|
|
|
cdfh = (CDFH*)za_find_id((const u8*)buf, cd_size, (const u8*)cdfh, cdfh_magic, CDFH_SIZE);
|
|
|
|
if(!cdfh) // no (further) CDFH found:
|
2004-08-24 19:29:54 +02:00
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
ret = ERR_CORRUPTED;
|
|
|
|
break;
|
2004-08-24 19:29:54 +02:00
|
|
|
}
|
2005-12-28 21:29:22 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
ArchiveEntry ent;
|
|
|
|
if(za_extract_cdfh(cdfh, &ent, ofs_to_next_cdfh) == ERR_OK)
|
|
|
|
{
|
|
|
|
ret = archive_add_file(a, &ent);
|
|
|
|
if(ret != ERR_OK)
|
|
|
|
break;
|
|
|
|
}
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
2006-01-23 21:05:09 +01:00
|
|
|
file_buf_free(buf);
|
|
|
|
|
|
|
|
return ret;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
/*
|
2006-01-23 21:05:09 +01:00
|
|
|
dont support partial adding, i.e. updating archive with only one file. only build archive from ground up
|
|
|
|
our archive builder always has to arrange everything for optimal performance
|
|
|
|
while testing, can use loose files, so no inconvenience
|
2005-12-28 21:29:22 +01:00
|
|
|
*/
|
2006-01-23 21:05:09 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2004-05-06 19:14:30 +02:00
|
|
|
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
struct ZipArchive
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2005-12-28 21:29:22 +01:00
|
|
|
File f;
|
|
|
|
off_t cur_file_size;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
Pool cdfhs;
|
|
|
|
uint cd_entries;
|
2004-03-03 00:56:51 +01:00
|
|
|
};
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
struct ZipEntry
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2005-12-28 21:29:22 +01:00
|
|
|
char path[PATH_MAX];
|
|
|
|
size_t ucsize;
|
|
|
|
time_t mtime;
|
|
|
|
ZipCompressionMethod method;
|
|
|
|
size_t csize;
|
2006-01-23 21:05:09 +01:00
|
|
|
const void* cdata;
|
2004-03-03 00:56:51 +01:00
|
|
|
};
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
LibError zip_archive_create(const char* zip_filename, ZipArchive* za)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2005-12-28 21:29:22 +01:00
|
|
|
memset(za, 0, sizeof(*za));
|
|
|
|
RETURN_ERR(file_open(zip_filename, 0, &za->f));
|
|
|
|
RETURN_ERR(pool_create(&za->cdfhs, 10*MiB, 0));
|
|
|
|
return ERR_OK;
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
static inline u32 u32_from_size_t(size_t x)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2005-12-28 21:29:22 +01:00
|
|
|
debug_assert(x <= 0xFFFFFFFF);
|
|
|
|
return (u32)(x & 0xFFFFFFFF);
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
static inline u16 u16_from_size_t(size_t x)
|
2005-10-12 06:35:01 +02:00
|
|
|
{
|
2005-12-28 21:29:22 +01:00
|
|
|
debug_assert(x <= 0xFFFF);
|
|
|
|
return (u16)(x & 0xFFFF);
|
2005-10-12 06:35:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
LibError zip_archive_add(ZipArchive* za, const ZipEntry* ze)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
FileIOBuf buf;
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
const char* fn = ze->path;
|
|
|
|
const size_t fn_len = strlen(fn);
|
|
|
|
const size_t ucsize = ze->ucsize;
|
|
|
|
const u32 fat_mtime = FAT_from_time_t(ze->mtime);
|
|
|
|
const u16 method = (u16)ze->method;
|
|
|
|
const size_t csize = ze->csize;
|
2006-01-23 21:05:09 +01:00
|
|
|
const void* cdata = ze->cdata;
|
2005-12-28 21:29:22 +01:00
|
|
|
|
|
|
|
const off_t lfh_ofs = za->cur_file_size;
|
|
|
|
|
|
|
|
// write (LFH, filename, file contents) to archive
|
|
|
|
const size_t lfh_size = sizeof( LFH);
|
|
|
|
const LFH lfh =
|
|
|
|
{
|
|
|
|
lfh_magic,
|
|
|
|
0, // x1
|
|
|
|
0, // flags
|
|
|
|
method,
|
|
|
|
fat_mtime,
|
|
|
|
0, // crc
|
|
|
|
u32_from_size_t(csize),
|
|
|
|
u32_from_size_t(ucsize),
|
|
|
|
u16_from_size_t(fn_len),
|
|
|
|
0 // e_len
|
|
|
|
};
|
2006-01-23 21:05:09 +01:00
|
|
|
buf = (FileIOBuf)&lfh;
|
|
|
|
file_io(&za->f, lfh_ofs, lfh_size, &buf);
|
|
|
|
buf = (FileIOBuf)fn;
|
|
|
|
file_io(&za->f, lfh_ofs+lfh_size, fn_len, &buf);
|
|
|
|
buf = (FileIOBuf)cdata;
|
|
|
|
file_io(&za->f, lfh_ofs+(off_t)(lfh_size+fn_len), csize, &buf);
|
2005-12-28 21:29:22 +01:00
|
|
|
za->cur_file_size += (off_t)(lfh_size+fn_len+csize);
|
|
|
|
|
|
|
|
// append a CDFH to the central dir (in memory)
|
|
|
|
const size_t cdfh_size = sizeof(CDFH);
|
|
|
|
CDFH* cdfh = (CDFH*)pool_alloc(&za->cdfhs, cdfh_size+fn_len);
|
|
|
|
if(cdfh)
|
2004-05-06 19:14:30 +02:00
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
cdfh->magic = cdfh_magic;
|
|
|
|
cdfh->x1 = 0;
|
|
|
|
cdfh->flags = 0;
|
|
|
|
cdfh->method = method;
|
|
|
|
cdfh->fat_mtime = fat_mtime;
|
|
|
|
cdfh->crc = 0;
|
|
|
|
cdfh->csize = u32_from_size_t(csize);
|
|
|
|
cdfh->ucsize = u32_from_size_t(ucsize);
|
|
|
|
cdfh->fn_len = u16_from_size_t(fn_len);
|
|
|
|
cdfh->e_len = 0;
|
|
|
|
cdfh->c_len = 0;
|
|
|
|
cdfh->x2 = 0;
|
|
|
|
cdfh->x3 = 0;
|
|
|
|
cdfh->lfh_ofs = lfh_ofs;
|
2005-12-28 21:29:22 +01:00
|
|
|
memcpy2((char*)cdfh+cdfh_size, fn, fn_len);
|
|
|
|
|
|
|
|
za->cd_entries++;
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
return ERR_OK;
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
LibError zip_archive_finish(ZipArchive* za)
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2005-12-28 21:29:22 +01:00
|
|
|
const size_t cd_size = za->cdfhs.da.pos;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
// append an ECDR to the CDFH list (this allows us to
|
|
|
|
// write out both to the archive file in one burst)
|
|
|
|
ECDR* ecdr = (ECDR*)pool_alloc(&za->cdfhs, sizeof(ECDR));
|
|
|
|
if(!ecdr)
|
|
|
|
return ERR_NO_MEM;
|
|
|
|
ecdr->magic = ecdr_magic;
|
|
|
|
memset(ecdr->x1, 0, sizeof(ecdr->x1));
|
|
|
|
ecdr->cd_entries = za->cd_entries;
|
|
|
|
ecdr->cd_size = (u32)cd_size;
|
|
|
|
ecdr->cd_ofs = za->cur_file_size;
|
|
|
|
ecdr->comment_len = 0;
|
2004-08-24 19:29:54 +02:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
FileIOBuf buf = za->cdfhs.da.base;
|
|
|
|
file_io(&za->f, za->cur_file_size, za->cdfhs.da.pos, &buf);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2005-12-28 21:29:22 +01:00
|
|
|
(void)file_close(&za->f);
|
|
|
|
(void)pool_destroy(&za->cdfhs);
|
2005-12-11 23:23:55 +01:00
|
|
|
return ERR_OK;
|
2004-05-06 19:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// built-in self test
|
|
|
|
//-----------------------------------------------------------------------------
|
2004-12-07 02:19:10 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
#if SELF_TEST_ENABLED
|
|
|
|
namespace test {
|
2004-12-07 02:19:10 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
static void test_fat_timedate_conversion()
|
2005-10-21 09:47:38 +02:00
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
// note: FAT time stores second/2, which means converting may
|
|
|
|
// end up off by 1 second.
|
2005-10-21 09:47:38 +02:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
time_t t = time(0);
|
|
|
|
time_t converted_t = time_t_from_FAT(FAT_from_time_t(t));
|
|
|
|
TEST(abs(converted_t-t) < 2);
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
t++;
|
|
|
|
converted_t = time_t_from_FAT(FAT_from_time_t(t));
|
|
|
|
TEST(abs(converted_t-t) < 2);
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
static void self_test()
|
2004-03-03 00:56:51 +01:00
|
|
|
{
|
2006-01-23 21:05:09 +01:00
|
|
|
test_fat_timedate_conversion();
|
2004-03-03 00:56:51 +01:00
|
|
|
}
|
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
SELF_TEST_RUN;
|
2004-03-03 00:56:51 +01:00
|
|
|
|
2006-01-23 21:05:09 +01:00
|
|
|
} // namespace test
|
|
|
|
#endif // #if SELF_TEST_ENABLED
|