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/TexturePack.c |
initial commit
Diffstat (limited to 'src/TexturePack.c')
-rw-r--r-- | src/TexturePack.c | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/src/TexturePack.c b/src/TexturePack.c new file mode 100644 index 0000000..c97cfc1 --- /dev/null +++ b/src/TexturePack.c @@ -0,0 +1,667 @@ +#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 */ +}; |