diff --git a/src/Deflate.c b/src/Deflate.c index 9f78e7734..0e8970501 100644 --- a/src/Deflate.c +++ b/src/Deflate.c @@ -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; diff --git a/src/Deflate.h b/src/Deflate.h index f2c1ca06b..4aef10b55 100644 --- a/src/Deflate.h +++ b/src/Deflate.h @@ -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 diff --git a/src/Launcher.c b/src/Launcher.c index 5caa2dfc4..b3e8f1d13 100644 --- a/src/Launcher.c +++ b/src/Launcher.c @@ -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 */ diff --git a/src/Resources.c b/src/Resources.c index 13e603fa1..bbe96e62f 100644 --- a/src/Resources.c +++ b/src/Resources.c @@ -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); } diff --git a/src/TexturePack.c b/src/TexturePack.c index 8d1a5b0e0..8c313b448 100644 --- a/src/TexturePack.c +++ b/src/TexturePack.c @@ -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);