diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/Resources.c |
initial commit
Diffstat (limited to 'src/Resources.c')
-rw-r--r-- | src/Resources.c | 1269 |
1 files changed, 1269 insertions, 0 deletions
diff --git a/src/Resources.c b/src/Resources.c new file mode 100644 index 0000000..f42afed --- /dev/null +++ b/src/Resources.c @@ -0,0 +1,1269 @@ +#include "Resources.h" +#ifdef CC_BUILD_RESOURCES +#include "Funcs.h" +#include "String.h" +#include "Constants.h" +#include "Deflate.h" +#include "Stream.h" +#include "Platform.h" +#include "Launcher.h" +#include "Utils.h" +#include "Vorbis.h" +#include "Errors.h" +#include "Logger.h" +#include "LWeb.h" +#include "Http.h" +#include "Game.h" +#include "Audio.h" + +/* Represents a set of assets/resources */ +/* E.g. music set, sounds set, textures set */ +struct AssetSet { + /* Checks whether all assets in this asset set exist on disc */ + void (*CheckExistence)(void); + /* Counts the number of size of missing assets that need to be downloaded */ + void (*CountMissing)(void); + /* Begins asynchronously downloading the missing assets in this asset set */ + void (*DownloadAssets)(void); + /* Returns the name for the associated request, or NULL otherwise */ + const char* (*GetRequestName)(int reqID); + /* Checks if any assets have been downloaded, and processes them if so */ + void (*CheckStatus)(void); + /* Resets state and frees and allocated memory */ + void (*ResetState)(void); +}; + +int Resources_MissingCount, Resources_MissingSize; +cc_bool Resources_MissingRequired; + +union ResourceValue { + cc_uint8* data; + struct Bitmap bmp; +}; +struct ResourceZipEntry { + const char* filename; + /* zip data */ + cc_uint32 type : 3; + cc_uint32 size : 29; + union ResourceValue value; + cc_uint32 offset, crc32; +}; +#define RESOURCE_TYPE_DATA 1 +#define RESOURCE_TYPE_PNG 2 +#define RESOURCE_TYPE_CONST 3 +#define RESOURCE_TYPE_SOUND 4 + +static CC_NOINLINE cc_bool Fetcher_Get(int reqID, struct HttpRequest* item); +CC_NOINLINE static struct ResourceZipEntry* ZipEntries_Find(const cc_string* name); +static cc_result ZipEntry_ExtractData(struct ResourceZipEntry* e, struct Stream* data, struct ZipEntry* source); + + +/*########################################################################################################################* +*------------------------------------------------------Utility functions -------------------------------------------------* +*#########################################################################################################################*/ +static void ZipFile_InspectEntries(const cc_string* path, Zip_SelectEntry selector) { + struct Stream stream; + cc_result res; + + res = Stream_OpenFile(&stream, path); + if (res == ReturnCode_FileNotFound) return; + if (res) { Logger_SysWarn2(res, "opening", path); return; } + + res = Zip_Extract(&stream, selector, NULL); + if (res) Logger_SysWarn2(res, "inspecting", path); + + /* No point logging error for closing readonly file */ + (void)stream.Close(&stream); +} + +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(data, e->value.data, size); +} + + +/*########################################################################################################################* +*-----------------------------------------------------Sound asset writing ------------------------------------------------* +*#########################################################################################################################*/ +#define WAV_FourCC(a, b, c, d) (((cc_uint32)a << 24) | ((cc_uint32)b << 16) | ((cc_uint32)c << 8) | (cc_uint32)d) +#define WAV_HDR_SIZE 44 + +/* Fixes up the .WAV header after having written all samples */ +static cc_result SoundPatcher_FixupHeader(struct Stream* s, struct VorbisState* ctx, cc_uint32 offset, cc_uint32 len) { + cc_uint8 header[WAV_HDR_SIZE]; + cc_result res = s->Seek(s, offset); + if (res) return res; + + Stream_SetU32_BE(header + 0, WAV_FourCC('R','I','F','F')); + Stream_SetU32_LE(header + 4, len - 8); + Stream_SetU32_BE(header + 8, WAV_FourCC('W','A','V','E')); + Stream_SetU32_BE(header + 12, WAV_FourCC('f','m','t',' ')); + Stream_SetU32_LE(header + 16, 16); /* fmt chunk size */ + Stream_SetU16_LE(header + 20, 1); /* PCM audio format */ + Stream_SetU16_LE(header + 22, ctx->channels); + Stream_SetU32_LE(header + 24, ctx->sampleRate); + + Stream_SetU32_LE(header + 28, ctx->sampleRate * ctx->channels * 2); /* byte rate */ + Stream_SetU16_LE(header + 32, ctx->channels * 2); /* block align */ + Stream_SetU16_LE(header + 34, 16); /* bits per sample */ + Stream_SetU32_BE(header + 36, WAV_FourCC('d','a','t','a')); + Stream_SetU32_LE(header + 40, len - WAV_HDR_SIZE); + + return Stream_Write(s, header, WAV_HDR_SIZE); +} + +/* Decodes all samples, then produces a .WAV file from them */ +static cc_result SoundPatcher_WriteWav(struct Stream* s, struct VorbisState* ctx) { + cc_int16* samples; + cc_uint32 begOffset; + cc_uint32 len = WAV_HDR_SIZE; + cc_result res; + int count; + + if ((res = s->Position(s, &begOffset))) return res; + + /* reuse context here for a temp garbage header */ + if ((res = Stream_Write(s, (const cc_uint8*)ctx, WAV_HDR_SIZE))) return res; + if ((res = Vorbis_DecodeHeaders(ctx))) return res; + + samples = (cc_int16*)Mem_TryAlloc(ctx->blockSizes[1] * ctx->channels, 2); + if (!samples) return ERR_OUT_OF_MEMORY; + + for (;;) { + res = Vorbis_DecodeFrame(ctx); + if (res == ERR_END_OF_STREAM) { + /* reached end of samples, so done */ + res = SoundPatcher_FixupHeader(s, ctx, begOffset, len); + break; + } + if (res) break; + + count = Vorbis_OutputFrame(ctx, samples); + len += count * 2; + +#ifdef CC_BUILD_BIGENDIAN + Utils_SwapEndian16(samples, count); +#endif + res = Stream_Write(s, (cc_uint8*)samples, count * 2); + if (res) break; + } + + Mem_Free(samples); + if (!res) res = s->Seek(s, begOffset + len); + return res; +} + +/* Converts an OGG sound to a WAV sound for faster decoding later */ +static cc_result SoundPatcher_Save(struct Stream* s, struct ResourceZipEntry* e) { + struct OggState* ogg = NULL; + struct VorbisState* ctx = NULL; + struct Stream src; + cc_result res; + + ogg = (struct OggState*)Mem_TryAlloc(1, sizeof(struct OggState)); + if (!ogg) { res = ERR_OUT_OF_MEMORY; goto cleanup; } + + ctx = (struct VorbisState*)Mem_TryAlloc(1, sizeof(struct VorbisState)); + if (!ctx) { res = ERR_OUT_OF_MEMORY; goto cleanup; } + + Stream_ReadonlyMemory(&src, e->value.data, e->size); + + Ogg_Init(ogg, &src); + Vorbis_Init(ctx); + ctx->source = ogg; + res = SoundPatcher_WriteWav(s, ctx); + +cleanup: + if (ctx) Vorbis_Free(ctx); + Mem_Free(ctx); + Mem_Free(ogg); + return res; +} + + +/*########################################################################################################################* +*------------------------------------------------------Zip entry writer---------------------------------------------------* +*#########################################################################################################################*/ +static void GetCurrentZipDate(int* modTime, int* modDate) { + struct DateTime now; + DateTime_CurrentLocal(&now); + + *modTime = (now.second / 2) | (now.minute << 5) | (now.hour << 11); + *modDate = (now.day) | (now.month << 5) | ((now.year - 1980) << 9); +} + +static cc_result ZipWriter_LocalFile(struct Stream* s, struct ResourceZipEntry* e) { + int filenameLen = String_Length(e->filename); + cc_uint8 header[30 + STRING_SIZE]; + cc_result res; + int modTime, modDate; + + GetCurrentZipDate(&modTime, &modDate); + if ((res = s->Position(s, &e->offset))) return res; + + Stream_SetU32_LE(header + 0, 0x04034b50); /* signature */ + Stream_SetU16_LE(header + 4, 20); /* version needed */ + Stream_SetU16_LE(header + 6, 0); /* bitflags */ + Stream_SetU16_LE(header + 8, 0); /* compression method */ + Stream_SetU16_LE(header + 10, modTime); /* last modified */ + Stream_SetU16_LE(header + 12, modDate); /* last modified */ + + Stream_SetU32_LE(header + 14, e->crc32); /* CRC32 */ + Stream_SetU32_LE(header + 18, e->size); /* Compressed size */ + Stream_SetU32_LE(header + 22, e->size); /* Uncompressed size */ + + Stream_SetU16_LE(header + 26, filenameLen); /* name length */ + Stream_SetU16_LE(header + 28, 0); /* extra field length */ + + Mem_Copy(header + 30, e->filename, filenameLen); + return Stream_Write(s, header, 30 + filenameLen); +} + +static cc_result ZipWriter_CentralDir(struct Stream* s, struct ResourceZipEntry* e) { + int filenameLen = String_Length(e->filename); + cc_uint8 header[46 + STRING_SIZE]; + int modTime, modDate; + GetCurrentZipDate(&modTime, &modDate); + + Stream_SetU32_LE(header + 0, 0x02014b50); /* signature */ + Stream_SetU16_LE(header + 4, 20); /* version */ + Stream_SetU16_LE(header + 6, 20); /* version needed */ + Stream_SetU16_LE(header + 8, 0); /* bitflags */ + Stream_SetU16_LE(header + 10, 0); /* compression method */ + Stream_SetU16_LE(header + 12, modTime); /* last modified */ + Stream_SetU16_LE(header + 14, modDate); /* last modified */ + + Stream_SetU32_LE(header + 16, e->crc32); /* CRC32 */ + Stream_SetU32_LE(header + 20, e->size); /* compressed size */ + Stream_SetU32_LE(header + 24, e->size); /* uncompressed size */ + + Stream_SetU16_LE(header + 28, filenameLen); /* name length */ + Stream_SetU16_LE(header + 30, 0); /* extra field length */ + Stream_SetU16_LE(header + 32, 0); /* file comment length */ + Stream_SetU16_LE(header + 34, 0); /* disk number */ + Stream_SetU16_LE(header + 36, 0); /* internal attributes */ + Stream_SetU32_LE(header + 38, 0); /* external attributes */ + Stream_SetU32_LE(header + 42, e->offset); /* local header offset */ + + Mem_Copy(header + 46, e->filename, filenameLen); + return Stream_Write(s, header, 46 + filenameLen); +} + +static cc_result ZipWriter_EndOfCentralDir(struct Stream* s, int numEntries, + cc_uint32 centralDirBeg, cc_uint32 centralDirEnd) { + cc_uint8 header[22]; + + Stream_SetU32_LE(header + 0, 0x06054b50); /* signature */ + Stream_SetU16_LE(header + 4, 0); /* disk number */ + Stream_SetU16_LE(header + 6, 0); /* disk number of start */ + Stream_SetU16_LE(header + 8, numEntries); /* disk entries */ + Stream_SetU16_LE(header + 10, numEntries); /* total entries */ + Stream_SetU32_LE(header + 12, centralDirEnd - centralDirBeg); /* central dir size */ + Stream_SetU32_LE(header + 16, centralDirBeg); /* central dir start */ + Stream_SetU16_LE(header + 20, 0); /* comment length */ + return Stream_Write(s, header, 22); +} + +static cc_result ZipWriter_FixupLocalFile(struct Stream* s, struct ResourceZipEntry* e) { + int filenameLen = String_Length(e->filename); + cc_uint8 tmp[2048]; + cc_uint32 dataBeg, dataEnd; + cc_uint32 i, crc, toRead, read; + cc_result res; + + dataBeg = e->offset + 30 + filenameLen; + if ((res = s->Position(s, &dataEnd))) return res; + e->size = dataEnd - dataBeg; + + /* work out the CRC 32 */ + crc = 0xffffffffUL; + if ((res = s->Seek(s, dataBeg))) return res; + + for (; dataBeg < dataEnd; dataBeg += read) { + toRead = dataEnd - dataBeg; + toRead = min(toRead, sizeof(tmp)); + + if ((res = s->Read(s, tmp, toRead, &read))) return res; + if (!read) return ERR_END_OF_STREAM; + + for (i = 0; i < read; i++) { + crc = Utils_Crc32Table[(crc ^ tmp[i]) & 0xFF] ^ (crc >> 8); + } + } + e->crc32 = crc ^ 0xffffffffUL; + + /* then fixup the header */ + if ((res = s->Seek(s, e->offset))) return res; + if ((res = ZipWriter_LocalFile(s, e))) return res; + return s->Seek(s, dataEnd); +} + +static cc_result ZipWriter_WriteData(struct Stream* dst, struct ResourceZipEntry* e) { + cc_uint8* data = e->value.data; + cc_result res; + e->crc32 = Utils_CRC32(data, e->size); + + if ((res = ZipWriter_LocalFile(dst, e))) return res; + return Stream_Write(dst, data, e->size); +} + +static cc_result ZipWriter_WritePng(struct Stream* dst, struct ResourceZipEntry* e) { + struct Bitmap* src = &e->value.bmp; + cc_result res; + + if ((res = ZipWriter_LocalFile(dst, e))) return res; + if ((res = Png_Encode(src, dst, NULL, true, NULL))) return res; + return ZipWriter_FixupLocalFile(dst, e); +} + +static cc_result ZipWriter_WriteWav(struct Stream* dst, struct ResourceZipEntry* e) { + cc_result res; + + if ((res = ZipWriter_LocalFile(dst, e))) return res; + if ((res = SoundPatcher_Save(dst, e))) return res; + return ZipWriter_FixupLocalFile(dst, e); +} + + +/*########################################################################################################################* +*------------------------------------------------------Zip file writer----------------------------------------------------* +*#########################################################################################################################*/ +static cc_result ZipFile_WriteEntries(struct Stream* s, struct ResourceZipEntry* entries, int numEntries) { + struct ResourceZipEntry* e; + cc_uint32 beg, end; + int i; + cc_result res; + + for (i = 0; i < numEntries; i++) + { + e = &entries[i]; + + if (e->type == RESOURCE_TYPE_PNG) { + if ((res = ZipWriter_WritePng(s, e))) return res; + } else if (e->type == RESOURCE_TYPE_SOUND) { + if ((res = ZipWriter_WriteWav(s, e))) return res; + } else { + if ((res = ZipWriter_WriteData(s, e))) return res; + } + } + + if ((res = s->Position(s, &beg))) return res; + for (i = 0; i < numEntries; i++) + { + if ((res = ZipWriter_CentralDir(s, &entries[i]))) return res; + } + + if ((res = s->Position(s, &end))) return res; + return ZipWriter_EndOfCentralDir(s, numEntries, beg, end); +} + +static void ZipFile_Create(const cc_string* path, struct ResourceZipEntry* entries, int numEntries) { + struct Stream s; + cc_result res; + + res = Stream_CreateFile(&s, path); + if (res) { + Logger_SysWarn2(res, "creating", path); return; + } + + res = ZipFile_WriteEntries(&s, entries, numEntries); + if (res) Logger_SysWarn2(res, "making", path); + + res = s.Close(&s); + if (res) Logger_SysWarn2(res, "closing", path); +} + + +/*########################################################################################################################* +*---------------------------------------------------------Music assets----------------------------------------------------* +*#########################################################################################################################*/ +static struct MusicAsset { + const char* name; + const char* hash; + short size; + cc_bool downloaded; + int reqID; +} musicAssets[] = { + { "calm1.ogg", "50a59a4f56e4046701b758ddbb1c1587efa4cadf", 2472 }, + { "calm2.ogg", "74da65c99aa578486efa7b69983d3533e14c0d6e", 1931 }, + { "calm3.ogg", "14ae57a6bce3d4254daa8be2b098c2d99743cc3f", 2181 }, + { "hal1.ogg", "df1ff11b79757432c5c3f279e5ecde7b63ceda64", 1926 }, + { "hal2.ogg", "ceaaaa1d57dfdfbb0bd4da5ea39628b42897a687", 1714 }, + { "hal3.ogg", "dd85fb564e96ee2dbd4754f711ae9deb08a169f9", 1879 }, + { "hal4.ogg", "5e7d63e75c6e042f452bc5e151276911ef92fed8", 2499 } +}; + +static void MusicAssets_CheckExistence(void) { + cc_string path; char pathBuffer[FILENAME_SIZE]; + int i; + String_InitArray(path, pathBuffer); + + for (i = 0; i < Array_Elems(musicAssets); i++) + { + path.length = 0; + String_Format1(&path, "audio/%c", musicAssets[i].name); + + musicAssets[i].downloaded = File_Exists(&path); + } +} + +static void MusicAssets_CountMissing(void) { + int i; + for (i = 0; i < Array_Elems(musicAssets); i++) + { + if (musicAssets[i].downloaded) continue; + + Resources_MissingSize += musicAssets[i].size; + Resources_MissingCount++; + } +} + + +/*########################################################################################################################* +*-----------------------------------------------------Music asset fetching -----------------------------------------------* +*#########################################################################################################################*/ +CC_NOINLINE static int MusicAsset_Download(const char* hash) { + cc_string url; char urlBuffer[URL_MAX_SIZE]; + + String_InitArray(url, urlBuffer); + String_Format3(&url, "https://resources.download.minecraft.net/%r%r/%c", + &hash[0], &hash[1], hash); + return Http_AsyncGetData(&url, 0); +} + +static void MusicAssets_DownloadAssets(void) { + int i; + for (i = 0; i < Array_Elems(musicAssets); i++) + { + if (musicAssets[i].downloaded) continue; + musicAssets[i].reqID = MusicAsset_Download(musicAssets[i].hash); + } +} + +static const char* MusicAssets_GetRequestName(int reqID) { + int i; + for (i = 0; i < Array_Elems(musicAssets); i++) + { + if (reqID == musicAssets[i].reqID) return musicAssets[i].name; + } + return NULL; +} + + +/*########################################################################################################################* +*----------------------------------------------------Music asset processing ----------------------------------------------* +*#########################################################################################################################*/ +static void MusicAsset_Save(const char* name, struct HttpRequest* req) { + cc_string path; char pathBuffer[STRING_SIZE]; + cc_result res; + + String_InitArray(path, pathBuffer); + String_Format1(&path, "audio/%c", name); + + res = Stream_WriteAllTo(&path, req->data, req->size); + if (res) Logger_SysWarn(res, "saving music file"); +} + +static void MusicAsset_Check(struct MusicAsset* music) { + struct HttpRequest item; + if (!Fetcher_Get(music->reqID, &item)) return; + + music->downloaded = true; + MusicAsset_Save(music->name, &item); + HttpRequest_Free(&item); +} + +static void MusicAssets_CheckStatus(void) { + int i; + for (i = 0; i < Array_Elems(musicAssets); i++) + { + if (musicAssets[i].downloaded) continue; + MusicAsset_Check(&musicAssets[i]); + } +} + +static void MusicAssets_ResetState(void) { +} + +static const struct AssetSet mccMusicAssetSet = { + MusicAssets_CheckExistence, + MusicAssets_CountMissing, + MusicAssets_DownloadAssets, + MusicAssets_GetRequestName, + MusicAssets_CheckStatus, + MusicAssets_ResetState +}; + + +/*########################################################################################################################* +*---------------------------------------------------------Sound assets----------------------------------------------------* +*#########################################################################################################################*/ +static struct SoundAsset { + const char* filename; + const char* hash; + int reqID, size; + void* data; +} soundAssets[] = { + { "dig_cloth1.wav", "5fd568d724ba7d53911b6cccf5636f859d2662e8" }, { "dig_cloth2.wav", "56c1d0ac0de2265018b2c41cb571cc6631101484" }, + { "dig_cloth3.wav", "9c63f2a3681832dc32d206f6830360bfe94b5bfc" }, { "dig_cloth4.wav", "55da1856e77cfd31a7e8c3d358e1f856c5583198" }, + { "dig_grass1.wav", "41cbf5dd08e951ad65883854e74d2e034929f572" }, { "dig_grass2.wav", "86cb1bb0c45625b18e00a64098cd425a38f6d3f2" }, + { "dig_grass3.wav", "f7d7e5c7089c9b45fa5d1b31542eb455fad995db" }, { "dig_grass4.wav", "c7b1005d4926f6a2e2387a41ab1fb48a72f18e98" }, + { "dig_gravel1.wav", "e8b89f316f3e9989a87f6e6ff12db9abe0f8b09f" }, { "dig_gravel2.wav", "c3b3797d04cb9640e1d3a72d5e96edb410388fa3" }, + { "dig_gravel3.wav", "48f7e1bb098abd36b9760cca27b9d4391a23de26" }, { "dig_gravel4.wav", "7bf3553a4fe41a0078f4988a13d6e1ed8663ef4c" }, + { "dig_sand1.wav", "9e59c3650c6c3fc0a475f1b753b2fcfef430bf81" }, { "dig_sand2.wav", "0fa4234797f336ada4e3735e013e44d1099afe57" }, + { "dig_sand3.wav", "c75589cc0087069f387de127dd1499580498738e" }, { "dig_sand4.wav", "37afa06f97d58767a1cd1382386db878be1532dd" }, + { "dig_snow1.wav", "e9bab7d3d15541f0aaa93fad31ad37fd07e03a6c" }, { "dig_snow2.wav", "5887d10234c4f244ec5468080412f3e6ef9522f3" }, + { "dig_snow3.wav", "a4bc069321a96236fde04a3820664cc23b2ea619" }, { "dig_snow4.wav", "e26fa3036cdab4c2264ceb19e1cd197a2a510227" }, + { "dig_stone1.wav", "4e094ed8dfa98656d8fec52a7d20c5ee6098b6ad" }, { "dig_stone2.wav", "9c92f697142ae320584bf64c0d54381d59703528" }, + { "dig_stone3.wav", "8f23c02475d388b23e5faa680eafe6b991d7a9d4" }, { "dig_stone4.wav", "363545a76277e5e47538b2dd3a0d6aa4f7a87d34" }, + { "dig_wood1.wav", "9bc2a84d0aa98113fc52609976fae8fc88ea6333" }, { "dig_wood2.wav", "98102533e6085617a2962157b4f3658f59aea018" }, + { "dig_wood3.wav", "45b2aef7b5049e81b39b58f8d631563fadcc778b" }, { "dig_wood4.wav", "dc66978374a46ab2b87db6472804185824868095" }, + { "dig_glass1.wav", "7274a2231ed4544a37e599b7b014e589e5377094" }, { "dig_glass2.wav", "87c47bda3645c68f18a49e83cbf06e5302d087ff" }, + { "dig_glass3.wav", "ad7d770b7fff3b64121f75bd60cecfc4866d1cd6" }, + + { "step_cloth1.wav", "5fd568d724ba7d53911b6cccf5636f859d2662e8" }, { "step_cloth2.wav", "56c1d0ac0de2265018b2c41cb571cc6631101484" }, + { "step_cloth3.wav", "9c63f2a3681832dc32d206f6830360bfe94b5bfc" }, { "step_cloth4.wav", "55da1856e77cfd31a7e8c3d358e1f856c5583198" }, + { "step_grass1.wav", "41cbf5dd08e951ad65883854e74d2e034929f572" }, { "step_grass2.wav", "86cb1bb0c45625b18e00a64098cd425a38f6d3f2" }, + { "step_grass3.wav", "f7d7e5c7089c9b45fa5d1b31542eb455fad995db" }, { "step_grass4.wav", "c7b1005d4926f6a2e2387a41ab1fb48a72f18e98" }, + { "step_gravel1.wav", "e8b89f316f3e9989a87f6e6ff12db9abe0f8b09f" }, { "step_gravel2.wav", "c3b3797d04cb9640e1d3a72d5e96edb410388fa3" }, + { "step_gravel3.wav", "48f7e1bb098abd36b9760cca27b9d4391a23de26" }, { "step_gravel4.wav", "7bf3553a4fe41a0078f4988a13d6e1ed8663ef4c" }, + { "step_sand1.wav", "9e59c3650c6c3fc0a475f1b753b2fcfef430bf81" }, { "step_sand2.wav", "0fa4234797f336ada4e3735e013e44d1099afe57" }, + { "step_sand3.wav", "c75589cc0087069f387de127dd1499580498738e" }, { "step_sand4.wav", "37afa06f97d58767a1cd1382386db878be1532dd" }, + { "step_snow1.wav", "e9bab7d3d15541f0aaa93fad31ad37fd07e03a6c" }, { "step_snow2.wav", "5887d10234c4f244ec5468080412f3e6ef9522f3" }, + { "step_snow3.wav", "a4bc069321a96236fde04a3820664cc23b2ea619" }, { "step_snow4.wav", "e26fa3036cdab4c2264ceb19e1cd197a2a510227" }, + { "step_stone1.wav", "4e094ed8dfa98656d8fec52a7d20c5ee6098b6ad" }, { "step_stone2.wav", "9c92f697142ae320584bf64c0d54381d59703528" }, + { "step_stone3.wav", "8f23c02475d388b23e5faa680eafe6b991d7a9d4" }, { "step_stone4.wav", "363545a76277e5e47538b2dd3a0d6aa4f7a87d34" }, + { "step_wood1.wav", "9bc2a84d0aa98113fc52609976fae8fc88ea6333" }, { "step_wood2.wav", "98102533e6085617a2962157b4f3658f59aea018" }, + { "step_wood3.wav", "45b2aef7b5049e81b39b58f8d631563fadcc778b" }, { "step_wood4.wav", "dc66978374a46ab2b87db6472804185824868095" } +}; +static cc_bool allSoundsExist; + +static void SoundAssets_ResetState(void) { + int i; + allSoundsExist = false; + + for (i = 0; i < Array_Elems(soundAssets); i++) + { + Mem_Free(soundAssets[i].data); + soundAssets[i].data = NULL; + soundAssets[i].size = 0; + } +} + + +/*########################################################################################################################* +*-----------------------------------------------------Sound asset checking -----------------------------------------------* +*#########################################################################################################################*/ +static int soundEntriesFound; + +static struct SoundAsset* SoundAssest_Find(const cc_string* name) { + struct SoundAsset* a; + int i; + + for (i = 0; i < Array_Elems(soundAssets); i++) + { + a = &soundAssets[i]; + if (String_CaselessEqualsConst(name, a->filename)) return a; + } + return NULL; +} + +static cc_bool SoundAssets_CheckEntry(const cc_string* path) { + cc_string name = *path; + Utils_UNSAFE_GetFilename(&name); + + if (SoundAssest_Find(&name)) soundEntriesFound++; + return false; +} + +static void SoundAssets_CheckExistence(void) { + soundEntriesFound = 0; + ZipFile_InspectEntries(&Sounds_ZipPathMC, SoundAssets_CheckEntry); + + /* >= in case somehow have say "gui.png", "GUI.png" */ + allSoundsExist = soundEntriesFound >= Array_Elems(soundAssets); +} + +static void SoundAssets_CountMissing(void) { + if (allSoundsExist) return; + + Resources_MissingCount += Array_Elems(soundAssets); + Resources_MissingSize += 417; +} + + +/*########################################################################################################################* +*----------------------------------------------------Sound asset generation ----------------------------------------------* +*#########################################################################################################################*/ +static void SoundAsset_CreateZip(void) { + struct ResourceZipEntry entries[Array_Elems(soundAssets)]; + int i; + + for (i = 0; i < Array_Elems(soundAssets); i++) + { + entries[i].filename = soundAssets[i].filename; + entries[i].type = RESOURCE_TYPE_SOUND; + + entries[i].value.data = soundAssets[i].data; + entries[i].size = soundAssets[i].size; + } + + ZipFile_Create(&Sounds_ZipPathMC, entries, Array_Elems(soundAssets)); + SoundAssets_ResetState(); +} + + +/*########################################################################################################################* +*-----------------------------------------------------Sound asset fetching -----------------------------------------------* +*#########################################################################################################################*/ +#define SoundAsset_Download(hash) MusicAsset_Download(hash) + +static void SoundAssets_DownloadAssets(void) { + int i; + for (i = 0; i < Array_Elems(soundAssets); i++) + { + if (allSoundsExist) continue; + soundAssets[i].reqID = SoundAsset_Download(soundAssets[i].hash); + } +} + +static const char* SoundAssets_GetRequestName(int reqID) { + int i; + for (i = 0; i < Array_Elems(soundAssets); i++) + { + if (reqID == soundAssets[i].reqID) return soundAssets[i].filename; + } + return NULL; +} + +static void SoundAsset_Check(struct SoundAsset* sound, int i) { + struct HttpRequest item; + if (!Fetcher_Get(sound->reqID, &item)) return; + + sound->data = item.data; + sound->size = item.size; + item.data = NULL; + HttpRequest_Free(&item); + + if (i == Array_Elems(soundAssets) - 1) + SoundAsset_CreateZip(); +} + +static void SoundAssets_CheckStatus(void) { + int i; + for (i = 0; i < Array_Elems(soundAssets); i++) + { + SoundAsset_Check(&soundAssets[i], i); + } +} + +static const struct AssetSet mccSoundAssetSet = { + SoundAssets_CheckExistence, + SoundAssets_CountMissing, + SoundAssets_DownloadAssets, + SoundAssets_GetRequestName, + SoundAssets_CheckStatus, + SoundAssets_ResetState +}; + + +/*########################################################################################################################* +*------------------------------------------------------CC texture assets--------------------------------------------------* +*#########################################################################################################################*/ +static const cc_string ccTexPack = String_FromConst("texpacks/classicube.zip"); +static cc_bool ccTexturesExist, ccTexturesDownloaded; +static int ccTexturesReqID; + +static void CCTextures_CheckExistence(void) { + ccTexturesExist = File_Exists(&ccTexPack); +} + +static void CCTextures_CountMissing(void) { + if (ccTexturesExist) return; + + Resources_MissingCount++; + Resources_MissingSize += 83; + Resources_MissingRequired = true; +} + + +/*########################################################################################################################* +*--------------------------------------------------CC texture assets fetching --------------------------------------------* +*#########################################################################################################################*/ +static void CCTextures_DownloadAssets(void) { + static cc_string url = String_FromConst(RESOURCE_SERVER "/default.zip"); + if (ccTexturesExist) return; + + ccTexturesReqID = Http_AsyncGetData(&url, 0); +} + +static const char* CCTextures_GetRequestName(int reqID) { + return reqID == ccTexturesReqID ? "ClassiCube textures" : NULL; +} + + +/*########################################################################################################################* +*-------------------------------------------------CC texture assets processing -------------------------------------------* +*#########################################################################################################################*/ +/* Android needs the touch.png */ +/* TODO: Unify both android and desktop platforms to both just extract from default.zip */ +static cc_bool CCTextures_SelectEntry(const cc_string* path) { + return String_CaselessEqualsConst(path, "touch.png"); +} + +static cc_result CCTextures_ProcessEntry(const cc_string* path, struct Stream* data, struct ZipEntry* source) { + struct ResourceZipEntry* e = ZipEntries_Find(path); + if (!e) return 0; /* TODO exteact on PC too */ + + return ZipEntry_ExtractData(e, data, source); +} + +static cc_result CCTextures_ExtractZip(struct HttpRequest* req) { + struct Stream src; + cc_result res; + + Stream_ReadonlyMemory(&src, req->data, req->size); + if ((res = Zip_Extract(&src, CCTextures_SelectEntry, CCTextures_ProcessEntry))) return res; + + return Stream_WriteAllTo(&ccTexPack, req->data, req->size); +} + +static void CCTextures_CheckStatus(void) { + struct HttpRequest item; + cc_result res; + + if (ccTexturesDownloaded) return; + if (!Fetcher_Get(ccTexturesReqID, &item)) return; + + ccTexturesDownloaded = true; + res = CCTextures_ExtractZip(&item); + if (res) Logger_SysWarn(res, "saving ClassiCube textures"); + + HttpRequest_Free(&item); +} + +static void CCTextures_ResetState(void) { + ccTexturesExist = false; + ccTexturesDownloaded = false; +} + +static const struct AssetSet ccTexsAssetSet = { + CCTextures_CheckExistence, + CCTextures_CountMissing, + CCTextures_DownloadAssets, + CCTextures_GetRequestName, + CCTextures_CheckStatus, + CCTextures_ResetState +}; + + +/*########################################################################################################################* +*----------------------------------------------------default.zip resources------------------------------------------------* +*#########################################################################################################################*/ +#define ANIMS_TXT \ +"# This file defines the animations used in a texture pack for ClassiCube.\r\n" \ +"# Each line is in the format : <TileX> <TileY> <FrameX> <FrameY> <Frame size> <Frames count> <Tick delay>\r\n" \ +"# - TileX and TileY are the coordinates of the tile in terrain.png that will be replaced by the animation frames.\r\n" \ +"# Essentially, TileX and TileY are the remainder and quotient of an ID in F10 menu divided by 16\r\n" \ +"# For instance, obsidian texture(37) has TileX of 5, and TileY of 2\r\n" \ +"# - FrameX and FrameY are the pixel coordinates of the first animation frame in animations.png.\r\n" \ +"# - Frame Size is the size in pixels of an animation frame.\r\n" \ +"# - Frames count is the number of used frames. The first frame is located at\r\n" \ +"# (FrameX, FrameY), the second one at (FrameX + FrameSize, FrameY) and so on.\r\n" \ +"# - Tick delay is the number of ticks a frame doesn't change. For instance, delay of 0\r\n" \ +"# means that the tile would be replaced every tick, while delay of 2 means\r\n" \ +"# 'replace with frame 1, don't change frame, don't change frame, replace with frame 2'.\r\n" \ +"# NOTE: If a file called 'uselavaanim' is in the texture pack, the game instead generates the lava texture animation.\r\n" \ +"# NOTE: If a file called 'usewateranim' is in the texture pack, the game instead generates the water texture animation.\r\n" \ +"\r\n" \ +"# 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 */ + { "terrain.png", RESOURCE_TYPE_PNG }, { "particles.png", RESOURCE_TYPE_DATA }, + { "clouds.png", RESOURCE_TYPE_DATA }, { "rain.png", RESOURCE_TYPE_DATA }, + { "char.png", RESOURCE_TYPE_DATA }, { "default.png", RESOURCE_TYPE_DATA }, + { "icons.png", RESOURCE_TYPE_DATA }, { "gui_classic.png", RESOURCE_TYPE_DATA }, + { "creeper.png", RESOURCE_TYPE_DATA }, { "pig.png", RESOURCE_TYPE_DATA }, + { "sheep.png", RESOURCE_TYPE_DATA }, { "sheep_fur.png", RESOURCE_TYPE_DATA }, + { "skeleton.png", RESOURCE_TYPE_DATA }, { "spider.png", RESOURCE_TYPE_DATA }, + { "zombie.png", RESOURCE_TYPE_DATA }, + /* other files */ + { "snow.png", RESOURCE_TYPE_DATA }, { "chicken.png", RESOURCE_TYPE_DATA }, + { "gui.png", RESOURCE_TYPE_DATA }, { "animations.png", RESOURCE_TYPE_PNG }, + { "animations.txt", RESOURCE_TYPE_CONST, sizeof(ANIMS_TXT) - 1, (cc_uint8*)ANIMS_TXT }, +#ifdef CC_BUILD_MOBILE + { "touch.png", RESOURCE_TYPE_DATA } +#endif +}; + +CC_NOINLINE static struct ResourceZipEntry* ZipEntries_Find(const cc_string* name) { + struct ResourceZipEntry* e; + int i; + + for (i = 0; i < Array_Elems(defaultZipEntries); i++) + { + e = &defaultZipEntries[i]; + if (String_CaselessEqualsConst(name, e->filename)) return e; + } + return NULL; +} + + +static cc_result ClassicPatcher_ExtractFiles(struct HttpRequest* req); +static cc_result ModernPatcher_ExtractFiles(struct HttpRequest* req); +static cc_result TerrainPatcher_Process(struct HttpRequest* req); +static cc_result NewTextures_ExtractGui(struct HttpRequest* req); +static cc_result Classic0023Patcher_OldGold(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; + cc_result (*Process)(struct HttpRequest* req); + short size; + cc_bool downloaded; + int reqID; +} defaultZipSources[] = { + { "classic jar", "http://launcher.mojang.com/mc/game/c0.30_01c/client/54622801f5ef1bcc1549a842c5b04cb5d5583005/client.jar", ClassicPatcher_ExtractFiles, 291 }, + { "1.6.2 jar", "http://launcher.mojang.com/mc/game/1.6.2/client/b6cb68afde1d9cf4a20cbf27fa90d0828bf440a4/client.jar", ModernPatcher_ExtractFiles, 4621 }, + { "terrain.png patch", RESOURCE_SERVER "/terrain-patch2.png", TerrainPatcher_Process, 7 }, + { "gui.png patch", "https://wlodekm.nekoweb.org/assets/misc/ch/gui.png", NewTextures_ExtractGui, 21 }, + { "classic gold", "https://classic.minecraft.net/assets/textures/gold.png", Classic0023Patcher_OldGold, 1 }, /* NOTE: this must be the last entry */ +}; +static int numDefaultZipSources, numDefaultZipProcessed; + +static void MCCTextures_ResetState(void) { + int i; + for (i = 0; i < Array_Elems(defaultZipEntries); i++) + { + if (defaultZipEntries[i].type == RESOURCE_TYPE_CONST) continue; + + /* can reuse value.data for value.bmp case too */ + Mem_Free(defaultZipEntries[i].value.data); + defaultZipEntries[i].value.data = NULL; + defaultZipEntries[i].size = 0; + } +} + + +/*########################################################################################################################* +*------------------------------------------------default.zip entry generators---------------------------------------------* +*#########################################################################################################################*/ +static cc_bool ClassicPatcher_SelectEntry(const cc_string* path) { + cc_string name = *path; + Utils_UNSAFE_GetFilename(&name); + return ZipEntries_Find(&name) != NULL; +} + +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* e; + cc_string name; + + name = *path; + Utils_UNSAFE_GetFilename(&name); + if (String_CaselessEqualsConst(&name, "gui.png")) name = guiClassicPng; + + e = ZipEntries_Find(&name); + + /* terrain.png requires special handling */ + if (String_CaselessEqualsConst(path, "terrain.png")) { + return Png_Decode(&e->value.bmp, data); + } + return ZipEntry_ExtractData(e, data, source); +} + +static cc_result ClassicPatcher_ExtractFiles(struct HttpRequest* req) { + struct Stream src; + Stream_ReadonlyMemory(&src, req->data, req->size); + + return Zip_Extract(&src, + ClassicPatcher_SelectEntry, ClassicPatcher_ProcessEntry); +} + +static void PatchTerrainTile(struct Bitmap* src, int srcX, int srcY, int tileX, int tileY) { + static const cc_string terrainPng = String_FromConst("terrain.png"); + struct ResourceZipEntry* entry = ZipEntries_Find(&terrainPng); + struct Bitmap* dst = &entry->value.bmp; + + Bitmap_UNSAFE_CopyBlock(srcX, srcY, tileX * 16, tileY * 16, src, dst, 16); +} + + +/* the x,y of tiles in terrain.png which get patched */ +static const struct TilePatch { const char* name; cc_uint8 x1,y1, x2,y2; } modern_tiles[12] = { + { "assets/minecraft/textures/blocks/sandstone_bottom.png", 9,3 }, + { "assets/minecraft/textures/blocks/sandstone_normal.png", 9,2 }, + { "assets/minecraft/textures/blocks/sandstone_top.png", 9,1, }, + { "assets/minecraft/textures/blocks/quartz_block_lines_top.png", 10,3, 10,1 }, + { "assets/minecraft/textures/blocks/quartz_block_lines.png", 10,2 }, + { "assets/minecraft/textures/blocks/stonebrick.png", 4,3 }, + { "assets/minecraft/textures/blocks/snow.png", 2,3 }, + { "assets/minecraft/textures/blocks/wool_colored_blue.png", 3,5 }, + { "assets/minecraft/textures/blocks/wool_colored_brown.png", 2,5 }, + { "assets/minecraft/textures/blocks/wool_colored_cyan.png", 4,5 }, + { "assets/minecraft/textures/blocks/wool_colored_green.png", 1,5 }, + { "assets/minecraft/textures/blocks/wool_colored_pink.png", 0,5 } +}; + +CC_NOINLINE static const struct TilePatch* ModernPatcher_GetTile(const cc_string* path) { + int i; + for (i = 0; i < Array_Elems(modern_tiles); i++) { + if (String_CaselessEqualsConst(path, modern_tiles[i].name)) return &modern_tiles[i]; + } + return NULL; +} + +static cc_result ModernPatcher_PatchTile(struct Stream* data, const struct TilePatch* tile) { + struct Bitmap bmp; + cc_result res; + + if ((res = Png_Decode(&bmp, data))) return res; + PatchTerrainTile(&bmp, 0, 0, tile->x1, tile->y1); + + /* only quartz needs copying to two tiles */ + if (tile->y2) PatchTerrainTile(&bmp, 0, 0, tile->x2, tile->y2); + + Mem_Free(bmp.scan0); + return 0; +} + + +static cc_bool ModernPatcher_SelectEntry(const cc_string* path) { + return + String_CaselessEqualsConst(path, "assets/minecraft/textures/environment/snow.png") || + String_CaselessEqualsConst(path, "assets/minecraft/textures/entity/chicken.png") || + String_CaselessEqualsConst(path, "assets/minecraft/textures/blocks/fire_layer_1.png") || + ModernPatcher_GetTile(path) != NULL; +} + +static cc_result ModernPatcher_MakeAnimations(struct Stream* data) { + static const cc_string animsPng = String_FromConst("animations.png"); + struct ResourceZipEntry* entry; + struct Bitmap* anim; + struct Bitmap bmp; + cc_result res; + int i; + + entry = ZipEntries_Find(&animsPng); + anim = &entry->value.bmp; + Bitmap_TryAllocate(anim, 512, 16); + + if (!anim->scan0) return ERR_OUT_OF_MEMORY; + if ((res = Png_Decode(&bmp, data))) return res; + + for (i = 0; i < 512; i += 16) { + Bitmap_UNSAFE_CopyBlock(0, i, i, 0, &bmp, anim, 16); + } + + Mem_Free(bmp.scan0); + return 0; +} + +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; + + if (String_CaselessEqualsConst(path, "assets/minecraft/textures/environment/snow.png") + || String_CaselessEqualsConst(path, "assets/minecraft/textures/entity/chicken.png")) { + name = *path; + Utils_UNSAFE_GetFilename(&name); + + e = ZipEntries_Find(&name); + return ZipEntry_ExtractData(e, data, source); + } + + if (String_CaselessEqualsConst(path, "assets/minecraft/textures/blocks/fire_layer_1.png")) { + return ModernPatcher_MakeAnimations(data); + } + + tile = ModernPatcher_GetTile(path); + return ModernPatcher_PatchTile(data, tile); +} + +static cc_result ModernPatcher_ExtractFiles(struct HttpRequest* req) { + struct Stream src; + Stream_ReadonlyMemory(&src, req->data, req->size); + + return Zip_Extract(&src, + ModernPatcher_SelectEntry, ModernPatcher_ProcessEntry); +} + + +static cc_result TerrainPatcher_Process(struct HttpRequest* req) { + struct Bitmap bmp; + struct Stream src; + cc_result res; + + Stream_ReadonlyMemory(&src, req->data, req->size); + if ((res = Png_Decode(&bmp, &src))) return res; + + PatchTerrainTile(&bmp, 0,0, 3,3); + PatchTerrainTile(&bmp, 16,0, 6,3); + PatchTerrainTile(&bmp, 32,0, 6,2); + + PatchTerrainTile(&bmp, 0,16, 5,3); + PatchTerrainTile(&bmp, 16,16, 6,5); + PatchTerrainTile(&bmp, 32,16, 11,0); + + Mem_Free(bmp.scan0); + return 0; +} + +static cc_result NewTextures_ExtractGui(struct HttpRequest* req) { + static const cc_string guiPng = String_FromConst("gui.png"); + struct ResourceZipEntry* entry = ZipEntries_Find(&guiPng); + + entry->value.data = req->data; + entry->size = req->size; + + req->data = NULL; /* don't free memory yet */ + return 0; +} + +static cc_result Classic0023Patcher_OldGold(struct HttpRequest* req) { + struct Bitmap bmp; + struct Stream src; + cc_result res; + + Stream_ReadonlyMemory(&src, req->data, req->size); + if ((res = Png_Decode(&bmp, &src))) return res; + + PatchTerrainTile(&bmp, 0,0, 8,1); + PatchTerrainTile(&bmp, 0,0, 8,2); + PatchTerrainTile(&bmp, 0,0, 8,3); + + Mem_Free(bmp.scan0); + return 0; +} + + +/*########################################################################################################################* +*-----------------------------------------------Minecraft Classic texture assets------------------------------------------* +*#########################################################################################################################*/ +static cc_bool allZipEntriesExist; +static int zipEntriesFound; + +static cc_bool DefaultZip_SelectEntry(const cc_string* path) { + cc_string name = *path; + Utils_UNSAFE_GetFilename(&name); + + if (ZipEntries_Find(&name)) zipEntriesFound++; + return false; +} + +static void MCCTextures_CheckExistence(void) { + cc_string path = String_FromReadonly(Game_Version.DefaultTexpack); + zipEntriesFound = 0; + + ZipFile_InspectEntries(&path, DefaultZip_SelectEntry); + /* >= in case somehow have say "gui.png", "GUI.png" */ + allZipEntriesExist = zipEntriesFound >= Array_Elems(defaultZipEntries); + + /* Need touch.png from ClassiCube textures */ + if (!allZipEntriesExist) ccTexturesExist = false; +} + +static void MCCTextures_CountMissing(void) { + int i; + if (allZipEntriesExist) return; + + numDefaultZipSources = Array_Elems(defaultZipSources); + /* old gold texture only needed in 0.0.23 and earlier */ + if (Game_Version.Version > VERSION_0023) numDefaultZipSources--; + + for (i = 0; i < numDefaultZipSources; i++) { + Resources_MissingCount++; + Resources_MissingSize += defaultZipSources[i].size; + } +} + + +/*########################################################################################################################* +*------------------------------------------Minecraft Classic texture assets fetching -------------------------------------* +*#########################################################################################################################*/ +static void MCCTextures_DownloadAssets(void) { + cc_string url; + int i; + if (allZipEntriesExist) return; + numDefaultZipProcessed = 0; + + for (i = 0; i < numDefaultZipSources; i++) + { + url = String_FromReadonly(defaultZipSources[i].url); + defaultZipSources[i].reqID = Http_AsyncGetData(&url, 0); + defaultZipSources[i].downloaded = false; + } +} + +static const char* MCCTextures_GetRequestName(int reqID) { + int i; + for (i = 0; i < numDefaultZipSources; i++) + { + if (reqID == defaultZipSources[i].reqID) return defaultZipSources[i].name; + } + return NULL; +} + + +/*########################################################################################################################* +*------------------------------------------Minecraft Classic texture assets processing -----------------------------------* +*#########################################################################################################################*/ +static void MCCTextures_CreateDefaultZip(void) { + cc_string path = String_FromReadonly(Game_Version.DefaultTexpack); + ZipFile_Create(&path, defaultZipEntries, Array_Elems(defaultZipEntries)); + MCCTextures_ResetState(); +} + +static void MCCTextures_CheckSource(struct ZipfileSource* source) { + struct HttpRequest item; + cc_result res; + if (!Fetcher_Get(source->reqID, &item)) return; + + source->downloaded = true; + res = source->Process(&item); + + if (res) { + cc_string name = String_FromReadonly(source->name); + Logger_SysWarn2(res, "making", &name); + } + HttpRequest_Free(&item); + + if (++numDefaultZipProcessed < numDefaultZipSources) return; + MCCTextures_CreateDefaultZip(); +} + +static void MCCTextures_CheckStatus(void) { + int i; + for (i = 0; i < numDefaultZipSources; i++) + { + if (defaultZipSources[i].downloaded) continue; + MCCTextures_CheckSource(&defaultZipSources[i]); + } +} + +static const struct AssetSet mccTexsAssetSet = { + MCCTextures_CheckExistence, + MCCTextures_CountMissing, + MCCTextures_DownloadAssets, + MCCTextures_GetRequestName, + MCCTextures_CheckStatus, + MCCTextures_ResetState +}; + + +/*########################################################################################################################* +*-----------------------------------------------------------Fetcher-------------------------------------------------------* +*#########################################################################################################################*/ +cc_bool Fetcher_Working, Fetcher_Completed, Fetcher_Failed; +int Fetcher_Downloaded; +FetcherErrorCallback Fetcher_ErrorCallback; + +static const struct AssetSet* const asset_sets[] = { + &ccTexsAssetSet, + &mccTexsAssetSet, + &mccMusicAssetSet, + &mccSoundAssetSet +}; + +static void ResetState() { + int i; + Resources_MissingCount = 0; + Resources_MissingSize = 0; + + for (i = 0; i < Array_Elems(asset_sets); i++) + { + asset_sets[i]->ResetState(); + } +} + +void Resources_CheckExistence(void) { + int i; + ResetState(); + + for (i = 0; i < Array_Elems(asset_sets); i++) + { + asset_sets[i]->CheckExistence(); + } + + for (i = 0; i < Array_Elems(asset_sets); i++) + { + asset_sets[i]->CountMissing(); + } +} + +const char* Fetcher_RequestName(int reqID) { + const char* name; + int i; + + for (i = 0; i < Array_Elems(asset_sets); i++) + { + if ((name = asset_sets[i]->GetRequestName(reqID))) return name; + } + return NULL; +} + +void Fetcher_Run(void) { + int i; + + Fetcher_Failed = false; + Fetcher_Downloaded = 0; + Fetcher_Working = true; + Fetcher_Completed = false; + + for (i = 0; i < Array_Elems(asset_sets); i++) + { + asset_sets[i]->DownloadAssets(); + } +} + +static void Fetcher_Finish(void) { + Fetcher_Completed = true; + Fetcher_Working = false; + ResetState(); +} + +static void Fetcher_Fail(struct HttpRequest* item) { + Http_ClearPending(); + + /* Only show error for first failed download */ + if (!Fetcher_Failed) Fetcher_ErrorCallback(item); + Fetcher_Failed = true; +} + +CC_NOINLINE static cc_bool Fetcher_Get(int reqID, struct HttpRequest* item) { + if (!Http_GetResult(reqID, item)) return false; + + if (item->success) { + Fetcher_Downloaded++; + return true; + } + + Fetcher_Fail(item); + HttpRequest_Free(item); + + Fetcher_Finish(); + return false; +} + +/* TODO: Implement this.. */ +/* TODO: How expensive is it to constantly do 'Get' over and over */ +void Fetcher_Update(void) { + int i; + for (i = 0; i < Array_Elems(asset_sets); i++) + { + asset_sets[i]->CheckStatus(); + } + + if (Fetcher_Downloaded != Resources_MissingCount) return; + Fetcher_Finish(); +} +#endif |