#include "TexturePack.h" #include "String.h" #include "Constants.h" #include "Stream.h" #include "World.h" #include "Graphics.h" #include "Event.h" #include "Game.h" #include "Http.h" #include "Platform.h" #include "Deflate.h" #include "Funcs.h" #include "ExtMath.h" #include "Options.h" #include "Logger.h" #include "Utils.h" #include "Chat.h" /* TODO avoid this include */ #include "Errors.h" /* Simple fallback terrain for when no texture packs are available at all */ static BitmapCol fallback_terrain[16 * 8] = { BitmapColor_RGB( 96, 144, 85), BitmapColor_RGB(129, 128, 127), BitmapColor_RGB(123, 87, 66), BitmapColor_RGB(174, 124, 74), BitmapColor_RGB(184, 151, 105), BitmapColor_RGB(200, 200, 197), BitmapColor_RGB(175, 173, 173), BitmapColor_RGB(153, 101, 75), BitmapColor_RGB(118, 111, 101), BitmapColor_RGB( 61, 20, 11), BitmapColor_RGB(179, 67, 23), BitmapColor_RGB(154, 128, 89), BitmapColor_RGB(163, 2, 29), BitmapColor_RGB(203, 206, 2), BitmapCol_Make(86,144,216,128), BitmapColor_RGB( 38, 88, 41), /* 16*/ BitmapColor_RGB(165, 163, 159), BitmapColor_RGB( 37, 48, 61), BitmapColor_RGB(227, 223, 151), BitmapColor_RGB(160, 152, 147), BitmapColor_RGB( 90, 71, 58), BitmapColor_RGB(173, 135, 87), BitmapColor_RGB( 38, 98, 37), BitmapColor_RGB(225, 229, 235), BitmapColor_RGB(246, 231, 23), BitmapColor_RGB(225, 218, 157), BitmapColor_RGB(247, 243, 234), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB(226, 18, 18), BitmapColor_RGB(172, 131, 101), BitmapColor_RGB(255, 122, 31), BitmapColor_RGB( 79, 120, 79), /* 32 */ BitmapColor_RGB(129, 128, 127), BitmapColor_RGB(189, 151, 134), BitmapColor_RGB( 53, 44, 61), BitmapColor_RGB(180, 151, 102), BitmapColor_RGB(165, 163, 159), BitmapColor_RGB( 20, 20, 33), BitmapColor_RGB(243, 139, 28), BitmapColor_RGB(193, 197, 202), BitmapColor_RGB(235, 188, 32), BitmapColor_RGB(203, 193, 135), BitmapColor_RGB(224, 220, 212), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB(174, 124, 74), /* 48 */ BitmapColor_RGB(175, 148, 43), BitmapColor_RGB(188, 225, 231), BitmapColor_RGB(238, 245, 245), BitmapCol_Make(205,232,252,128),BitmapColor_RGB(153, 150, 149), BitmapColor_RGB(105, 80, 54), BitmapColor_RGB(236, 236, 240), BitmapColor_RGB(161, 165, 170), BitmapColor_RGB(225, 146, 30), BitmapColor_RGB(203, 193, 135), BitmapColor_RGB(247, 243, 234), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), /* 64 */ BitmapColor_RGB(217, 35, 35), BitmapColor_RGB(219, 137, 13), BitmapColor_RGB(224, 224, 0), BitmapColor_RGB(128, 221, 2), BitmapColor_RGB( 13, 217, 13), BitmapColor_RGB( 8, 218, 133), BitmapColor_RGB( 4, 219, 219), BitmapColor_RGB( 89, 175, 219), BitmapColor_RGB(122, 122, 217), BitmapColor_RGB(131, 39, 225), BitmapColor_RGB(178, 69, 230), BitmapColor_RGB(227, 52, 227), BitmapColor_RGB(227, 41, 133), BitmapColor_RGB( 73, 73, 73), BitmapColor_RGB(151, 151, 151), BitmapColor_RGB(227, 227, 227), /* 80 */ BitmapColor_RGB(220, 127, 162), BitmapColor_RGB( 42, 66, 8), BitmapColor_RGB( 75, 37, 11), BitmapColor_RGB( 24, 37, 149), BitmapColor_RGB( 29, 113, 149), BitmapColor_RGB(155, 161, 174), BitmapColor_RGB(167, 41, 13), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), /* 96 */ BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), /* 112 */ BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), BitmapColor_RGB( 52, 90, 134), BitmapColor_RGB( 57, 115, 158), }; static void LoadFallbackAtlas(void) { struct Bitmap bmp, src; src.width = 16; src.height = 8; src.scan0 = fallback_terrain; if (Gfx.MinTexWidth || Gfx.MinTexHeight) { Bitmap_Allocate(&bmp, 16 * Gfx.MinTexWidth, 8 * Gfx.MinTexHeight); Bitmap_Scale(&bmp, &src, 0, 0, 16, 8); Atlas_TryChange(&bmp); } else { Atlas_TryChange(&src); } } /*########################################################################################################################* *------------------------------------------------------TerrainAtlas-------------------------------------------------------* *#########################################################################################################################*/ struct _Atlas2DData Atlas2D; struct _Atlas1DData Atlas1D; int TexturePack_ReqID; TextureRec Atlas1D_TexRec(TextureLoc texLoc, int uCount, int* index) { TextureRec rec; int y = Atlas1D_RowId(texLoc); *index = Atlas1D_Index(texLoc); /* Adjust coords to be slightly inside - fixes issues with AMD/ATI cards */ rec.u1 = 0.0f; rec.v1 = y * Atlas1D.InvTileSize; rec.u2 = (uCount - 1) + UV2_Scale; rec.v2 = rec.v1 + UV2_Scale * Atlas1D.InvTileSize; return rec; } static void Atlas_Convert2DTo1D(void) { int tileSize = Atlas2D.TileSize; int tilesPerAtlas = Atlas1D.TilesPerAtlas; int atlasesCount = Atlas1D.Count; struct Bitmap atlas1D; int atlasX, atlasY; int tile = 0, i, y; Platform_Log2("Loaded terrain atlas: %i bmps, %i per bmp", &atlasesCount, &tilesPerAtlas); Bitmap_Allocate(&atlas1D, tileSize, tilesPerAtlas * tileSize); for (i = 0; i < atlasesCount; i++) { for (y = 0; y < tilesPerAtlas; y++, tile++) { atlasX = Atlas2D_TileX(tile) * tileSize; atlasY = Atlas2D_TileY(tile) * tileSize; Bitmap_UNSAFE_CopyBlock(atlasX, atlasY, 0, y * tileSize, &Atlas2D.Bmp, &atlas1D, tileSize); } Gfx_RecreateTexture(&Atlas1D.TexIds[i], &atlas1D, TEXTURE_FLAG_MANAGED | TEXTURE_FLAG_DYNAMIC, Gfx.Mipmaps); } Mem_Free(atlas1D.scan0); } static void Atlas_Update1D(void) { int maxAtlasHeight, maxTilesPerAtlas, maxTiles; int maxTexHeight = Gfx.MaxTexHeight; /* E.g. a graphics backend may support textures up to 256 x 256 */ /* dimension wise, but only have enough storage for 16 x 256 */ if (Gfx.MaxTexSize) { int maxCurHeight = Gfx.MaxTexSize / Atlas2D.TileSize; maxTexHeight = min(maxTexHeight, maxCurHeight); } maxAtlasHeight = min(4096, maxTexHeight); maxTilesPerAtlas = maxAtlasHeight / Atlas2D.TileSize; maxTiles = Atlas2D.RowsCount * ATLAS2D_TILES_PER_ROW; Atlas1D.TilesPerAtlas = min(maxTilesPerAtlas, maxTiles); Atlas1D.Count = Math_CeilDiv(maxTiles, Atlas1D.TilesPerAtlas); Atlas1D.InvTileSize = 1.0f / Atlas1D.TilesPerAtlas; Atlas1D.Mask = Atlas1D.TilesPerAtlas - 1; Atlas1D.Shift = Math_ilog2(Atlas1D.TilesPerAtlas); } /* Loads the given atlas and converts it into an array of 1D atlases. */ static void Atlas_Update(struct Bitmap* bmp) { Atlas2D.Bmp = *bmp; Atlas2D.TileSize = bmp->width / ATLAS2D_TILES_PER_ROW; Atlas2D.RowsCount = bmp->height / Atlas2D.TileSize; Atlas2D.RowsCount = min(Atlas2D.RowsCount, ATLAS2D_MAX_ROWS_COUNT); Atlas_Update1D(); Atlas_Convert2DTo1D(); } GfxResourceID Atlas2D_LoadTile(TextureLoc texLoc) { int size = Atlas2D.TileSize; struct Bitmap tile; int x = Atlas2D_TileX(texLoc), y = Atlas2D_TileY(texLoc); if (y >= Atlas2D.RowsCount) return 0; tile.scan0 = Bitmap_GetRow(&Atlas2D.Bmp, y * size) + (x * size); tile.width = size; tile.height = size; return Gfx_CreateTexture2(&tile, Atlas2D.Bmp.width, 0, Gfx.Mipmaps); } static void Atlas2D_Free(void) { if (Atlas2D.Bmp.scan0 != fallback_terrain) Mem_Free(Atlas2D.Bmp.scan0); Atlas2D.Bmp.scan0 = NULL; Atlas2D.RowsCount = 0; } static void Atlas1D_Free(void) { int i; for (i = 0; i < Atlas1D.Count; i++) { Gfx_DeleteTexture(&Atlas1D.TexIds[i]); } } cc_bool Atlas_TryChange(struct Bitmap* atlas) { static const cc_string terrain = String_FromConst("terrain.png"); int tileSize; if (!Game_ValidateBitmapPow2(&terrain, atlas)) return false; tileSize = atlas->width / ATLAS2D_TILES_PER_ROW; if (tileSize <= 0) { Chat_AddRaw("&cUnable to use terrain.png from the texture pack."); Chat_AddRaw("&c It must be 16 or more pixels wide."); return false; } if (atlas->height < tileSize) { Chat_AddRaw("&cUnable to use terrain.png from the texture pack."); Chat_AddRaw("&c It must have at least one row in it."); return false; } if (!Gfx_CheckTextureSize(tileSize, tileSize, 0)) { Chat_AddRaw("&cUnable to use terrain.png from the texture pack."); Chat_Add4("&c Tile size is (%i,%i), your GPU supports (%i,%i) at most.", &tileSize, &tileSize, &Gfx.MaxTexWidth, &Gfx.MaxTexHeight); return false; } if (atlas->height < atlas->width) { /* Probably wouldn't want to use these, but you still can technically */ Chat_AddRaw("&cHeight of terrain.png is less than its width."); Chat_AddRaw("&c Some tiles will therefore appear completely white."); } if (atlas->width > Gfx.MaxTexWidth) { /* Super HD textures probably won't work great on this GPU */ Chat_AddRaw("&cYou may experience significantly reduced performance."); Chat_Add4("&c terrain.png size is (%i,%i), your GPU supports (%i,%i) at most.", &atlas->width, &atlas->height, &Gfx.MaxTexWidth, &Gfx.MaxTexHeight); } if (Gfx.LostContext) return false; Atlas1D_Free(); Atlas2D_Free(); Atlas_Update(atlas); Event_RaiseVoid(&TextureEvents.AtlasChanged); return true; } /*########################################################################################################################* *------------------------------------------------------TextureCache-------------------------------------------------------* *#########################################################################################################################*/ static struct StringsBuffer acceptedList, deniedList, etagCache, lastModCache; #define ACCEPTED_TXT "texturecache/acceptedurls.txt" #define DENIED_TXT "texturecache/deniedurls.txt" #define ETAGS_TXT "texturecache/etags.txt" #define LASTMOD_TXT "texturecache/lastmodified.txt" /* Initialises cache state (loading various lists) */ static void TextureCache_Init(void) { EntryList_UNSAFE_Load(&acceptedList, ACCEPTED_TXT); EntryList_UNSAFE_Load(&deniedList, DENIED_TXT); EntryList_UNSAFE_Load(&etagCache, ETAGS_TXT); EntryList_UNSAFE_Load(&lastModCache, LASTMOD_TXT); } cc_bool TextureCache_HasAccepted(const cc_string* url) { return EntryList_Find(&acceptedList, url, ' ') >= 0; } cc_bool TextureCache_HasDenied(const cc_string* url) { return EntryList_Find(&deniedList, url, ' ') >= 0; } void TextureCache_Accept(const cc_string* url) { EntryList_Set(&acceptedList, url, &String_Empty, ' '); EntryList_Save(&acceptedList, ACCEPTED_TXT); } void TextureCache_Deny(const cc_string* url) { EntryList_Set(&deniedList, url, &String_Empty, ' '); EntryList_Save(&deniedList, DENIED_TXT); } int TextureCache_ClearDenied(void) { int count = deniedList.count; StringsBuffer_Clear(&deniedList); EntryList_Save(&deniedList, DENIED_TXT); return count; } CC_INLINE static void HashUrl(cc_string* key, const cc_string* url) { String_AppendUInt32(key, Utils_CRC32((const cc_uint8*)url->buffer, url->length)); } static cc_bool createdCache, cacheInvalid; static cc_bool UseDedicatedCache(cc_string* path, const cc_string* key) { cc_result res; Directory_GetCachePath(path); if (!path->length || cacheInvalid) return false; String_AppendConst(path, "/texturecache"); res = Directory_Create(path); /* Check if something is deleting the cache directory behind our back */ /* (Several users have reported this happening on some Android devices) */ if (createdCache && res == 0) { Chat_AddRaw("&cSomething has deleted system managed cache folder"); Chat_AddRaw(" &cFalling back to caching to game folder instead.."); cacheInvalid = true; } if (res == 0) createdCache = true; String_Format1(path, "/%s", key); return !cacheInvalid; } CC_NOINLINE static void MakeCachePath(cc_string* mainPath, cc_string* altPath, const cc_string* url) { cc_string key; char keyBuffer[STRING_INT_CHARS]; String_InitArray(key, keyBuffer); HashUrl(&key, url); if (UseDedicatedCache(mainPath, &key)) { /* If using dedicated cache directory, also fallback to default cache directory */ String_Format1(altPath, "texturecache/%s", &key); } else { mainPath->length = 0; String_Format1(mainPath, "texturecache/%s", &key); } } /* Returns non-zero if given URL has been cached */ static int IsCached(const cc_string* url) { cc_string mainPath; char mainBuffer[FILENAME_SIZE]; cc_string altPath; char altBuffer[FILENAME_SIZE]; String_InitArray(mainPath, mainBuffer); String_InitArray(altPath, altBuffer); MakeCachePath(&mainPath, &altPath, url); return File_Exists(&mainPath) || (altPath.length && File_Exists(&altPath)); } /* Attempts to open the cached data stream for the given url */ static cc_bool OpenCachedData(const cc_string* url, struct Stream* stream) { cc_string mainPath; char mainBuffer[FILENAME_SIZE]; cc_string altPath; char altBuffer[FILENAME_SIZE]; cc_result res; String_InitArray(mainPath, mainBuffer); String_InitArray(altPath, altBuffer); MakeCachePath(&mainPath, &altPath, url); res = Stream_OpenFile(stream, &mainPath); /* try fallback cache if can't find in main cache */ if (res == ReturnCode_FileNotFound && altPath.length) res = Stream_OpenFile(stream, &altPath); if (res == ReturnCode_FileNotFound) return false; if (res) { Logger_SysWarn2(res, "opening cache for", url); return false; } return true; } CC_NOINLINE static cc_string GetCachedTag(const cc_string* url, struct StringsBuffer* list) { cc_string key; char keyBuffer[STRING_INT_CHARS]; String_InitArray(key, keyBuffer); HashUrl(&key, url); return EntryList_UNSAFE_Get(list, &key, ' '); } static cc_string GetCachedLastModified(const cc_string* url) { int i; cc_string entry = GetCachedTag(url, &lastModCache); /* Entry used to be a timestamp of C# DateTime ticks since 01/01/0001 */ /* Check whether timestamp entry is old or new format */ for (i = 0; i < entry.length; i++) { if (entry.buffer[i] < '0' || entry.buffer[i] > '9') return entry; } /* Entry is all digits, so the old unsupported format */ entry.length = 0; return entry; } static cc_string GetCachedETag(const cc_string* url) { return GetCachedTag(url, &etagCache); } CC_NOINLINE static void SetCachedTag(const cc_string* url, struct StringsBuffer* list, const cc_string* data, const char* file) { cc_string key; char keyBuffer[STRING_INT_CHARS]; if (!data->length) return; String_InitArray(key, keyBuffer); HashUrl(&key, url); EntryList_Set(list, &key, data, ' '); EntryList_Save(list, file); } /* Updates cached data, ETag, and Last-Modified for the given URL */ static void UpdateCache(struct HttpRequest* req) { cc_string url, altPath, value; cc_string path; char pathBuffer[FILENAME_SIZE]; cc_result res; url = String_FromRawArray(req->url); value = String_FromRawArray(req->etag); SetCachedTag(&url, &etagCache, &value, ETAGS_TXT); value = String_FromRawArray(req->lastModified); SetCachedTag(&url, &lastModCache, &value, LASTMOD_TXT); String_InitArray(path, pathBuffer); altPath = String_Empty; MakeCachePath(&path, &altPath, &url); res = Stream_WriteAllTo(&path, req->data, req->size); if (res) { Logger_SysWarn2(res, "caching", &url); } } /*########################################################################################################################* *-------------------------------------------------------TexturePack-------------------------------------------------------* *#########################################################################################################################*/ static char textureUrlBuffer[URL_MAX_SIZE]; static char texpackPathBuffer[FILENAME_SIZE]; cc_string TexturePack_Url = String_FromArray(textureUrlBuffer); cc_string TexturePack_Path = String_FromArray(texpackPathBuffer); cc_bool TexturePack_DefaultMissing; void TexturePack_SetDefault(const cc_string* texPack) { TexturePack_Path.length = 0; String_Format1(&TexturePack_Path, "texpacks/%s", texPack); Options_Set(OPT_DEFAULT_TEX_PACK, texPack); } cc_result TexturePack_ExtractDefault(DefaultZipCallback callback) { cc_result res = ReturnCode_FileNotFound; const char* defaults[3]; cc_string path; int i; defaults[0] = Game_Version.DefaultTexpack; defaults[1] = "texpacks/default.zip"; defaults[2] = "texpacks/classicube.zip"; for (i = 0; i < Array_Elems(defaults); i++) { path = String_FromReadonly(defaults[i]); res = callback(&path); if (!res) return 0; } return res; } 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 ExtractPng(struct Stream* stream) { struct Bitmap bmp; cc_result res = Png_Decode(&bmp, stream); if (!res && Atlas_TryChange(&bmp)) return 0; Mem_Free(bmp.scan0); return res; } static cc_bool needReload; static cc_result ExtractFrom(struct Stream* stream, const cc_string* path) { cc_result res; Event_RaiseVoid(&TextureEvents.PackChanged); /* If context is lost, then trying to load textures will just fail */ /* So defer loading the texture pack until context is restored */ if (Gfx.LostContext) { needReload = true; return 0; } needReload = false; res = ExtractPng(stream); if (res == PNG_ERR_INVALID_SIG) { /* 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); } return res; } #ifdef CC_BUILD_PS1 #include "../misc/ps1/classicubezip.h" static cc_result ExtractFromFile(const cc_string* path) { struct Stream stream; Stream_ReadonlyMemory(&stream, ccTextures, ccTextures_length); return ExtractFrom(&stream, path); } #else static cc_result ExtractFromFile(const cc_string* path) { struct Stream stream; cc_result res; res = Stream_OpenFile(&stream, path); if (res) { Logger_SysWarn2(res, "opening", path); return res; } res = ExtractFrom(&stream, path); /* No point logging error for closing readonly file */ (void)stream.Close(&stream); return res; } #endif static cc_result ExtractUserTextures(void) { cc_string path; cc_result res; /* TODO: Log error for multiple default texture pack extract failure */ res = TexturePack_ExtractDefault(ExtractFromFile); /* Game shows a warning dialog if default textures are missing */ TexturePack_DefaultMissing = res == ReturnCode_FileNotFound; path = TexturePack_Path; if (String_CaselessEqualsConst(&path, "texpacks/default.zip")) path.length = 0; if (Game_ClassicMode || path.length == 0) return res; /* override default textures with user's selected texture pack */ return ExtractFromFile(&path); } static cc_bool usingDefault; cc_result TexturePack_ExtractCurrent(cc_bool forceReload) { cc_string url = TexturePack_Url; struct Stream stream; cc_result res = 0; /* don't pointlessly load default texture pack */ if (!usingDefault || forceReload) { res = ExtractUserTextures(); usingDefault = true; } if (url.length && OpenCachedData(&url, &stream)) { res = ExtractFrom(&stream, &url); usingDefault = false; /* No point logging error for closing readonly file */ (void)stream.Close(&stream); } /* Use fallback terrain texture with 1 pixel per tile */ if (!Atlas2D.Bmp.scan0) LoadFallbackAtlas(); return res; } /* Extracts and updates cache for the downloaded texture pack */ static void ApplyDownloaded(struct HttpRequest* item) { struct Stream mem; cc_string url; url = String_FromRawArray(item->url); if (!Platform_ReadonlyFilesystem) UpdateCache(item); /* Took too long to download and is no longer active texture pack */ if (!String_Equals(&TexturePack_Url, &url)) return; Stream_ReadonlyMemory(&mem, item->data, item->size); ExtractFrom(&mem, &url); usingDefault = false; } void TexturePack_CheckPending(void) { struct HttpRequest item; if (!Http_GetResult(TexturePack_ReqID, &item)) return; if (item.success) { ApplyDownloaded(&item); } else if (item.result) { Http_LogError("trying to download texture pack", &item); } else if (item.statusCode == 200 || item.statusCode == 304) { /* Empty responses is okay for these status codes, so don't log an error */ } else if (item.statusCode == 404) { Chat_AddRaw("&c404 Not Found error when trying to download texture pack"); Chat_AddRaw(" &cThe texture pack URL may be incorrect or no longer exist"); } else if (item.statusCode == 401 || item.statusCode == 403) { Chat_Add1("&c%i Not Authorised error when trying to download texture pack", &item.statusCode); Chat_AddRaw(" &cThe texture pack URL may not be publicly shared"); } else { Chat_Add1("&c%i error when trying to download texture pack", &item.statusCode); } HttpRequest_Free(&item); } /* Asynchronously downloads the given texture pack */ static void DownloadAsync(const cc_string* url) { cc_string etag = String_Empty; cc_string time = String_Empty; /* Only retrieve etag/last-modified headers if the file exists */ /* This inconsistency can occur if user deleted some cached files */ if (IsCached(url)) { time = GetCachedLastModified(url); etag = GetCachedETag(url); } Http_TryCancel(TexturePack_ReqID); TexturePack_ReqID = Http_AsyncGetDataEx(url, HTTP_FLAG_PRIORITY, &time, &etag, NULL); } void TexturePack_Extract(const cc_string* url) { if (url->length) DownloadAsync(url); if (String_Equals(url, &TexturePack_Url)) return; String_Copy(&TexturePack_Url, url); TexturePack_ExtractCurrent(false); } static struct TextureEntry* entries_head; static struct TextureEntry* entries_tail; void TextureEntry_Register(struct TextureEntry* entry) { LinkedList_Append(entry, entries_head, entries_tail); } /*########################################################################################################################* *---------------------------------------------------Textures component----------------------------------------------------* *#########################################################################################################################*/ static void TerrainPngProcess(struct Stream* stream, const cc_string* name) { struct Bitmap bmp; cc_result res = Png_Decode(&bmp, stream); if (res) { Logger_SysWarn2(res, "decoding", name); Mem_Free(bmp.scan0); } else if (!Atlas_TryChange(&bmp)) { Mem_Free(bmp.scan0); } } static struct TextureEntry terrain_entry = { "terrain.png", TerrainPngProcess }; static void OnFileChanged(void* obj, struct Stream* stream, const cc_string* name) { struct TextureEntry* e; for (e = entries_head; e; e = e->next) { if (!String_CaselessEqualsConst(name, e->filename)) continue; e->Callback(stream, name); return; } } static void OnContextLost(void* obj) { if (!Gfx.ManagedTextures) Atlas1D_Free(); } static void OnContextRecreated(void* obj) { if (!Gfx.ManagedTextures || needReload) { TexturePack_ExtractCurrent(true); } } static void OnInit(void) { cc_string file; Event_Register_(&TextureEvents.FileChanged, NULL, OnFileChanged); Event_Register_(&GfxEvents.ContextLost, NULL, OnContextLost); Event_Register_(&GfxEvents.ContextRecreated, NULL, OnContextRecreated); TexturePack_Path.length = 0; if (Options_UNSAFE_Get(OPT_DEFAULT_TEX_PACK, &file)) { String_Format1(&TexturePack_Path, "texpacks/%s", &file); } /* TODO temp hack to fix mobile, need to properly fix */ /* issue is that Drawer2D_Component.Init is called from Launcher,*/ /* which called TextureEntry_Register, whoops*/ entries_head = NULL; TextureEntry_Register(&terrain_entry); Utils_EnsureDirectory("texpacks"); Utils_EnsureDirectory("texturecache"); TextureCache_Init(); } static void OnReset(void) { if (!TexturePack_Url.length) return; TexturePack_Url.length = 0; TexturePack_ExtractCurrent(false); } static void OnFree(void) { OnContextLost(NULL); Atlas2D_Free(); TexturePack_Url.length = 0; entries_head = NULL; } struct IGameComponent Textures_Component = { OnInit, /* Init */ OnFree, /* Free */ OnReset /* Reset */ };