Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/source/lib/file/archive/archive_zip.cpp
/* Copyright (C) 2017 Wildfire Games. | /* Copyright (C) 2020 Wildfire Games. | ||||
* | * | ||||
* Permission is hereby granted, free of charge, to any person obtaining | * Permission is hereby granted, free of charge, to any person obtaining | ||||
* a copy of this software and associated documentation files (the | * a copy of this software and associated documentation files (the | ||||
* "Software"), to deal in the Software without restriction, including | * "Software"), to deal in the Software without restriction, including | ||||
* without limitation the rights to use, copy, modify, merge, publish, | * without limitation the rights to use, copy, modify, merge, publish, | ||||
* distribute, sublicense, and/or sell copies of the Software, and to | * distribute, sublicense, and/or sell copies of the Software, and to | ||||
* permit persons to whom the Software is furnished to do so, subject to | * permit persons to whom the Software is furnished to do so, subject to | ||||
* the following conditions: | * the following conditions: | ||||
▲ Show 20 Lines • Show All 433 Lines • ▼ Show 20 Lines | public: | ||||
virtual Status ReadEntries(ArchiveEntryCallback cb, uintptr_t cbData) | virtual Status ReadEntries(ArchiveEntryCallback cb, uintptr_t cbData) | ||||
{ | { | ||||
// locate and read Central Directory | // locate and read Central Directory | ||||
off_t cd_ofs = 0; | off_t cd_ofs = 0; | ||||
size_t cd_numEntries = 0; | size_t cd_numEntries = 0; | ||||
size_t cd_size = 0; | size_t cd_size = 0; | ||||
RETURN_STATUS_IF_ERR(LocateCentralDirectory(m_file, m_fileSize, cd_ofs, cd_numEntries, cd_size)); | RETURN_STATUS_IF_ERR(LocateCentralDirectory(m_file, m_fileSize, cd_ofs, cd_numEntries, cd_size)); | ||||
UniqueRange buf(io::Allocate(cd_size)); | io::BufferPtr buf(io::Allocate(cd_size)); | ||||
io::Operation op(*m_file.get(), buf.get(), cd_size, cd_ofs); | io::Operation op(*m_file.get(), buf.get(), cd_size, cd_ofs); | ||||
RETURN_STATUS_IF_ERR(io::Run(op)); | RETURN_STATUS_IF_ERR(io::Run(op)); | ||||
// iterate over Central Directory | // iterate over Central Directory | ||||
const u8* pos = (const u8*)buf.get(); | const u8* pos = buf.get(); | ||||
for(size_t i = 0; i < cd_numEntries; i++) | for(size_t i = 0; i < cd_numEntries; i++) | ||||
{ | { | ||||
// scan for next CDFH | // scan for next CDFH | ||||
CDFH* cdfh = (CDFH*)FindRecord((const u8*)buf.get(), cd_size, pos, cdfh_magic, sizeof(CDFH)); | CDFH* cdfh = (CDFH*)FindRecord(buf.get(), cd_size, pos, cdfh_magic, sizeof(CDFH)); | ||||
if(!cdfh) | if(!cdfh) | ||||
WARN_RETURN(ERR::CORRUPTED); | WARN_RETURN(ERR::CORRUPTED); | ||||
const Path relativePathname(cdfh->Pathname()); | const Path relativePathname(cdfh->Pathname()); | ||||
if(!relativePathname.IsDirectory()) | if(!relativePathname.IsDirectory()) | ||||
{ | { | ||||
const OsPath name = relativePathname.Filename(); | const OsPath name = relativePathname.Filename(); | ||||
CFileInfo fileInfo(name, cdfh->USize(), cdfh->MTime()); | CFileInfo fileInfo(name, cdfh->USize(), cdfh->MTime()); | ||||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | static Status ScanForEcdr(const PFile& file, off_t fileSize, u8* buf, size_t maxScanSize, size_t& cd_numEntries, off_t& cd_ofs, size_t& cd_size) | ||||
ecdr->Decompose(cd_numEntries, cd_ofs, cd_size); | ecdr->Decompose(cd_numEntries, cd_ofs, cd_size); | ||||
return INFO::OK; | return INFO::OK; | ||||
} | } | ||||
static Status LocateCentralDirectory(const PFile& file, off_t fileSize, off_t& cd_ofs, size_t& cd_numEntries, size_t& cd_size) | static Status LocateCentralDirectory(const PFile& file, off_t fileSize, off_t& cd_ofs, size_t& cd_numEntries, size_t& cd_size) | ||||
{ | { | ||||
const size_t maxScanSize = 66000u; // see below | const size_t maxScanSize = 66000u; // see below | ||||
UniqueRange buf(io::Allocate(maxScanSize)); | io::BufferPtr buf(io::Allocate(maxScanSize)); | ||||
// expected case: ECDR at EOF; no file comment | // expected case: ECDR at EOF; no file comment | ||||
Status ret = ScanForEcdr(file, fileSize, (u8*)buf.get(), sizeof(ECDR), cd_numEntries, cd_ofs, cd_size); | Status ret = ScanForEcdr(file, fileSize, buf.get(), sizeof(ECDR), cd_numEntries, cd_ofs, cd_size); | ||||
if(ret == INFO::OK) | if(ret == INFO::OK) | ||||
return INFO::OK; | return INFO::OK; | ||||
// worst case: ECDR precedes 64 KiB of file comment | // worst case: ECDR precedes 64 KiB of file comment | ||||
ret = ScanForEcdr(file, fileSize, (u8*)buf.get(), maxScanSize, cd_numEntries, cd_ofs, cd_size); | ret = ScanForEcdr(file, fileSize, buf.get(), maxScanSize, cd_numEntries, cd_ofs, cd_size); | ||||
if(ret == INFO::OK) | if(ret == INFO::OK) | ||||
return INFO::OK; | return INFO::OK; | ||||
// both ECDR scans failed - this is not a valid Zip file. | // both ECDR scans failed - this is not a valid Zip file. | ||||
io::Operation op(*file.get(), buf.get(), sizeof(LFH)); | io::Operation op(*file.get(), buf.get(), sizeof(LFH)); | ||||
RETURN_STATUS_IF_ERR(io::Run(op)); | RETURN_STATUS_IF_ERR(io::Run(op)); | ||||
// the Zip file has an LFH but lacks an ECDR. this can happen if | // the Zip file has an LFH but lacks an ECDR. this can happen if | ||||
// the user hard-exits while an archive is being written. | // the user hard-exits while an archive is being written. | ||||
// notes: | // notes: | ||||
// - return ERR::CORRUPTED so VFS will not include this file. | // - return ERR::CORRUPTED so VFS will not include this file. | ||||
// - we could work around this by scanning all LFHs, but won't bother | // - we could work around this by scanning all LFHs, but won't bother | ||||
// because it'd be slow. | // because it'd be slow. | ||||
// - do not warn - the corrupt archive will be deleted on next | // - do not warn - the corrupt archive will be deleted on next | ||||
// successful archive builder run anyway. | // successful archive builder run anyway. | ||||
if(FindRecord((const u8*)buf.get(), sizeof(LFH), (const u8*)buf.get(), lfh_magic, sizeof(LFH))) | if(FindRecord(buf.get(), sizeof(LFH), buf.get(), lfh_magic, sizeof(LFH))) | ||||
return ERR::CORRUPTED; // NOWARN | return ERR::CORRUPTED; // NOWARN | ||||
// totally bogus | // totally bogus | ||||
else | else | ||||
WARN_RETURN(ERR::ARCHIVE_UNKNOWN_FORMAT); | WARN_RETURN(ERR::ARCHIVE_UNKNOWN_FORMAT); | ||||
} | } | ||||
PFile m_file; | PFile m_file; | ||||
off_t m_fileSize; | off_t m_fileSize; | ||||
▲ Show 20 Lines • Show All 90 Lines • ▼ Show 20 Lines | Status AddFileOrMemory(const CFileInfo& fileInfo, const OsPath& pathnameInArchive, const PFile& file, const u8* data) | ||||
else | else | ||||
{ | { | ||||
method = ZIP_METHOD_DEFLATE; | method = ZIP_METHOD_DEFLATE; | ||||
codec = CreateCompressor_ZLibDeflate(); | codec = CreateCompressor_ZLibDeflate(); | ||||
} | } | ||||
// allocate memory | // allocate memory | ||||
const size_t csizeMax = codec->MaxOutputSize(size_t(usize)); | const size_t csizeMax = codec->MaxOutputSize(size_t(usize)); | ||||
UniqueRange buf(io::Allocate(sizeof(LFH) + pathnameLength + csizeMax)); | io::BufferPtr buf(io::Allocate(sizeof(LFH) + pathnameLength + csizeMax)); | ||||
// read and compress file contents | // read and compress file contents | ||||
size_t csize; u32 checksum; | size_t csize; u32 checksum; | ||||
{ | { | ||||
u8* cdata = (u8*)buf.get() + sizeof(LFH) + pathnameLength; | u8* cdata = buf.get() + sizeof(LFH) + pathnameLength; | ||||
Stream stream(codec); | Stream stream(codec); | ||||
stream.SetOutputBuffer(cdata, csizeMax); | stream.SetOutputBuffer(cdata, csizeMax); | ||||
StreamFeeder streamFeeder(stream); | StreamFeeder streamFeeder(stream); | ||||
if(file) | if(file) | ||||
{ | { | ||||
io::Operation op(*file.get(), 0, usize); | io::Operation op(*file.get(), 0, usize); | ||||
RETURN_STATUS_IF_ERR(io::Run(op, io::Parameters(), streamFeeder)); | RETURN_STATUS_IF_ERR(io::Run(op, io::Parameters(), streamFeeder)); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
RETURN_STATUS_IF_ERR(streamFeeder(data, usize)); | RETURN_STATUS_IF_ERR(streamFeeder(data, usize)); | ||||
} | } | ||||
RETURN_STATUS_IF_ERR(stream.Finish()); | RETURN_STATUS_IF_ERR(stream.Finish()); | ||||
csize = stream.OutSize(); | csize = stream.OutSize(); | ||||
checksum = stream.Checksum(); | checksum = stream.Checksum(); | ||||
} | } | ||||
// build LFH | // build LFH | ||||
{ | { | ||||
LFH* lfh = (LFH*)buf.get(); | LFH* lfh = reinterpret_cast<LFH*>(buf.get()); | ||||
lfh->Init(fileInfo, (off_t)csize, method, checksum, pathnameInArchive); | lfh->Init(fileInfo, (off_t)csize, method, checksum, pathnameInArchive); | ||||
} | } | ||||
// append a CDFH to the central directory (in memory) | // append a CDFH to the central directory (in memory) | ||||
const off_t ofs = m_fileSize; | const off_t ofs = m_fileSize; | ||||
const size_t prev_pos = m_cdfhPool.da.pos; // (required to determine padding size) | const size_t prev_pos = m_cdfhPool.da.pos; // (required to determine padding size) | ||||
const size_t cdfhSize = sizeof(CDFH) + pathnameLength; | const size_t cdfhSize = sizeof(CDFH) + pathnameLength; | ||||
CDFH* cdfh = (CDFH*)pool_alloc(&m_cdfhPool, cdfhSize); | CDFH* cdfh = (CDFH*)pool_alloc(&m_cdfhPool, cdfhSize); | ||||
▲ Show 20 Lines • Show All 57 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator