Simplify ZIP reader api and make it a little bit faster in most cases

This commit is contained in:
UnknownShadow200 2022-09-27 22:08:05 +10:00
parent e6a49460eb
commit 4a9551e661
5 changed files with 106 additions and 138 deletions

View File

@ -1124,20 +1124,49 @@ void ZLib_MakeStream(struct Stream* stream, struct ZLibState* state, struct Stre
/*########################################################################################################################*
*--------------------------------------------------------ZipEntry---------------------------------------------------------*
*--------------------------------------------------------ZipReader--------------------------------------------------------*
*#########################################################################################################################*/
#define ZIP_MAXNAMELEN 512
#define ZIP_MAX_ENTRIES 1024
/* Stores state for reading and processing entries in a .zip archive */
struct ZipState {
struct Stream* source;
Zip_SelectEntry SelectEntry;
Zip_ProcessEntry ProcessEntry;
/* Number of entries selected by SelectEntry */
int usedEntries;
/* Total number of entries in the archive */
int totalEntries;
/* Offset to central directory entries */
cc_uint32 centralDirBeg;
/* Data for each entry in the .zip archive */
struct ZipEntry entries[ZIP_MAX_ENTRIES];
};
static cc_result Zip_ReadLocalFileHeader(struct ZipState* state, struct ZipEntry* entry) {
struct Stream* stream = state->input;
struct Stream* stream = state->source;
cc_uint8 header[26];
cc_string path; char pathBuffer[ZIP_MAXNAMELEN];
cc_uint32 compressedSize, uncompressedSize;
int method, pathLen, extraLen;
cc_string path; char pathBuffer[ZIP_MAXNAMELEN];
struct Stream portion, compStream;
struct InflateState inflate;
cc_result res;
if ((res = Stream_Read(stream, header, sizeof(header)))) return res;
pathLen = Stream_GetU16_LE(&header[22]);
if (pathLen > ZIP_MAXNAMELEN) return ZIP_ERR_FILENAME_LEN;
/* NOTE: ZIP spec says path uses code page 437 for encoding */
path = String_Init(pathBuffer, pathLen, pathLen);
if ((res = Stream_Read(stream, (cc_uint8*)pathBuffer, pathLen))) return res;
if (!state->SelectEntry(&path)) return 0;
extraLen = Stream_GetU16_LE(&header[24]);
/* local file may have extra data before actual data (e.g. ZIP64) */
if ((res = stream->Skip(stream, extraLen))) return res;
method = Stream_GetU16_LE(&header[4]);
compressedSize = Stream_GetU32_LE(&header[14]);
@ -1147,26 +1176,13 @@ static cc_result Zip_ReadLocalFileHeader(struct ZipState* state, struct ZipEntry
if (!compressedSize) compressedSize = entry->CompressedSize;
if (!uncompressedSize) uncompressedSize = entry->UncompressedSize;
pathLen = Stream_GetU16_LE(&header[22]);
extraLen = Stream_GetU16_LE(&header[24]);
if (pathLen > ZIP_MAXNAMELEN) return ZIP_ERR_FILENAME_LEN;
/* NOTE: ZIP spec says path uses code page 437 for encoding */
path = String_Init(pathBuffer, pathLen, pathLen);
if ((res = Stream_Read(stream, (cc_uint8*)pathBuffer, pathLen))) return res;
state->_curEntry = entry;
if (!state->SelectEntry(&path)) return 0;
/* local file may have extra data before actual data (e.g. ZIP64) */
if ((res = stream->Skip(stream, extraLen))) return res;
if (method == 0) {
Stream_ReadonlyPortion(&portion, stream, uncompressedSize);
return state->ProcessEntry(&path, &portion, state);
return state->ProcessEntry(&path, &portion, entry);
} else if (method == 8) {
Stream_ReadonlyPortion(&portion, stream, compressedSize);
Inflate_MakeStream2(&compStream, &inflate, &portion);
return state->ProcessEntry(&path, &compStream, state);
return state->ProcessEntry(&path, &compStream, entry);
} else {
Platform_Log1("Unsupported.zip entry compression method: %i", &method);
/* TODO: Should this be an error */
@ -1175,15 +1191,15 @@ static cc_result Zip_ReadLocalFileHeader(struct ZipState* state, struct ZipEntry
}
static cc_result Zip_ReadCentralDirectory(struct ZipState* state) {
struct Stream* stream = state->input;
struct Stream* stream = state->source;
struct ZipEntry* entry;
cc_uint8 header[42];
cc_string path; char pathBuffer[ZIP_MAXNAMELEN];
int pathLen, extraLen, commentLen;
cc_result res;
if ((res = Stream_Read(stream, header, sizeof(header)))) return res;
if ((res = Stream_Read(stream, header, sizeof(header)))) return res;
pathLen = Stream_GetU16_LE(&header[24]);
if (pathLen > ZIP_MAXNAMELEN) return ZIP_ERR_FILENAME_LEN;
@ -1197,8 +1213,8 @@ static cc_result Zip_ReadCentralDirectory(struct ZipState* state) {
if ((res = stream->Skip(stream, extraLen + commentLen))) return res;
if (!state->SelectEntry(&path)) return 0;
if (state->_usedEntries >= ZIP_MAX_ENTRIES) return ZIP_ERR_TOO_MANY_ENTRIES;
entry = &state->entries[state->_usedEntries++];
if (state->usedEntries >= ZIP_MAX_ENTRIES) return ZIP_ERR_TOO_MANY_ENTRIES;
entry = &state->entries[state->usedEntries++];
entry->CRC32 = Stream_GetU32_LE(&header[12]);
entry->CompressedSize = Stream_GetU32_LE(&header[16]);
@ -1208,14 +1224,14 @@ static cc_result Zip_ReadCentralDirectory(struct ZipState* state) {
}
static cc_result Zip_ReadEndOfCentralDirectory(struct ZipState* state) {
struct Stream* stream = state->input;
struct Stream* stream = state->source;
cc_uint8 header[18];
cc_result res;
if ((res = Stream_Read(stream, header, sizeof(header)))) return res;
state->_totalEntries = Stream_GetU16_LE(&header[6]);
state->_centralDirBeg = Stream_GetU32_LE(&header[12]);
state->totalEntries = Stream_GetU16_LE(&header[6]);
state->centralDirBeg = Stream_GetU32_LE(&header[12]);
return 0;
}
@ -1225,48 +1241,43 @@ enum ZipSig {
ZIP_SIG_LOCALFILEHEADER = 0x04034b50
};
static cc_result Zip_DefaultProcessor(const cc_string* path, struct Stream* data, struct ZipState* s) { return 0; }
static cc_bool Zip_DefaultSelector(const cc_string* path) { return true; }
void Zip_Init(struct ZipState* state, struct Stream* input) {
state->input = input;
state->obj = NULL;
state->ProcessEntry = Zip_DefaultProcessor;
state->SelectEntry = Zip_DefaultSelector;
}
cc_result Zip_Extract(struct ZipState* state) {
struct Stream* stream = state->input;
cc_result Zip_Extract(struct Stream* source, Zip_SelectEntry selector, Zip_ProcessEntry processor) {
struct ZipState state;
cc_uint32 stream_len;
cc_uint32 sig = 0;
int i, count;
cc_result res;
if ((res = stream->Length(stream, &stream_len))) return res;
if ((res = source->Length(source, &stream_len))) return res;
/* At -22 for nearly all zips, but try a bit further back in case of comment */
count = min(257, stream_len);
for (i = 22; i < count; i++) {
res = stream->Seek(stream, stream_len - i);
res = source->Seek(source, stream_len - i);
if (res) return ZIP_ERR_SEEK_END_OF_CENTRAL_DIR;
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
if ((res = Stream_ReadU32_LE(source, &sig))) return res;
if (sig == ZIP_SIG_ENDOFCENTRALDIR) break;
}
state.source = source;
state.SelectEntry = selector;
state.ProcessEntry = processor;
if (sig != ZIP_SIG_ENDOFCENTRALDIR) return ZIP_ERR_NO_END_OF_CENTRAL_DIR;
res = Zip_ReadEndOfCentralDirectory(state);
res = Zip_ReadEndOfCentralDirectory(&state);
if (res) return res;
res = stream->Seek(stream, state->_centralDirBeg);
res = source->Seek(source, state.centralDirBeg);
if (res) return ZIP_ERR_SEEK_CENTRAL_DIR;
state->_usedEntries = 0;
state.usedEntries = 0;
/* Read all the central directory entries */
for (i = 0; i < state->_totalEntries; i++) {
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
for (i = 0; i < state.totalEntries; i++) {
if ((res = Stream_ReadU32_LE(source, &sig))) return res;
if (sig == ZIP_SIG_CENTRALDIR) {
res = Zip_ReadCentralDirectory(state);
res = Zip_ReadCentralDirectory(&state);
if (res) return res;
} else if (sig == ZIP_SIG_ENDOFCENTRALDIR) {
break;
@ -1276,15 +1287,15 @@ cc_result Zip_Extract(struct ZipState* state) {
}
/* Now read the local file header entries */
for (i = 0; i < state->_usedEntries; i++) {
struct ZipEntry* entry = &state->entries[i];
res = stream->Seek(stream, entry->LocalHeaderOffset);
for (i = 0; i < state.usedEntries; i++) {
struct ZipEntry* entry = &state.entries[i];
res = source->Seek(source, entry->LocalHeaderOffset);
if (res) return ZIP_ERR_SEEK_LOCAL_DIR;
if ((res = Stream_ReadU32_LE(stream, &sig))) return res;
if ((res = Stream_ReadU32_LE(source, &sig))) return res;
if (sig != ZIP_SIG_LOCALFILEHEADER) return ZIP_ERR_INVALID_LOCAL_DIR;
res = Zip_ReadLocalFileHeader(state, entry);
res = Zip_ReadLocalFileHeader(&state, entry);
if (res) return res;
}
return 0;

View File

@ -115,40 +115,15 @@ struct ZLibState { struct DeflateState Base; cc_uint32 Adler32; };
/* ZLIB compression is ZLIB header, followed by DEFLATE compressed data, followed by ZLIB footer. */
CC_API void ZLib_MakeStream(struct Stream* stream, struct ZLibState* state, struct Stream* underlying);
/* Minimal data needed to describe an entry in a .zip archive. */
/* Minimal data needed to describe an entry in a .zip archive */
struct ZipEntry { cc_uint32 CompressedSize, UncompressedSize, LocalHeaderOffset, CRC32; };
#define ZIP_MAX_ENTRIES 1024
struct ZipState;
/* Callback function to process the data in a .zip archive entry */
/* Return non-zero to indicate an error and stop further processing */
/* NOTE: data stream MAY NOT be seekable (i.e. entry data might be compressed) */
typedef cc_result (*Zip_ProcessEntry)(const cc_string* path, struct Stream* data, struct ZipEntry* entry);
/* Predicate used to select which entries in a .zip archive get processed */
/* NOTE: returning false entirely skips the entry (avoids pointless seek to entry) */
typedef cc_bool (*Zip_SelectEntry)(const cc_string* path);
/* Stores state for reading and processing entries in a .zip archive. */
struct ZipState {
/* Source of the .zip archive data. Must be seekable. */
struct Stream* input;
/* Callback function to process the data in a .zip archive entry. */
/* Return non-zero to indicate an error and stop further processing. */
/* NOTE: data stream MAY NOT be seekable. (i.e. entry data might be compressed) */
cc_result (*ProcessEntry)(const cc_string* path, struct Stream* data, struct ZipState* state);
/* Predicate used to select which entries in a .zip archive get processed. */
/* NOTE: returning false entirely skips the entry. (avoids pointless seek to entry) */
cc_bool (*SelectEntry)(const cc_string* path);
/* Generic object/pointer for ProcessEntry callback. */
void* obj;
/* (internal) Number of entries selected by SelectEntry. */
int _usedEntries;
/* (internal) Total number of entries in the archive. */
int _totalEntries;
/* (internal) Offset to central directory entries. */
cc_uint32 _centralDirBeg;
/* (internal) Current entry being processed. */
struct ZipEntry* _curEntry;
/* Data for each entry in the .zip archive. */
struct ZipEntry entries[ZIP_MAX_ENTRIES];
};
/* Initialises .zip archive reader state to defaults. */
CC_API void Zip_Init(struct ZipState* state, struct Stream* input);
/* Reads and processes the entries in a .zip archive. */
/* NOTE: Must have been initialised with Zip_Init first. */
CC_API cc_result Zip_Extract(struct ZipState* state);
CC_API cc_result Zip_Extract(struct Stream* source, Zip_SelectEntry selector, Zip_ProcessEntry processor);
#endif

View File

@ -401,7 +401,7 @@ static cc_bool Launcher_SelectZipEntry(const cc_string* path) {
String_CaselessEqualsConst(path, "terrain.png");
}
static cc_result Launcher_ProcessZipEntry(const cc_string* path, struct Stream* data, struct ZipState* s) {
static cc_result Launcher_ProcessZipEntry(const cc_string* path, struct Stream* data, struct ZipEntry* source) {
struct Bitmap bmp;
cc_result res;
@ -432,7 +432,6 @@ static cc_result Launcher_ProcessZipEntry(const cc_string* path, struct Stream*
}
static void ExtractTexturePack(const cc_string* path) {
struct ZipState state;
struct Stream stream;
cc_result res;
@ -440,10 +439,8 @@ static void ExtractTexturePack(const cc_string* path) {
if (res == ReturnCode_FileNotFound) return;
if (res) { Logger_SysWarn(res, "opening texture pack"); return; }
Zip_Init(&state, &stream);
state.SelectEntry = Launcher_SelectZipEntry;
state.ProcessEntry = Launcher_ProcessZipEntry;
res = Zip_Extract(&state);
res = Zip_Extract(&stream,
Launcher_SelectZipEntry, Launcher_ProcessZipEntry);
if (res) { Logger_SysWarn(res, "extracting texture pack"); }
/* No point logging error for closing readonly file */

View File

@ -399,6 +399,7 @@ static cc_result ZipWriter_WritePng(struct Stream* dst, struct ResourceZipEntry*
"# fire\r\n" \
"6 2 0 0 16 32 0"
/* The entries that are required to exist within default.zip */
static struct ResourceZipEntry defaultZipEntries[] = {
/* classic jar files */
{ "char.png", RESOURCE_TYPE_DATA }, { "clouds.png", RESOURCE_TYPE_DATA },
@ -429,13 +430,13 @@ CC_NOINLINE static struct ResourceZipEntry* ZipEntries_Find(const cc_string* nam
return NULL;
}
static cc_result ZipEntry_ExtractData(struct ResourceZipEntry* e, struct ZipState* state, struct Stream* src) {
cc_uint32 size = state->_curEntry->UncompressedSize;
static cc_result ZipEntry_ExtractData(struct ResourceZipEntry* e, struct Stream* data, struct ZipEntry* source) {
cc_uint32 size = source->UncompressedSize;
e->value.data = Mem_TryAlloc(size, 1);
e->size = size;
if (!e->value.data) return ERR_OUT_OF_MEMORY;
return Stream_Read(src, e->value.data, size);
return Stream_Read(data, e->value.data, size);
}
@ -444,6 +445,7 @@ static cc_result ModernPatcher_ExtractFiles(struct HttpRequest* req);
static cc_result TerrainPatcher_Process(struct HttpRequest* req);
static cc_result NewTextures_ExtractGui(struct HttpRequest* req);
/* URLs which data is downloaded from in order to generate the entries in default.zip */
static struct ZipfileSource {
const char* name;
const char* url;
@ -472,34 +474,30 @@ static cc_bool ClassicPatcher_SelectEntry(const cc_string* path) {
return ZipEntries_Find(&name) != NULL;
}
static cc_result ClassicPatcher_ProcessEntry(const cc_string* path, struct Stream* data, struct ZipState* state) {
static cc_result ClassicPatcher_ProcessEntry(const cc_string* path, struct Stream* data, struct ZipEntry* source) {
static const cc_string guiClassicPng = String_FromConst("gui_classic.png");
struct ResourceZipEntry* entry;
struct ResourceZipEntry* e;
cc_string name;
name = *path;
Utils_UNSAFE_GetFilename(&name);
if (String_CaselessEqualsConst(&name, "gui.png")) name = guiClassicPng;
entry = ZipEntries_Find(&name);
e = ZipEntries_Find(&name);
/* terrain.png requires special handling */
if (String_CaselessEqualsConst(path, "terrain.png")) {
return Png_Decode(&entry->value.bmp, data);
return Png_Decode(&e->value.bmp, data);
}
return ZipEntry_ExtractData(entry, state, data);
return ZipEntry_ExtractData(e, data, source);
}
static cc_result ClassicPatcher_ExtractFiles(struct HttpRequest* req) {
struct ZipState zip;
struct Stream src;
Stream_ReadonlyMemory(&src, req->data, req->size);
Zip_Init(&zip, &src);
zip.SelectEntry = ClassicPatcher_SelectEntry;
zip.ProcessEntry = ClassicPatcher_ProcessEntry;
return Zip_Extract(&zip);
return Zip_Extract(&src,
ClassicPatcher_SelectEntry, ClassicPatcher_ProcessEntry);
}
/* the x,y of tiles in terrain.png which get patched */
@ -577,8 +575,8 @@ static cc_result ModernPatcher_MakeAnimations(struct Stream* data) {
return 0;
}
static cc_result ModernPatcher_ProcessEntry(const cc_string* path, struct Stream* data, struct ZipState* state) {
struct ResourceZipEntry* entry;
static cc_result ModernPatcher_ProcessEntry(const cc_string* path, struct Stream* data, struct ZipEntry* source) {
struct ResourceZipEntry* e;
const struct TilePatch* tile;
cc_string name;
@ -587,8 +585,8 @@ static cc_result ModernPatcher_ProcessEntry(const cc_string* path, struct Stream
name = *path;
Utils_UNSAFE_GetFilename(&name);
entry = ZipEntries_Find(&name);
return ZipEntry_ExtractData(entry, state, data);
e = ZipEntries_Find(&name);
return ZipEntry_ExtractData(e, data, source);
}
if (String_CaselessEqualsConst(path, "assets/minecraft/textures/blocks/fire_layer_1.png")) {
@ -600,15 +598,11 @@ static cc_result ModernPatcher_ProcessEntry(const cc_string* path, struct Stream
}
static cc_result ModernPatcher_ExtractFiles(struct HttpRequest* req) {
struct ZipState zip;
struct Stream src;
Stream_ReadonlyMemory(&src, req->data, req->size);
Zip_Init(&zip, &src);
zip.SelectEntry = ModernPatcher_SelectEntry;
zip.ProcessEntry = ModernPatcher_ProcessEntry;
return Zip_Extract(&zip);
return Zip_Extract(&src,
ModernPatcher_SelectEntry, ModernPatcher_ProcessEntry);
}
#ifdef CC_BUILD_MOBILE
@ -617,18 +611,17 @@ static cc_result ModernPatcher_ExtractFiles(struct HttpRequest* req) {
static cc_bool NewTextures_SelectEntry(const cc_string* path) {
return String_CaselessEqualsConst(path, "gui.png") || String_CaselessEqualsConst(path, "touch.png");
}
static cc_result NewTextures_ProcessEntry(const cc_string* path, struct Stream* data, struct ZipState* state) {
struct ResourceZipEntry* entry = ZipEntries_Find(path);
return ZipEntry_ExtractData(entry, state, data);
static cc_result NewTextures_ProcessEntry(const cc_string* path, struct Stream* data, struct ZipEntry* source) {
struct ResourceZipEntry* e = ZipEntries_Find(path);
return ZipEntry_ExtractData(e, data, source);
}
static cc_result NewTextures_ExtractGui(struct HttpRequest* req) {
struct ZipState zip;
struct Stream src;
Stream_ReadonlyMemory(&src, req->data, req->size);
Zip_Init(&zip, &src);
zip.input = &src;
zip.SelectEntry = NewTextures_SelectEntry;
zip.ProcessEntry = NewTextures_ProcessEntry;
return Zip_Extract(&zip);
@ -741,20 +734,17 @@ static cc_bool DefaultZip_SelectEntry(const cc_string* path) {
static void DefaultZip_CountEntries(void) {
static const cc_string path = String_FromConst("texpacks/default.zip");
struct Stream stream;
struct ZipState state;
cc_result res;
res = Stream_OpenFile(&stream, &path);
if (res == ReturnCode_FileNotFound) return;
if (res) { Logger_SysWarn2(res, "opening", &path); return; }
if (res) { Logger_SysWarn(res, "checking default.zip"); return; }
Zip_Init(&state, &stream);
state.SelectEntry = DefaultZip_SelectEntry;
res = Zip_Extract(&state);
stream.Close(&stream);
if (res) Logger_SysWarn(res, "inspecting default.zip");
res = Zip_Extract(&stream, DefaultZip_SelectEntry, NULL);
if (res) Logger_SysWarn2(res, "inspecting", &path);
/* No point logging error for closing readonly file */
(void)stream.Close(&stream);
/* >= in case somehow have say "gui.png", "GUI.png" */
allZipEntriesExist = zipEntriesFound >= Array_Elems(defaultZipEntries);
}

View File

@ -293,20 +293,14 @@ void TexturePack_SetDefault(const cc_string* texPack) {
Options_Set(OPT_DEFAULT_TEX_PACK, texPack);
}
static cc_result ProcessZipEntry(const cc_string* path, struct Stream* stream, struct ZipState* s) {
static cc_bool SelectZipEntry(const cc_string* path) { return true; }
static cc_result ProcessZipEntry(const cc_string* path, struct Stream* stream, struct ZipEntry* source) {
cc_string name = *path;
Utils_UNSAFE_GetFilename(&name);
Event_RaiseEntry(&TextureEvents.FileChanged, stream, &name);
return 0;
}
static cc_result ExtractZip(struct Stream* stream) {
struct ZipState state;
Zip_Init(&state, stream);
state.ProcessEntry = ProcessZipEntry;
return Zip_Extract(&state);
}
static cc_result ExtractPng(struct Stream* stream) {
struct Bitmap bmp;
cc_result res = Png_Decode(&bmp, stream);
@ -328,8 +322,9 @@ static cc_result ExtractFrom(struct Stream* stream, const cc_string* path) {
res = ExtractPng(stream);
if (res == PNG_ERR_INVALID_SIG) {
/* file isn't a .png, probably a .zip then */
res = ExtractZip(stream);
/* file isn't a .png image, probably a .zip archive then */
res = Zip_Extract(stream, SelectZipEntry, ProcessZipEntry);
if (res) Logger_SysWarn2(res, "extracting", path);
} else if (res) {
Logger_SysWarn2(res, "decoding", path);