From 485867e4c4d4e73e2bba9a1b8a9c13f484c6df4f Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Mon, 26 Jan 2026 08:09:45 +0000 Subject: [PATCH 01/20] Add image overlay support to possession mode lenses Implements overlay image rendering for lens effects, allowing modders to add custom visual overlays (helmets, visors, HUD elements) during creature possession. Features: - RAW (256x256, 8-bit indexed) and PNG/BMP format support - Automatic mod directory detection and fallback to base game - Palette index 0 treated as transparent - Stretch-to-fit scaling for widescreen compatibility - Overlay configuration via lenses.cfg: Overlay = filename.raw alpha Files modified: - src/config_lenses.c/h: Configuration parsing and data structures - src/lens_api.c: Image loading and rendering implementation --- src/config_lenses.c | 52 ++++++++++- src/config_lenses.h | 7 ++ src/lens_api.c | 215 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 273 insertions(+), 1 deletion(-) diff --git a/src/config_lenses.c b/src/config_lenses.c index c134928795..5550008233 100644 --- a/src/config_lenses.c +++ b/src/config_lenses.c @@ -27,6 +27,9 @@ #include "config.h" #include "thing_doors.h" +#include +#include + #include "keeperfx.hpp" #include "post_inc.h" @@ -49,6 +52,7 @@ const struct ConfigFileData keeper_lenses_file_data = { static int64_t value_mist(const struct NamedField* named_field, const char* value_text, const struct NamedFieldSet* named_fields_set, int idx, const char* src_str, unsigned char flags); static int64_t value_pallete(const struct NamedField* named_field, const char* value_text, const struct NamedFieldSet* named_fields_set, int idx, const char* src_str, unsigned char flags); static int64_t value_displace(const struct NamedField* named_field, const char* value_text, const struct NamedFieldSet* named_fields_set, int idx, const char* src_str, unsigned char flags); +static int64_t value_overlay(const struct NamedField* named_field, const char* value_text, const struct NamedFieldSet* named_fields_set, int idx, const char* src_str, unsigned char flags); const struct NamedField lenses_data_named_fields[] = { //name //pos //field //default //min //max //NamedCommand @@ -60,6 +64,8 @@ const struct NamedField lenses_data_named_fields[] = { {"DISPLACEMENT", 1, field(lenses_conf.lenses[0].displace_magnitude), 0, 0, 511, NULL, value_default, assign_default}, {"DISPLACEMENT", 2, field(lenses_conf.lenses[0].displace_period), 1, 0, 511, NULL, value_displace, assign_default}, {"PALETTE", 0, field(lenses_conf.lenses[0].palette), 0, 0, 0, NULL, value_pallete, assign_null}, + {"OVERLAY", 0, field(lenses_conf.lenses[0].overlay_file), 0, 0, 0, NULL, value_overlay, assign_null}, + {"OVERLAY", 1, field(lenses_conf.lenses[0].overlay_alpha), 128, 0, 255, NULL, value_default, assign_default}, {NULL}, }; @@ -107,9 +113,53 @@ static int64_t value_pallete(const struct NamedField* named_field, const char* v return 0; } +static int64_t value_overlay(const struct NamedField* named_field, const char* value_text, const struct NamedFieldSet* named_fields_set, int idx, const char* src_str, unsigned char flags) +{ + if (value_text == NULL || value_text[0] == '\0') { + CONFWRNLOG("Empty filename for \"%s\" parameter in [%s%d] block of lens.cfg file.", + named_field->name, named_fields_set->block_basename, idx); + return 0; + } + + lenses_conf.lenses[idx].flags |= LCF_HasOverlay; + struct LensConfig* lenscfg = &lenses_conf.lenses[idx]; + + // Store the filename + strncpy(lenscfg->overlay_file, value_text, DISKPATH_SIZE - 1); + lenscfg->overlay_file[DISKPATH_SIZE - 1] = '\0'; + + // Try to extract mod directory from src_str (full config file path) + // src_str format for mods: "/mods//fxdata/lenses.cfg" + // We want to extract "mods/" + lenscfg->overlay_mod_dir[0] = '\0'; // Default: no mod directory + if (src_str != NULL) { + const char* mods_pos = strstr(src_str, "mods/"); + if (mods_pos != NULL) { + // Find the end of the mod name (before next '/') + const char* mod_start = mods_pos; + const char* next_slash = strchr(mods_pos + 5, '/'); // +5 to skip "mods/" + if (next_slash != NULL) { + size_t len = next_slash - mod_start; + if (len < DISKPATH_SIZE) { + strncpy(lenscfg->overlay_mod_dir, mod_start, len); + lenscfg->overlay_mod_dir[len] = '\0'; + SYNCDBG(9, "Detected mod directory for overlay: %s", lenscfg->overlay_mod_dir); + } + } + } + } + + // Initialize overlay data pointer to NULL - will be loaded during setup + lenscfg->overlay_data = NULL; + lenscfg->overlay_width = 0; + lenscfg->overlay_height = 0; + + return 0; +} + static TbBool load_lenses_config_file(const char *fname, unsigned short flags) { - SYNCDBG(0,"%s file \"%s\".",((flags & CnfLd_ListOnly) == 0)?"Reading":"Parsing",fname); + SYNCLOG("%s file \"%s\".",((flags & CnfLd_ListOnly) == 0)?"Reading":"Parsing",fname); long len = LbFileLengthRnc(fname); if (len < MIN_CONFIG_FILE_SIZE) { diff --git a/src/config_lenses.h b/src/config_lenses.h index 64d7cbda27..ea296eabba 100644 --- a/src/config_lenses.h +++ b/src/config_lenses.h @@ -35,6 +35,7 @@ enum LensConfigFlags { LCF_HasMist = 0x01, LCF_HasDisplace = 0x02, LCF_HasPalette = 0x04, + LCF_HasOverlay = 0x08, }; struct LensConfig { @@ -47,6 +48,12 @@ struct LensConfig { short displace_kind; short displace_magnitude; short displace_period; + char overlay_file[DISKPATH_SIZE]; + char overlay_mod_dir[DISKPATH_SIZE]; // Mod directory for overlay, empty for base game + unsigned char *overlay_data; + int overlay_width; + int overlay_height; + short overlay_alpha; }; struct LensesConfig { diff --git a/src/lens_api.c b/src/lens_api.c index 75d6ed88c8..5b36fbdebb 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -20,11 +20,15 @@ #include "lens_api.h" #include +#include #include "globals.h" #include "bflib_basics.h" #include "bflib_fileio.h" #include "bflib_dernc.h" +#include +#include + #include "config_lenses.h" #include "lens_mist.h" #include "lens_flyeye.h" @@ -40,6 +44,8 @@ extern "C" { #endif /******************************************************************************/ +#define RAW_OVERLAY_SIZE 256 // RAW overlay files are 256x256 pixels (matching mist texture format) + uint32_t *eye_lens_memory; TbPixel *eye_lens_spare_screen_memory; /******************************************************************************/ @@ -145,6 +151,142 @@ void init_lens(uint32_t *lens_mem, int width, int height, int pitch, int nlens, } } +static TbBool load_overlay_image(struct LensConfig* lenscfg) +{ + if (lenscfg->overlay_data != NULL) { + // Already loaded + return true; + } + + // Validate overlay filename + if (lenscfg->overlay_file[0] == '\0') { + WARNLOG("Empty overlay filename"); + return false; + } + + // Detect file format from extension + const char* ext = strrchr(lenscfg->overlay_file, '.'); + TbBool is_raw = (ext != NULL && strcasecmp(ext, ".raw") == 0); + + char* fname = NULL; + + if (is_raw) { + // RAW format: 256x256 8-bit indexed palette data + // Try loading from mod directory first (if specified), then fall back to base game + SYNCLOG("Loading RAW overlay: file='%s', mod_dir='%s'", lenscfg->overlay_file, lenscfg->overlay_mod_dir); + if (lenscfg->overlay_mod_dir[0] != '\0') { + fname = prepare_file_path_mod(lenscfg->overlay_mod_dir, FGrp_StdData, lenscfg->overlay_file); + SYNCLOG("Trying mod path: %s", fname); + if (fname[0] != '\0' && LbFileExists(fname)) { + SYNCLOG("Found RAW overlay in mod: %s", fname); + } else { + SYNCLOG("Not found in mod directory"); + fname = NULL; + } + } + + // Fall back to base game data directory + if (fname == NULL || fname[0] == '\0') { + fname = prepare_file_path(FGrp_StdData, lenscfg->overlay_file); + SYNCLOG("Trying base game path: %s", fname); + } + + // Load RAW file directly - expected to be 256x256 pixels + const size_t raw_size = (size_t)RAW_OVERLAY_SIZE * RAW_OVERLAY_SIZE; + lenscfg->overlay_data = (unsigned char*)malloc(raw_size); + if (lenscfg->overlay_data == NULL) { + ERRORLOG("Failed to allocate memory for RAW overlay image"); + return false; + } + + long loaded = LbFileLoadAt(fname, lenscfg->overlay_data); + if (loaded != (long)raw_size) { + WARNLOG("Failed to load RAW overlay \"%s\" (expected %lu bytes, got %ld)", lenscfg->overlay_file, (unsigned long)raw_size, loaded); + free(lenscfg->overlay_data); + lenscfg->overlay_data = NULL; + return false; + } + + lenscfg->overlay_width = RAW_OVERLAY_SIZE; + lenscfg->overlay_height = RAW_OVERLAY_SIZE; + SYNCDBG(7, "Loaded RAW overlay image \"%s\" (%dx%d)", lenscfg->overlay_file, RAW_OVERLAY_SIZE, RAW_OVERLAY_SIZE); + return true; + } + + // PNG/BMP format: Use SDL_image + SDL_Surface* surface = NULL; + + // First try: mod directory (if overlay_mod_dir is set) + if (lenscfg->overlay_mod_dir[0] != '\0') { + fname = prepare_file_path_mod(lenscfg->overlay_mod_dir, FGrp_StdData, lenscfg->overlay_file); + if (fname[0] != '\0' && LbFileExists(fname)) { + surface = IMG_Load(fname); + if (surface != NULL) { + SYNCDBG(8, "Loaded overlay from mod: %s", fname); + } + } + } + + // Second try: base game data directory + if (surface == NULL) { + fname = prepare_file_path(FGrp_StdData, lenscfg->overlay_file); + surface = IMG_Load(fname); + if (surface != NULL) { + SYNCDBG(8, "Loaded overlay from base game: %s", fname); + } + } + + if (surface == NULL) { + WARNLOG("Failed to load overlay image \"%s\": %s", lenscfg->overlay_file, IMG_GetError()); + return false; + } + + // Validate dimensions to prevent integer overflow + if (surface->w <= 0 || surface->h <= 0 || surface->w > 16384 || surface->h > 16384) { + WARNLOG("Invalid overlay image dimensions (%dx%d) in \"%s\"", surface->w, surface->h, lenscfg->overlay_file); + SDL_FreeSurface(surface); + return false; + } + + // Convert to 8-bit indexed format matching game palette + SDL_Surface* converted = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_INDEX8, 0); + if (converted == NULL) { + WARNLOG("Failed to convert overlay image \"%s\" to indexed format: %s", lenscfg->overlay_file, SDL_GetError()); + SDL_FreeSurface(surface); + return false; + } + + // Store the image data - size is safe due to dimension validation above + size_t size = (size_t)converted->w * converted->h; + lenscfg->overlay_data = (unsigned char*)malloc(size); + if (lenscfg->overlay_data == NULL) { + ERRORLOG("Failed to allocate memory for overlay image"); + SDL_FreeSurface(converted); + SDL_FreeSurface(surface); + return false; + } + + memcpy(lenscfg->overlay_data, converted->pixels, size); + lenscfg->overlay_width = converted->w; + lenscfg->overlay_height = converted->h; + + SDL_FreeSurface(converted); + SDL_FreeSurface(surface); + + SYNCDBG(7, "Loaded overlay image \"%s\" (%dx%d)", lenscfg->overlay_file, lenscfg->overlay_width, lenscfg->overlay_height); + return true; +} + +static void free_overlay_image(struct LensConfig* lenscfg) +{ + if (lenscfg->overlay_data != NULL) { + free(lenscfg->overlay_data); + lenscfg->overlay_data = NULL; + lenscfg->overlay_width = 0; + lenscfg->overlay_height = 0; + } +} + TbBool clear_lens_palette(void) { SYNCDBG(7,"Staring"); @@ -175,6 +317,11 @@ void reset_eye_lenses(void) SYNCDBG(7,"Starting"); free_mist(); clear_lens_palette(); + // Free any loaded overlay images + for (size_t i = 0; i < (size_t)lenses_conf.lenses_count; i++) + { + free_overlay_image(&lenses_conf.lenses[i]); + } if (eye_lens_memory != NULL) { free(eye_lens_memory); @@ -276,6 +423,15 @@ void setup_eye_lens(long nlens) SYNCDBG(9,"Palette config entered"); set_lens_palette(lenscfg->palette); } + if ((lenscfg->flags & LCF_HasOverlay) != 0) + { + SYNCLOG("Overlay config entered for lens %ld, file='%s', mod_dir='%s'", nlens, lenscfg->overlay_file, lenscfg->overlay_mod_dir); + if (!load_overlay_image(lenscfg)) { + WARNLOG("Failed to load overlay for lens %ld", nlens); + } else { + SYNCLOG("Successfully loaded overlay %dx%d", lenscfg->overlay_width, lenscfg->overlay_height); + } + } game.applied_lens_type = nlens; game.active_lens_type = nlens; } @@ -320,6 +476,52 @@ void draw_copy(unsigned char *dstbuf, long dstpitch, unsigned char *srcbuf, long } } +static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long height, struct LensConfig* lenscfg) +{ + if (lenscfg->overlay_data == NULL) { + WARNLOG("Overlay data is NULL, cannot draw"); + return; + } + + // Validate dimensions + if (width <= 0 || height <= 0 || lenscfg->overlay_width <= 0 || lenscfg->overlay_height <= 0) { + WARNLOG("Invalid dimensions for overlay rendering: screen=%ldx%ld, overlay=%dx%d", width, height, lenscfg->overlay_width, lenscfg->overlay_height); + return; + } + + SYNCLOG("Drawing overlay: screen=%ldx%ld, overlay=%dx%d, alpha=%d", width, height, lenscfg->overlay_width, lenscfg->overlay_height, lenscfg->overlay_alpha); + + // Calculate scale factors to stretch/fit overlay to fill entire viewport + float scale_x = (float)lenscfg->overlay_width / width; + float scale_y = (float)lenscfg->overlay_height / height; + + // Draw the overlay - palette index 0 is transparent, all others are solid + unsigned char* dst = dstbuf; + for (long y = 0; y < height; y++) + { + int src_y = (int)(y * scale_y); + if (src_y >= lenscfg->overlay_height) src_y = lenscfg->overlay_height - 1; + + unsigned char* src_row = lenscfg->overlay_data + (src_y * lenscfg->overlay_width); + + for (long x = 0; x < width; x++) + { + int src_x = (int)(x * scale_x); + if (src_x >= lenscfg->overlay_width) src_x = lenscfg->overlay_width - 1; + + unsigned char overlay_pixel = src_row[src_x]; + + // Color 0 (black) is treated as transparent - skip it completely + // All other pixels are drawn at full opacity (no dithering) + if (overlay_pixel != 0) + { + dst[x] = overlay_pixel; + } + } + dst += dstpitch; + } +} + void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbuf, long srcpitch, long width, long height, long effect) { long copied = 0; @@ -355,6 +557,19 @@ void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbu { // Nothing to do - palette is just set and don't have to be drawn } + // Draw overlay effect if present + if ((lenscfg->flags & LCF_HasOverlay) != 0) + { + // First, copy the source buffer to destination if not already done + if (!copied) + { + draw_copy(dstbuf, dstpitch, srcbuf, srcpitch, width, height); + } + // Now draw the overlay on top of the game scene + SYNCLOG("Calling draw_overlay (flags=%d, data=%p)", lenscfg->flags, lenscfg->overlay_data); + draw_overlay(dstbuf, dstpitch, width, height, lenscfg); + copied = true; + } // If we haven't copied the buffer to screen yet, do so now if (!copied) { From dd22e83d09204818b044d8c97937b5e62fd1b7b5 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:19:11 +0000 Subject: [PATCH 02/20] refactored to use json loading --- src/config_lenses.c | 53 +++---- src/custom_sprites.c | 343 +++++++++++++++++++++++++++++++++++++++++++ src/custom_sprites.h | 11 ++ src/lens_api.c | 150 ++++++------------- 4 files changed, 418 insertions(+), 139 deletions(-) diff --git a/src/config_lenses.c b/src/config_lenses.c index 5550008233..7a8278308b 100644 --- a/src/config_lenses.c +++ b/src/config_lenses.c @@ -26,9 +26,7 @@ #include "config.h" #include "thing_doors.h" - -#include -#include +#include "custom_sprites.h" #include "keeperfx.hpp" #include "post_inc.h" @@ -115,8 +113,10 @@ static int64_t value_pallete(const struct NamedField* named_field, const char* v static int64_t value_overlay(const struct NamedField* named_field, const char* value_text, const struct NamedFieldSet* named_fields_set, int idx, const char* src_str, unsigned char flags) { + SYNCLOG("value_overlay called: argnum=%d, value='%s', lens=%d", named_field->argnum, value_text, idx); + if (value_text == NULL || value_text[0] == '\0') { - CONFWRNLOG("Empty filename for \"%s\" parameter in [%s%d] block of lens.cfg file.", + CONFWRNLOG("Empty overlay name for \"%s\" parameter in [%s%d] block of lens.cfg file.", named_field->name, named_fields_set->block_basename, idx); return 0; } @@ -124,42 +124,29 @@ static int64_t value_overlay(const struct NamedField* named_field, const char* v lenses_conf.lenses[idx].flags |= LCF_HasOverlay; struct LensConfig* lenscfg = &lenses_conf.lenses[idx]; - // Store the filename - strncpy(lenscfg->overlay_file, value_text, DISKPATH_SIZE - 1); - lenscfg->overlay_file[DISKPATH_SIZE - 1] = '\0'; - - // Try to extract mod directory from src_str (full config file path) - // src_str format for mods: "/mods//fxdata/lenses.cfg" - // We want to extract "mods/" - lenscfg->overlay_mod_dir[0] = '\0'; // Default: no mod directory - if (src_str != NULL) { - const char* mods_pos = strstr(src_str, "mods/"); - if (mods_pos != NULL) { - // Find the end of the mod name (before next '/') - const char* mod_start = mods_pos; - const char* next_slash = strchr(mods_pos + 5, '/'); // +5 to skip "mods/" - if (next_slash != NULL) { - size_t len = next_slash - mod_start; - if (len < DISKPATH_SIZE) { - strncpy(lenscfg->overlay_mod_dir, mod_start, len); - lenscfg->overlay_mod_dir[len] = '\0'; - SYNCDBG(9, "Detected mod directory for overlay: %s", lenscfg->overlay_mod_dir); - } - } - } + // Only store the overlay name when processing position 0 (the name field), pos 1 is for alpha value + if (named_field->argnum == 0) + { + strncpy(lenscfg->overlay_file, value_text, DISKPATH_SIZE - 1); + lenscfg->overlay_file[DISKPATH_SIZE - 1] = '\0'; + + lenscfg->overlay_data = NULL; + lenscfg->overlay_width = 0; + lenscfg->overlay_height = 0; + + SYNCDBG(9, "Registered overlay name '%s' for lens %d", value_text, idx); + } + else + { + SYNCLOG("Skipping overlay name storage for argnum=%d (alpha value)", named_field->argnum); } - - // Initialize overlay data pointer to NULL - will be loaded during setup - lenscfg->overlay_data = NULL; - lenscfg->overlay_width = 0; - lenscfg->overlay_height = 0; return 0; } static TbBool load_lenses_config_file(const char *fname, unsigned short flags) { - SYNCLOG("%s file \"%s\".",((flags & CnfLd_ListOnly) == 0)?"Reading":"Parsing",fname); + SYNCDBG(0,"%s file \"%s\".",((flags & CnfLd_ListOnly) == 0)?"Reading":"Parsing",fname); long len = LbFileLengthRnc(fname); if (len < MIN_CONFIG_FILE_SIZE) { diff --git a/src/custom_sprites.c b/src/custom_sprites.c index 77d62264a9..40bb0e046b 100644 --- a/src/custom_sprites.c +++ b/src/custom_sprites.c @@ -85,6 +85,11 @@ static struct NamedCommand added_sprites[KEEPERSPRITE_ADD_NUM]; static struct NamedCommand added_icons[GUI_PANEL_SPRITES_NEW]; static int num_added_sprite = 0; static int num_added_icons = 0; + +#define MAX_LENS_OVERLAYS 64 +static struct LensOverlayData added_lens_overlays[MAX_LENS_OVERLAYS]; +static int num_added_lens_overlays = 0; + unsigned char base_pal[PALETTE_SIZE]; int total_sprite_zip_count = 0; @@ -99,6 +104,8 @@ static TbBool add_custom_sprite(const char *path); static TbBool add_custom_json(const char *path, const char *name, TbBool (*process)(const char *path, unzFile zip, VALUE *root)); +static TbBool process_lens_overlay(const char *path, unzFile zip, VALUE *root); + static TbBool process_icon(const char *path, unzFile zip, VALUE *root); static int cmp_named_command(const void *a, const void *b); @@ -217,6 +224,11 @@ static int load_file_sprites(const char *path, const char *file_desc) add_flag |= 0x2; } + if (add_custom_json(path, "lenses.json", &process_lens_overlay)) + { + add_flag |= 0x4; + } + if (file_desc != NULL) { if (add_flag & 0x1) @@ -236,6 +248,15 @@ static int load_file_sprites(const char *path, const char *file_desc) { SYNCDBG(0, "Unable to load per-map icons from %s", file_desc); } + + if (add_flag & 0x4) + { + JUSTLOG("Loaded lens overlays from %s", file_desc); + } + else + { + SYNCDBG(0, "Unable to load lens overlays from %s", file_desc); + } total_sprite_zip_count++; } @@ -1331,6 +1352,298 @@ add_custom_json(const char *path, const char *name, TbBool (*process)(const char return 0; } +static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx, VALUE *root) +{ + VALUE *val; + + val = value_dict_get(root, "name"); + if (val == NULL) + { + WARNLOG("Invalid lens overlay %s/lenses.json[%d]: no \"name\" key", path, idx); + return 0; + } + const char *name = value_string(val); + SYNCDBG(2, "found lens overlay: '%s/%s'", path, name); + + VALUE *file_value = value_dict_get(root, "file"); + if (file_value == NULL) + { + WARNLOG("Invalid lens overlay %s/lenses.json[%d]: no \"file\" key", path, idx); + return 0; + } + + const char *file = NULL; + if (value_type(file_value) == VALUE_STRING) + { + file = value_string(file_value); + } + else if (value_type(file_value) == VALUE_ARRAY && value_array_size(file_value) > 0) + { + file = value_string(value_array_get(file_value, 0)); + } + else + { + WARNLOG("Invalid lens overlay %s/lenses.json[%d]: invalid \"file\" value", path, idx); + return 0; + } + + if (fastUnzLocateFile(zip, file, 0)) + { + WARNLOG("File '%s' not found in '%s'", file, path); + return 0; + } + + unz_file_info64 zip_info = {0}; + if (UNZ_OK != unzGetCurrentFileInfo64(zip, &zip_info, NULL, 0, NULL, 0, NULL, 0)) + { + WARNLOG("Failed to get file info for '%s' in '%s'", file, path); + return 0; + } + + if (UNZ_OK != unzOpenCurrentFile(zip)) + { + return 0; + } + + // Check if this is a RAW file (256x256 = 65536 bytes) + const size_t raw_size = 256 * 256; + const char *ext = strrchr(file, '.'); + TbBool is_raw = (zip_info.uncompressed_size == raw_size) && ext && (strcasecmp(ext, ".raw") == 0); + + if (is_raw && zip_info.uncompressed_size == raw_size) + { + // Load RAW format directly (256x256 8-bit indexed palette data) + unsigned char *indexed_data = malloc(raw_size); + if (indexed_data == NULL) + { + ERRORLOG("Failed to allocate memory for RAW overlay"); + unzCloseCurrentFile(zip); + return 0; + } + + if (unzReadCurrentFile(zip, indexed_data, raw_size) != raw_size) + { + WARNLOG("Failed to read RAW file '%s' from '%s'", file, path); + free(indexed_data); + unzCloseCurrentFile(zip); + return 0; + } + + unzCloseCurrentFile(zip); + + // Check if overlay with this name already exists + struct LensOverlayData *existing = NULL; + for (int i = 0; i < num_added_lens_overlays; i++) + { + if (strcasecmp(added_lens_overlays[i].name, name) == 0) + { + existing = &added_lens_overlays[i]; + break; + } + } + + if (existing) + { + // Override existing overlay + free(existing->data); + existing->data = indexed_data; + existing->width = 256; + existing->height = 256; + JUSTLOG("Overriding lens overlay '%s/%s'", path, name); + } + else + { + // Add new overlay + if (num_added_lens_overlays >= MAX_LENS_OVERLAYS) + { + ERRORLOG("Too many lens overlays (max %d)", MAX_LENS_OVERLAYS); + free(indexed_data); + return 0; + } + + added_lens_overlays[num_added_lens_overlays].name = strdup(name); + added_lens_overlays[num_added_lens_overlays].data = indexed_data; + added_lens_overlays[num_added_lens_overlays].width = 256; + added_lens_overlays[num_added_lens_overlays].height = 256; + num_added_lens_overlays++; + SYNCDBG(8, "Added RAW lens overlay '%s' (256x256)", name); + } + + return 1; + } + + // PNG format handling + if (zip_info.uncompressed_size > 1024 * 1024 * 4) + { + WARNLOG("Lens overlay file too large: '%s' in '%s'", file, path); + unzCloseCurrentFile(zip); + return 0; + } + + unsigned char *png_buffer = malloc(zip_info.uncompressed_size); + if (png_buffer == NULL) + { + ERRORLOG("Failed to allocate memory for PNG buffer"); + unzCloseCurrentFile(zip); + return 0; + } + + if (unzReadCurrentFile(zip, png_buffer, zip_info.uncompressed_size) != zip_info.uncompressed_size) + { + WARNLOG("Failed to read '%s' from '%s'", file, path); + free(png_buffer); + unzCloseCurrentFile(zip); + return 0; + } + + unzCloseCurrentFile(zip); + + // Decode PNG using spng + spng_ctx *ctx = spng_ctx_new(0); + if (ctx == NULL) + { + ERRORLOG("Failed to create spng context"); + free(png_buffer); + return 0; + } + + spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE); + size_t limit = 1024 * 1024 * 4; + spng_set_chunk_limits(ctx, limit, limit); + + if (spng_set_png_buffer(ctx, png_buffer, zip_info.uncompressed_size)) + { + ERRORLOG("Failed to set PNG buffer for '%s'", file); + spng_ctx_free(ctx); + free(png_buffer); + return 0; + } + + struct spng_ihdr ihdr; + int r = spng_get_ihdr(ctx, &ihdr); + if (r) + { + ERRORLOG("spng_get_ihdr() error: %s for '%s'", spng_strerror(r), file); + spng_ctx_free(ctx); + free(png_buffer); + return 0; + } + + if (ihdr.width <= 0 || ihdr.height <= 0 || ihdr.width > 4096 || ihdr.height > 4096) + { + WARNLOG("Invalid lens overlay dimensions (%dx%d) in '%s'", ihdr.width, ihdr.height, file); + spng_ctx_free(ctx); + free(png_buffer); + return 0; + } + + // Decode to RGBA8 + size_t out_size; + int fmt = SPNG_FMT_RGBA8; + if (spng_decoded_image_size(ctx, fmt, &out_size)) + { + ERRORLOG("Failed to get decoded image size for '%s'", file); + spng_ctx_free(ctx); + free(png_buffer); + return 0; + } + + unsigned char *rgba_buffer = malloc(out_size); + if (rgba_buffer == NULL) + { + ERRORLOG("Failed to allocate memory for decoded image"); + spng_ctx_free(ctx); + free(png_buffer); + return 0; + } + + if (spng_decode_image(ctx, rgba_buffer, out_size, fmt, SPNG_DECODE_TRNS)) + { + ERRORLOG("Failed to decode PNG '%s'", file); + free(rgba_buffer); + spng_ctx_free(ctx); + free(png_buffer); + return 0; + } + + spng_ctx_free(ctx); + free(png_buffer); + + // Convert RGBA to indexed palette format + size_t indexed_size = ihdr.width * ihdr.height; + unsigned char *indexed_data = malloc(indexed_size); + if (indexed_data == NULL) + { + ERRORLOG("Failed to allocate memory for indexed image data"); + free(rgba_buffer); + return 0; + } + + // Convert RGBA to palette indices using rgb_to_pal_table + for (size_t i = 0; i < indexed_size; i++) + { + unsigned char red = rgba_buffer[i * 4 + 0]; + unsigned char green = rgba_buffer[i * 4 + 1]; + unsigned char blue = rgba_buffer[i * 4 + 2]; + unsigned char alpha = rgba_buffer[i * 4 + 3]; + + if (alpha < 128) { + // Transparent pixel - use color 0 (typically transparent/black) + indexed_data[i] = 0; + } else if (rgb_to_pal_table != NULL) { + // Use lookup table for color conversion + indexed_data[i] = rgb_to_pal_table[ + ((red >> 2) << 12) | ((green >> 2) << 6) | (blue >> 2) + ]; + } else { + // Fallback: simple grayscale conversion + indexed_data[i] = (red + green + blue) / 3; + } + } + + free(rgba_buffer); + + // Check if overlay with this name already exists + struct LensOverlayData *existing = NULL; + for (int i = 0; i < num_added_lens_overlays; i++) + { + if (strcasecmp(added_lens_overlays[i].name, name) == 0) + { + existing = &added_lens_overlays[i]; + break; + } + } + + if (existing) + { + // Override existing overlay + free(existing->data); + existing->data = indexed_data; + existing->width = ihdr.width; + existing->height = ihdr.height; + JUSTLOG("Overriding lens overlay '%s/%s'", path, name); + } + else + { + // Add new overlay + if (num_added_lens_overlays >= MAX_LENS_OVERLAYS) + { + ERRORLOG("Too many lens overlays (max %d)", MAX_LENS_OVERLAYS); + free(indexed_data); + return 0; + } + + added_lens_overlays[num_added_lens_overlays].name = strdup(name); + added_lens_overlays[num_added_lens_overlays].data = indexed_data; + added_lens_overlays[num_added_lens_overlays].width = ihdr.width; + added_lens_overlays[num_added_lens_overlays].height = ihdr.height; + num_added_lens_overlays++; + SYNCDBG(8, "Added PNG lens overlay '%s' (%dx%d)", name, ihdr.width, ihdr.height); + } + + return 1; +} + static int process_icon_from_list(const char *path, unzFile zip, int idx, VALUE *root) { VALUE *val; @@ -1424,6 +1737,21 @@ static int process_icon_from_list(const char *path, unzFile zip, int idx, VALUE return 1; } +static TbBool process_lens_overlay(const char *path, unzFile zip, VALUE *root) +{ + TbBool ret_ok = true; + for (int i = 0; i < value_array_size(root); i++) + { + VALUE *val = value_array_get(root, i); + if (!process_lens_overlay_from_list(path, zip, i, val)) + { + ret_ok = false; + continue; + } + } + return ret_ok; +} + static TbBool process_icon(const char *path, unzFile zip, VALUE *root) { TbBool ret_ok = true; @@ -1619,3 +1947,18 @@ int is_custom_icon(short icon_idx) icon_idx -= GUI_PANEL_SPRITES_COUNT; return (icon_idx >= 0) && (icon_idx < num_sprites(custom_sprites)); } + +const struct LensOverlayData* get_lens_overlay_data(const char *name) +{ + if (name == NULL || name[0] == '\0') + return NULL; + + for (int i = 0; i < num_added_lens_overlays; i++) + { + if (strcasecmp(added_lens_overlays[i].name, name) == 0) + { + return &added_lens_overlays[i]; + } + } + return NULL; +} diff --git a/src/custom_sprites.h b/src/custom_sprites.h index 8b3a6b624b..1986015368 100644 --- a/src/custom_sprites.h +++ b/src/custom_sprites.h @@ -42,6 +42,17 @@ const struct TbSprite *get_new_icon_sprite(short sprite_idx); const struct TbSprite *get_panel_sprite(short sprite_idx); int is_custom_icon(short icon_idx); +// Lens overlay data structure +struct LensOverlayData { + char *name; + unsigned char *data; + int width; + int height; +}; + +// Get lens overlay data by name (returns NULL if not found) +const struct LensOverlayData* get_lens_overlay_data(const char *name); + extern short bad_icon_id; #ifdef __cplusplus } diff --git a/src/lens_api.c b/src/lens_api.c index 5b36fbdebb..07f8162ca2 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -35,6 +35,7 @@ #include "vidmode.h" #include "game_legacy.h" #include "config_keeperfx.h" +#include "custom_sprites.h" #include "keeperfx.hpp" #include "post_inc.h" @@ -158,122 +159,36 @@ static TbBool load_overlay_image(struct LensConfig* lenscfg) return true; } - // Validate overlay filename if (lenscfg->overlay_file[0] == '\0') { - WARNLOG("Empty overlay filename"); + WARNLOG("Empty overlay name"); return false; } - // Detect file format from extension - const char* ext = strrchr(lenscfg->overlay_file, '.'); - TbBool is_raw = (ext != NULL && strcasecmp(ext, ".raw") == 0); + // Look up overlay by name in the custom sprites registry + const struct LensOverlayData* overlay = get_lens_overlay_data(lenscfg->overlay_file); - char* fname = NULL; - - if (is_raw) { - // RAW format: 256x256 8-bit indexed palette data - // Try loading from mod directory first (if specified), then fall back to base game - SYNCLOG("Loading RAW overlay: file='%s', mod_dir='%s'", lenscfg->overlay_file, lenscfg->overlay_mod_dir); - if (lenscfg->overlay_mod_dir[0] != '\0') { - fname = prepare_file_path_mod(lenscfg->overlay_mod_dir, FGrp_StdData, lenscfg->overlay_file); - SYNCLOG("Trying mod path: %s", fname); - if (fname[0] != '\0' && LbFileExists(fname)) { - SYNCLOG("Found RAW overlay in mod: %s", fname); - } else { - SYNCLOG("Not found in mod directory"); - fname = NULL; - } - } - - // Fall back to base game data directory - if (fname == NULL || fname[0] == '\0') { - fname = prepare_file_path(FGrp_StdData, lenscfg->overlay_file); - SYNCLOG("Trying base game path: %s", fname); - } - - // Load RAW file directly - expected to be 256x256 pixels - const size_t raw_size = (size_t)RAW_OVERLAY_SIZE * RAW_OVERLAY_SIZE; - lenscfg->overlay_data = (unsigned char*)malloc(raw_size); - if (lenscfg->overlay_data == NULL) { - ERRORLOG("Failed to allocate memory for RAW overlay image"); - return false; - } - - long loaded = LbFileLoadAt(fname, lenscfg->overlay_data); - if (loaded != (long)raw_size) { - WARNLOG("Failed to load RAW overlay \"%s\" (expected %lu bytes, got %ld)", lenscfg->overlay_file, (unsigned long)raw_size, loaded); - free(lenscfg->overlay_data); - lenscfg->overlay_data = NULL; - return false; - } - - lenscfg->overlay_width = RAW_OVERLAY_SIZE; - lenscfg->overlay_height = RAW_OVERLAY_SIZE; - SYNCDBG(7, "Loaded RAW overlay image \"%s\" (%dx%d)", lenscfg->overlay_file, RAW_OVERLAY_SIZE, RAW_OVERLAY_SIZE); - return true; - } - - // PNG/BMP format: Use SDL_image - SDL_Surface* surface = NULL; - - // First try: mod directory (if overlay_mod_dir is set) - if (lenscfg->overlay_mod_dir[0] != '\0') { - fname = prepare_file_path_mod(lenscfg->overlay_mod_dir, FGrp_StdData, lenscfg->overlay_file); - if (fname[0] != '\0' && LbFileExists(fname)) { - surface = IMG_Load(fname); - if (surface != NULL) { - SYNCDBG(8, "Loaded overlay from mod: %s", fname); - } - } - } - - // Second try: base game data directory - if (surface == NULL) { - fname = prepare_file_path(FGrp_StdData, lenscfg->overlay_file); - surface = IMG_Load(fname); - if (surface != NULL) { - SYNCDBG(8, "Loaded overlay from base game: %s", fname); - } - } - - if (surface == NULL) { - WARNLOG("Failed to load overlay image \"%s\": %s", lenscfg->overlay_file, IMG_GetError()); + if (overlay == NULL) { + WARNLOG("Lens overlay '%s' not found. Make sure it's defined in a lenses.json file in a .zip", lenscfg->overlay_file); return false; } - // Validate dimensions to prevent integer overflow - if (surface->w <= 0 || surface->h <= 0 || surface->w > 16384 || surface->h > 16384) { - WARNLOG("Invalid overlay image dimensions (%dx%d) in \"%s\"", surface->w, surface->h, lenscfg->overlay_file); - SDL_FreeSurface(surface); + if (overlay->data == NULL || overlay->width <= 0 || overlay->height <= 0) { + WARNLOG("Invalid lens overlay data for '%s'", lenscfg->overlay_file); return false; } - // Convert to 8-bit indexed format matching game palette - SDL_Surface* converted = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_INDEX8, 0); - if (converted == NULL) { - WARNLOG("Failed to convert overlay image \"%s\" to indexed format: %s", lenscfg->overlay_file, SDL_GetError()); - SDL_FreeSurface(surface); - return false; - } - - // Store the image data - size is safe due to dimension validation above - size_t size = (size_t)converted->w * converted->h; + size_t size = overlay->width * overlay->height; lenscfg->overlay_data = (unsigned char*)malloc(size); if (lenscfg->overlay_data == NULL) { ERRORLOG("Failed to allocate memory for overlay image"); - SDL_FreeSurface(converted); - SDL_FreeSurface(surface); return false; } - memcpy(lenscfg->overlay_data, converted->pixels, size); - lenscfg->overlay_width = converted->w; - lenscfg->overlay_height = converted->h; - - SDL_FreeSurface(converted); - SDL_FreeSurface(surface); + memcpy(lenscfg->overlay_data, overlay->data, size); + lenscfg->overlay_width = overlay->width; + lenscfg->overlay_height = overlay->height; - SYNCDBG(7, "Loaded overlay image \"%s\" (%dx%d)", lenscfg->overlay_file, lenscfg->overlay_width, lenscfg->overlay_height); + SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from registry", lenscfg->overlay_file, lenscfg->overlay_width, lenscfg->overlay_height); return true; } @@ -425,11 +340,11 @@ void setup_eye_lens(long nlens) } if ((lenscfg->flags & LCF_HasOverlay) != 0) { - SYNCLOG("Overlay config entered for lens %ld, file='%s', mod_dir='%s'", nlens, lenscfg->overlay_file, lenscfg->overlay_mod_dir); + SYNCDBG(7, "Overlay config entered for lens %ld, name='%s'", nlens, lenscfg->overlay_file); if (!load_overlay_image(lenscfg)) { WARNLOG("Failed to load overlay for lens %ld", nlens); } else { - SYNCLOG("Successfully loaded overlay %dx%d", lenscfg->overlay_width, lenscfg->overlay_height); + SYNCDBG(7, "Successfully loaded overlay %dx%d", lenscfg->overlay_width, lenscfg->overlay_height); } } game.applied_lens_type = nlens; @@ -489,13 +404,21 @@ static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long return; } - SYNCLOG("Drawing overlay: screen=%ldx%ld, overlay=%dx%d, alpha=%d", width, height, lenscfg->overlay_width, lenscfg->overlay_height, lenscfg->overlay_alpha); + SYNCDBG(8, "Drawing overlay: screen=%ldx%ld, overlay=%dx%d, alpha=%d", width, height, lenscfg->overlay_width, lenscfg->overlay_height, lenscfg->overlay_alpha); // Calculate scale factors to stretch/fit overlay to fill entire viewport float scale_x = (float)lenscfg->overlay_width / width; float scale_y = (float)lenscfg->overlay_height / height; - // Draw the overlay - palette index 0 is transparent, all others are solid + // Determine dithering threshold based on alpha (0-255) + // alpha=255 (opaque): draw all pixels + // alpha=128 (50%): draw checkerboard pattern + // alpha=0 (transparent): draw nothing + int alpha = lenscfg->overlay_alpha; + if (alpha < 0) alpha = 0; + if (alpha > 255) alpha = 255; + + // Draw the overlay with alpha transparency using dithering unsigned char* dst = dstbuf; for (long y = 0; y < height; y++) { @@ -511,11 +434,26 @@ static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long unsigned char overlay_pixel = src_row[src_x]; - // Color 0 (black) is treated as transparent - skip it completely - // All other pixels are drawn at full opacity (no dithering) + // Color 0 is treated as transparent - skip it completely if (overlay_pixel != 0) { - dst[x] = overlay_pixel; + // Apply alpha dithering using checkerboard pattern + if (alpha >= 255) + { + // Fully opaque - always draw + dst[x] = overlay_pixel; + } + else if (alpha > 0) + { + // Partial transparency - use checkerboard dithering + // Dither pattern alternates based on x+y coordinate + int dither = ((x + y) & 1) ? 255 : 0; + if (alpha > dither) + { + dst[x] = overlay_pixel; + } + } + // If alpha == 0, skip pixel (fully transparent) } } dst += dstpitch; @@ -566,7 +504,7 @@ void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbu draw_copy(dstbuf, dstpitch, srcbuf, srcpitch, width, height); } // Now draw the overlay on top of the game scene - SYNCLOG("Calling draw_overlay (flags=%d, data=%p)", lenscfg->flags, lenscfg->overlay_data); + SYNCDBG(8, "Calling draw_overlay (flags=%d, data=%p)", lenscfg->flags, lenscfg->overlay_data); draw_overlay(dstbuf, dstpitch, width, height, lenscfg); copied = true; } From f35386e07d7bac2c49cac25c77f50bffbffbe06d Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Mon, 26 Jan 2026 22:20:45 +0000 Subject: [PATCH 03/20] added example mod for lens overlay --- config/mods/lens_overlay_test/creatrs/imp.cfg | 4 ++++ .../lens_overlay_test/fxdata/lens_overlays.zip | Bin 0 -> 589 bytes config/mods/lens_overlay_test/fxdata/lenses.cfg | 15 +++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 config/mods/lens_overlay_test/creatrs/imp.cfg create mode 100644 config/mods/lens_overlay_test/fxdata/lens_overlays.zip create mode 100644 config/mods/lens_overlay_test/fxdata/lenses.cfg diff --git a/config/mods/lens_overlay_test/creatrs/imp.cfg b/config/mods/lens_overlay_test/creatrs/imp.cfg new file mode 100644 index 0000000000..02793ff701 --- /dev/null +++ b/config/mods/lens_overlay_test/creatrs/imp.cfg @@ -0,0 +1,4 @@ +; IMP with helmet visor overlay lens + mist effect + +[senses] +EyeEffect = TEST_WITH_MIST diff --git a/config/mods/lens_overlay_test/fxdata/lens_overlays.zip b/config/mods/lens_overlay_test/fxdata/lens_overlays.zip new file mode 100644 index 0000000000000000000000000000000000000000..a2d92f1b83c028c71b2bedcb6d525c64d32ffe9c GIT binary patch literal 589 zcmWIWW@Zs#U|`^2aGPuufI43o)IJHiW)F089tXSdGrW8%Dn{~W!n0$y%8BeS8zGcztO%P%XiCNizCZemO?#A1G)M04X++ eHDZeigr Date: Mon, 26 Jan 2026 22:45:50 +0000 Subject: [PATCH 04/20] changed load bit flags to descriptive enums --- src/custom_sprites.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/custom_sprites.c b/src/custom_sprites.c index 40bb0e046b..13a93519f6 100644 --- a/src/custom_sprites.c +++ b/src/custom_sprites.c @@ -94,6 +94,16 @@ unsigned char base_pal[PALETTE_SIZE]; int total_sprite_zip_count = 0; +// Indicates what custom assets to load +enum CustomLoadFlags { + /// @brief Custom sprites + CLF_Sprites = 0x1, + /// @brief Custom icons + CLF_Icons = 0x2, + /// @brief Lens overlays + CLF_LensOverlays = 0x4 +}; + static unsigned char big_scratch_data[1024*1024*16] = {0}; unsigned char *big_scratch = big_scratch_data; @@ -216,22 +226,22 @@ static int load_file_sprites(const char *path, const char *file_desc) int add_flag = 0; if (add_custom_sprite(path)) { - add_flag |= 0x1; + add_flag |= CLF_Sprites; } if (add_custom_json(path, "icons.json", &process_icon)) { - add_flag |= 0x2; + add_flag |= CLF_Icons; } if (add_custom_json(path, "lenses.json", &process_lens_overlay)) { - add_flag |= 0x4; + add_flag |= CLF_LensOverlays; } if (file_desc != NULL) { - if (add_flag & 0x1) + if (add_flag & CLF_Sprites) { JUSTLOG("Loaded per-map sprites from %s", file_desc); } @@ -240,7 +250,7 @@ static int load_file_sprites(const char *path, const char *file_desc) SYNCDBG(0, "Unable to load per-map sprites from %s", file_desc); } - if (add_flag & 0x2) + if (add_flag & CLF_Icons) { JUSTLOG("Loaded per-map icons from %s", file_desc); } @@ -249,7 +259,7 @@ static int load_file_sprites(const char *path, const char *file_desc) SYNCDBG(0, "Unable to load per-map icons from %s", file_desc); } - if (add_flag & 0x4) + if (add_flag & CLF_LensOverlays) { JUSTLOG("Loaded lens overlays from %s", file_desc); } @@ -277,9 +287,9 @@ static void load_dir_sprites(const char *dir_path, const char *dir_desc) do { sprintf(full_path, "%s/%s", dir_path, fe.Filename); int add_flag = load_file_sprites(full_path, NULL); - if (add_flag & 0x1) + if (add_flag & CLF_Sprites) cnt_sprite++; - if (add_flag & 0x2) + if (add_flag & CLF_Icons) cnt_icon++; cnt_zip++; } while (LbFileFindNext(ff, &fe) >= 0); From 3af40d0ceeb6c3635b7e24e2c4866bfc314a9c77 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Tue, 27 Jan 2026 06:49:54 +0000 Subject: [PATCH 05/20] corrected render order of swipe effect and lenses --- src/engine_redraw.c | 1 - src/thing_creature.c | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine_redraw.c b/src/engine_redraw.c index c43dc146fd..a495ef3c0a 100644 --- a/src/engine_redraw.c +++ b/src/engine_redraw.c @@ -629,7 +629,6 @@ void redraw_creature_view(void) ewnd.width, ewnd.height, lbDisplay.GraphicsScreenWidth); } remove_explored_flags_for_power_sight(player); - draw_swipe_graphic(); if ((game.operation_flags & GOF_ShowGui) != 0) { draw_whole_status_panel(); } diff --git a/src/thing_creature.c b/src/thing_creature.c index ba2b9d40c7..8cb575fb7b 100644 --- a/src/thing_creature.c +++ b/src/thing_creature.c @@ -4271,6 +4271,8 @@ void draw_creature_view(struct Thing *thing) // Draw on our buffer setup_engine_window(0, 0, MyScreenWidth, MyScreenHeight); engine(player, render_cam); + // Draw swipe into buffer BEFORE lens effects (so overlay renders on top of swipe) + draw_swipe_graphic(); // Restore original graphics settings lbDisplay.WScreen = wscr_cp; LbScreenLoadGraphicsWindow(&grwnd); From 11ce658f3b5751ad30babe5f3b9d937c6fb53610 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Tue, 27 Jan 2026 06:58:34 +0000 Subject: [PATCH 06/20] thing creature view now scales lens effect for viewport area correctly --- src/thing_creature.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/thing_creature.c b/src/thing_creature.c index 8cb575fb7b..d3bae8c33f 100644 --- a/src/thing_creature.c +++ b/src/thing_creature.c @@ -4273,13 +4273,18 @@ void draw_creature_view(struct Thing *thing) engine(player, render_cam); // Draw swipe into buffer BEFORE lens effects (so overlay renders on top of swipe) draw_swipe_graphic(); + // Get the actual viewport dimensions (accounts for sidebar) + long view_width = player->engine_window_width / pixel_size; + long view_height = player->engine_window_height / pixel_size; + long view_x = player->engine_window_x / pixel_size; // Restore original graphics settings lbDisplay.WScreen = wscr_cp; LbScreenLoadGraphicsWindow(&grwnd); - // Draw the buffer on real screen + // Draw the buffer on real screen using actual viewport dimensions setup_engine_window(0, 0, MyScreenWidth, MyScreenHeight); - draw_lens_effect(lbDisplay.WScreen, lbDisplay.GraphicsScreenWidth, scrmem, eye_lens_width, - MyScreenWidth/pixel_size, MyScreenHeight/pixel_size, game.applied_lens_type); + // Apply lens effect to the viewport area only (not including sidebar) + draw_lens_effect(lbDisplay.WScreen + view_x, lbDisplay.GraphicsScreenWidth, + scrmem + view_x, eye_lens_width, view_width, view_height, game.applied_lens_type); } struct Thing *get_creature_near_for_controlling(PlayerNumber plyr_idx, MapCoord x, MapCoord y) From d65b05644c855516175ef7c07aa2ef26be80dca9 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:21:48 +0000 Subject: [PATCH 07/20] ci added properties json for vscode mingw intellisense --- .vscode/c_cpp_properties.json | 84 +++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 8 deletions(-) diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 88331444dc..ef56dfc675 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -1,17 +1,85 @@ { "configurations": [ { - "name": "MinGW", + "name": "WSL MinGW", "includePath": [ - "${workspaceFolder}/**" + "${workspaceFolder}/src", + "${workspaceFolder}/deps/zlib/include", + "${workspaceFolder}/deps/spng/include", + "${workspaceFolder}/sdl/include", + "${workspaceFolder}/sdl/include/SDL2", + "${workspaceFolder}/deps/enet/include", + "${workspaceFolder}/deps/centijson/include", + "${workspaceFolder}/deps/astronomy/include", + "${workspaceFolder}/deps/openal/include", + "${workspaceFolder}/deps/luajit/include", + "${workspaceFolder}/deps/miniupnpc/include", + "${workspaceFolder}/deps/libnatpmp/include" ], - "defines": [], - "compilerPath": "/usr/bin/i686-w64-mingw32-gcc", - "cStandard": "c17", - "cppStandard": "gnu++17", + "defines": [ + "SPNG_STATIC=1", + "AL_LIBTYPE_STATIC", + "USE_PRE_FILE=1", + "BFDEBUG_LEVEL=0" + ], + "cStandard": "gnu11", + "cppStandard": "gnu++14", "intelliSenseMode": "windows-gcc-x86", - //"configurationProvider": "ms-vscode.makefile-tools", - //"compileCommands": "${workspaceFolder}/compile_commands.json", + "compileCommands": "${workspaceFolder}/compile_commands.json" + }, + { + "name": "Linux MinGW", + "includePath": [ + "${workspaceFolder}/src", + "${workspaceFolder}/deps/zlib/include", + "${workspaceFolder}/deps/spng/include", + "${workspaceFolder}/sdl/include", + "${workspaceFolder}/sdl/include/SDL2", + "${workspaceFolder}/deps/enet/include", + "${workspaceFolder}/deps/centijson/include", + "${workspaceFolder}/deps/astronomy/include", + "${workspaceFolder}/deps/openal/include", + "${workspaceFolder}/deps/luajit/include", + "${workspaceFolder}/deps/miniupnpc/include", + "${workspaceFolder}/deps/libnatpmp/include" + ], + "defines": [ + "SPNG_STATIC=1", + "AL_LIBTYPE_STATIC", + "USE_PRE_FILE=1", + "BFDEBUG_LEVEL=0" + ], + "compilerPath": "/usr/bin/i686-w64-mingw32-gcc", + "cStandard": "gnu11", + "cppStandard": "gnu++14", + "intelliSenseMode": "linux-gcc-x86" + }, + { + "name": "Windows MinGW", + "includePath": [ + "${workspaceFolder}/src", + "${workspaceFolder}/deps/zlib/include", + "${workspaceFolder}/deps/spng/include", + "${workspaceFolder}/sdl/include", + "${workspaceFolder}/sdl/include/SDL2", + "${workspaceFolder}/deps/enet/include", + "${workspaceFolder}/deps/centijson/include", + "${workspaceFolder}/deps/astronomy/include", + "${workspaceFolder}/deps/openal/include", + "${workspaceFolder}/deps/luajit/include", + "${workspaceFolder}/deps/miniupnpc/include", + "${workspaceFolder}/deps/libnatpmp/include" + ], + "defines": [ + "SPNG_STATIC=1", + "AL_LIBTYPE_STATIC", + "USE_PRE_FILE=1", + "BFDEBUG_LEVEL=0" + ], + "compilerPath": "C:\\msys64\\mingw32\\bin\\gcc.exe", + "cStandard": "gnu11", + "cppStandard": "gnu++14", + "intelliSenseMode": "windows-gcc-x86" } ], "version": 4 From 3fe10b1f5dbe6f889137c3cedf8c337115e961cd Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:22:31 +0000 Subject: [PATCH 08/20] feat allowe dfor custom lens mist effects, added logic to gracefull look at zip, then mod data, then core data in case of not finding the file --- .gitignore | 2 + src/config.c | 8 +- src/config_creature.c | 1 + src/config_lenses.c | 5 +- src/config_lenses.h | 2 +- src/creature_control.c | 1 + src/custom_sprites.c | 468 +++++++++++++++++++++++++++++------------ src/custom_sprites.h | 9 + src/lens_api.c | 116 +++++++++- src/thing_creature.c | 3 + 10 files changed, 479 insertions(+), 136 deletions(-) diff --git a/.gitignore b/.gitignore index 471615ab84..b9771d146e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ res/keeperfx_icon.ico /deps/*.tar.gz /cppcheck.cache /src/ver_defs.h +keeperfx.log +crash_log.txt diff --git a/src/config.c b/src/config.c index 691b47bf0e..2208cf017c 100644 --- a/src/config.c +++ b/src/config.c @@ -1008,7 +1008,7 @@ void set_defaults(const struct NamedFieldSet* named_fields_set, const char *conf named_fields_set->names[i].name = (char*)name_NamedField->field + i * named_fields_set->struct_size; named_fields_set->names[i].num = i; } - named_fields_set->names[named_fields_set->max_count - 1].name = NULL; // must be null for get_id + // Don't set terminator here - it will be set after parsing based on actual count, lens name lookups may depend on it } } @@ -1047,6 +1047,12 @@ TbBool parse_named_field_blocks(char *buf, long len, const char *config_textname parse_named_field_block(buf, len, config_textname, flags, blockname_null, named_fields_set->named_fields, named_fields_set, i); } + // Set the terminator at the actual count position (not at max_count-1) + if (named_fields_set->names != NULL && named_fields_set->count_field != NULL) + { + named_fields_set->names[*named_fields_set->count_field].name = NULL; + } + return true; } diff --git a/src/config_creature.c b/src/config_creature.c index b0e41e3c6e..a138980445 100644 --- a/src/config_creature.c +++ b/src/config_creature.c @@ -396,6 +396,7 @@ void check_and_auto_fix_stats(void) /* Initialize all creature model stats, called only once when first loading a map. */ void init_creature_model_stats(void) { + WARNLOG("CONFIG_DEBUG: init_creature_model_stats() called"); struct CreatureModelConfig *crconf; int n; for (int i = 0; i < CREATURE_TYPES_MAX; i++) diff --git a/src/config_lenses.c b/src/config_lenses.c index 7a8278308b..ce1b18ffe7 100644 --- a/src/config_lenses.c +++ b/src/config_lenses.c @@ -124,12 +124,15 @@ static int64_t value_overlay(const struct NamedField* named_field, const char* v lenses_conf.lenses[idx].flags |= LCF_HasOverlay; struct LensConfig* lenscfg = &lenses_conf.lenses[idx]; - // Only store the overlay name when processing position 0 (the name field), pos 1 is for alpha value + // Only store the overlay name when processing position 0 (the name field) + // Position 1 is the alpha value, handled by value_default if (named_field->argnum == 0) { + // Store the overlay name (a reference name from JSON) strncpy(lenscfg->overlay_file, value_text, DISKPATH_SIZE - 1); lenscfg->overlay_file[DISKPATH_SIZE - 1] = '\0'; + // Initialize overlay data pointer to NULL - will be looked up during setup lenscfg->overlay_data = NULL; lenscfg->overlay_width = 0; lenscfg->overlay_height = 0; diff --git a/src/config_lenses.h b/src/config_lenses.h index ea296eabba..c5f2605539 100644 --- a/src/config_lenses.h +++ b/src/config_lenses.h @@ -29,7 +29,7 @@ extern "C" { #endif /******************************************************************************/ -#define LENS_ITEMS_MAX 32 +#define LENS_ITEMS_MAX 255 enum LensConfigFlags { LCF_HasMist = 0x01, diff --git a/src/creature_control.c b/src/creature_control.c index 51e9b487b0..e1f3b2183b 100644 --- a/src/creature_control.c +++ b/src/creature_control.c @@ -179,6 +179,7 @@ struct Thing *create_and_control_creature_as_controller(struct PlayerInfo *playe if (thing->class_id == TCls_Creature) { struct CreatureModelConfig* crconf = creature_stats_get_from_thing(thing); + SYNCDBG(7,"Possessing creature '%s', eye_effect=%d", crconf->name, crconf->eye_effect); setup_eye_lens(crconf->eye_effect); } } diff --git a/src/custom_sprites.c b/src/custom_sprites.c index 13a93519f6..e67466eb63 100644 --- a/src/custom_sprites.c +++ b/src/custom_sprites.c @@ -90,6 +90,10 @@ static int num_added_icons = 0; static struct LensOverlayData added_lens_overlays[MAX_LENS_OVERLAYS]; static int num_added_lens_overlays = 0; +#define MAX_LENS_MISTS 64 +static struct LensMistData added_lens_mists[MAX_LENS_MISTS]; +static int num_added_lens_mists = 0; + unsigned char base_pal[PALETTE_SIZE]; int total_sprite_zip_count = 0; @@ -101,7 +105,9 @@ enum CustomLoadFlags { /// @brief Custom icons CLF_Icons = 0x2, /// @brief Lens overlays - CLF_LensOverlays = 0x4 + CLF_LensOverlays = 0x4, + /// @brief Lens mists + CLF_LensMists = 0x8 }; static unsigned char big_scratch_data[1024*1024*16] = {0}; @@ -115,6 +121,7 @@ static TbBool add_custom_json(const char *path, const char *name, TbBool (*process)(const char *path, unzFile zip, VALUE *root)); static TbBool process_lens_overlay(const char *path, unzFile zip, VALUE *root); +static TbBool process_lens_mist(const char *path, unzFile zip, VALUE *root); static TbBool process_icon(const char *path, unzFile zip, VALUE *root); @@ -239,6 +246,11 @@ static int load_file_sprites(const char *path, const char *file_desc) add_flag |= CLF_LensOverlays; } + if (add_custom_json(path, "mists.json", &process_lens_mist)) + { + add_flag |= CLF_LensMists; + } + if (file_desc != NULL) { if (add_flag & CLF_Sprites) @@ -267,6 +279,14 @@ static int load_file_sprites(const char *path, const char *file_desc) { SYNCDBG(0, "Unable to load lens overlays from %s", file_desc); } + if (add_flag & CLF_LensMists) + { + JUSTLOG("Loaded lens mists from %s", file_desc); + } + else + { + SYNCDBG(0, "Unable to load lens mists from %s", file_desc); + } total_sprite_zip_count++; } @@ -1362,6 +1382,143 @@ add_custom_json(const char *path, const char *name, TbBool (*process)(const char return 0; } +// Helper function to decode PNG from ZIP file to indexed palette format +// Returns indexed data on success, NULL on failure +// Caller must free the returned data +static unsigned char* decode_png_to_indexed(unzFile zip, const char *file, const char *path, + int *out_width, int *out_height, + unz_file_info64 *zip_info) +{ + if (zip_info->uncompressed_size > 1024 * 1024 * 4) + { + WARNLOG("PNG file too large: '%s' in '%s'", file, path); + return NULL; + } + + unsigned char *png_buffer = malloc(zip_info->uncompressed_size); + if (png_buffer == NULL) + { + ERRORLOG("Failed to allocate memory for PNG buffer"); + return NULL; + } + + if (unzReadCurrentFile(zip, png_buffer, zip_info->uncompressed_size) != zip_info->uncompressed_size) + { + WARNLOG("Failed to read '%s' from '%s'", file, path); + free(png_buffer); + return NULL; + } + + // Decode PNG using spng + spng_ctx *ctx = spng_ctx_new(0); + if (ctx == NULL) + { + ERRORLOG("Failed to create spng context"); + free(png_buffer); + return NULL; + } + + spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE); + size_t limit = 1024 * 1024 * 4; + spng_set_chunk_limits(ctx, limit, limit); + + if (spng_set_png_buffer(ctx, png_buffer, zip_info->uncompressed_size)) + { + ERRORLOG("Failed to set PNG buffer for '%s'", file); + spng_ctx_free(ctx); + free(png_buffer); + return NULL; + } + + struct spng_ihdr ihdr; + int r = spng_get_ihdr(ctx, &ihdr); + if (r) + { + ERRORLOG("spng_get_ihdr() error: %s for '%s'", spng_strerror(r), file); + spng_ctx_free(ctx); + free(png_buffer); + return NULL; + } + + if (ihdr.width <= 0 || ihdr.height <= 0 || ihdr.width > 4096 || ihdr.height > 4096) + { + WARNLOG("Invalid image dimensions (%dx%d) in '%s'", ihdr.width, ihdr.height, file); + spng_ctx_free(ctx); + free(png_buffer); + return NULL; + } + + // Decode to RGBA8 + size_t out_size; + int fmt = SPNG_FMT_RGBA8; + if (spng_decoded_image_size(ctx, fmt, &out_size)) + { + ERRORLOG("Failed to get decoded image size for '%s'", file); + spng_ctx_free(ctx); + free(png_buffer); + return NULL; + } + + unsigned char *rgba_buffer = malloc(out_size); + if (rgba_buffer == NULL) + { + ERRORLOG("Failed to allocate memory for decoded image"); + spng_ctx_free(ctx); + free(png_buffer); + return NULL; + } + + if (spng_decode_image(ctx, rgba_buffer, out_size, fmt, SPNG_DECODE_TRNS)) + { + ERRORLOG("Failed to decode PNG '%s'", file); + free(rgba_buffer); + spng_ctx_free(ctx); + free(png_buffer); + return NULL; + } + + spng_ctx_free(ctx); + free(png_buffer); + + // Convert RGBA to indexed palette format + size_t indexed_size = ihdr.width * ihdr.height; + unsigned char *indexed_data = malloc(indexed_size); + if (indexed_data == NULL) + { + ERRORLOG("Failed to allocate memory for indexed image data"); + free(rgba_buffer); + return NULL; + } + + // Convert RGBA to palette indices using rgb_to_pal_table + for (size_t i = 0; i < indexed_size; i++) + { + unsigned char red = rgba_buffer[i * 4 + 0]; + unsigned char green = rgba_buffer[i * 4 + 1]; + unsigned char blue = rgba_buffer[i * 4 + 2]; + unsigned char alpha = rgba_buffer[i * 4 + 3]; + + if (alpha < 128) { + // Transparent pixel - use color 0 (typically transparent/black) + indexed_data[i] = 0; + } else if (rgb_to_pal_table != NULL) { + // Use lookup table for color conversion + indexed_data[i] = rgb_to_pal_table[ + ((red >> 2) << 12) | ((green >> 2) << 6) | (blue >> 2) + ]; + } else { + // Fallback: simple grayscale conversion + indexed_data[i] = (red + green + blue) / 3; + } + } + + free(rgba_buffer); + + *out_width = ihdr.width; + *out_height = ihdr.height; + return indexed_data; +} + static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx, VALUE *root) { VALUE *val; @@ -1482,137 +1639,21 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx return 1; } - // PNG format handling - if (zip_info.uncompressed_size > 1024 * 1024 * 4) - { - WARNLOG("Lens overlay file too large: '%s' in '%s'", file, path); - unzCloseCurrentFile(zip); - return 0; - } - - unsigned char *png_buffer = malloc(zip_info.uncompressed_size); - if (png_buffer == NULL) - { - ERRORLOG("Failed to allocate memory for PNG buffer"); - unzCloseCurrentFile(zip); - return 0; - } - - if (unzReadCurrentFile(zip, png_buffer, zip_info.uncompressed_size) != zip_info.uncompressed_size) - { - WARNLOG("Failed to read '%s' from '%s'", file, path); - free(png_buffer); - unzCloseCurrentFile(zip); - return 0; - } - - unzCloseCurrentFile(zip); - - // Decode PNG using spng - spng_ctx *ctx = spng_ctx_new(0); - if (ctx == NULL) - { - ERRORLOG("Failed to create spng context"); - free(png_buffer); - return 0; - } - - spng_set_crc_action(ctx, SPNG_CRC_USE, SPNG_CRC_USE); - size_t limit = 1024 * 1024 * 4; - spng_set_chunk_limits(ctx, limit, limit); - - if (spng_set_png_buffer(ctx, png_buffer, zip_info.uncompressed_size)) - { - ERRORLOG("Failed to set PNG buffer for '%s'", file); - spng_ctx_free(ctx); - free(png_buffer); - return 0; - } - - struct spng_ihdr ihdr; - int r = spng_get_ihdr(ctx, &ihdr); - if (r) - { - ERRORLOG("spng_get_ihdr() error: %s for '%s'", spng_strerror(r), file); - spng_ctx_free(ctx); - free(png_buffer); - return 0; - } - - if (ihdr.width <= 0 || ihdr.height <= 0 || ihdr.width > 4096 || ihdr.height > 4096) - { - WARNLOG("Invalid lens overlay dimensions (%dx%d) in '%s'", ihdr.width, ihdr.height, file); - spng_ctx_free(ctx); - free(png_buffer); - return 0; - } - - // Decode to RGBA8 - size_t out_size; - int fmt = SPNG_FMT_RGBA8; - if (spng_decoded_image_size(ctx, fmt, &out_size)) - { - ERRORLOG("Failed to get decoded image size for '%s'", file); - spng_ctx_free(ctx); - free(png_buffer); - return 0; - } - - unsigned char *rgba_buffer = malloc(out_size); - if (rgba_buffer == NULL) - { - ERRORLOG("Failed to allocate memory for decoded image"); - spng_ctx_free(ctx); - free(png_buffer); - return 0; - } - - if (spng_decode_image(ctx, rgba_buffer, out_size, fmt, SPNG_DECODE_TRNS)) - { - ERRORLOG("Failed to decode PNG '%s'", file); - free(rgba_buffer); - spng_ctx_free(ctx); - free(png_buffer); - return 0; - } - - spng_ctx_free(ctx); - free(png_buffer); - - // Convert RGBA to indexed palette format - size_t indexed_size = ihdr.width * ihdr.height; - unsigned char *indexed_data = malloc(indexed_size); + // PNG format handling - use shared helper + int width, height; + unsigned char *indexed_data = decode_png_to_indexed(zip, file, path, &width, &height, &zip_info); if (indexed_data == NULL) { - ERRORLOG("Failed to allocate memory for indexed image data"); - free(rgba_buffer); return 0; } - // Convert RGBA to palette indices using rgb_to_pal_table - for (size_t i = 0; i < indexed_size; i++) + if (width <= 0 || height <= 0 || width > 4096 || height > 4096) { - unsigned char red = rgba_buffer[i * 4 + 0]; - unsigned char green = rgba_buffer[i * 4 + 1]; - unsigned char blue = rgba_buffer[i * 4 + 2]; - unsigned char alpha = rgba_buffer[i * 4 + 3]; - - if (alpha < 128) { - // Transparent pixel - use color 0 (typically transparent/black) - indexed_data[i] = 0; - } else if (rgb_to_pal_table != NULL) { - // Use lookup table for color conversion - indexed_data[i] = rgb_to_pal_table[ - ((red >> 2) << 12) | ((green >> 2) << 6) | (blue >> 2) - ]; - } else { - // Fallback: simple grayscale conversion - indexed_data[i] = (red + green + blue) / 3; - } + WARNLOG("Invalid lens overlay dimensions (%dx%d) in '%s'", width, height, file); + free(indexed_data); + return 0; } - free(rgba_buffer); - // Check if overlay with this name already exists struct LensOverlayData *existing = NULL; for (int i = 0; i < num_added_lens_overlays; i++) @@ -1629,8 +1670,8 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx // Override existing overlay free(existing->data); existing->data = indexed_data; - existing->width = ihdr.width; - existing->height = ihdr.height; + existing->width = width; + existing->height = height; JUSTLOG("Overriding lens overlay '%s/%s'", path, name); } else @@ -1645,10 +1686,10 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx added_lens_overlays[num_added_lens_overlays].name = strdup(name); added_lens_overlays[num_added_lens_overlays].data = indexed_data; - added_lens_overlays[num_added_lens_overlays].width = ihdr.width; - added_lens_overlays[num_added_lens_overlays].height = ihdr.height; + added_lens_overlays[num_added_lens_overlays].width = width; + added_lens_overlays[num_added_lens_overlays].height = height; num_added_lens_overlays++; - SYNCDBG(8, "Added PNG lens overlay '%s' (%dx%d)", name, ihdr.width, ihdr.height); + SYNCDBG(8, "Added PNG lens overlay '%s' (%dx%d)", name, width, height); } return 1; @@ -1762,6 +1803,160 @@ static TbBool process_lens_overlay(const char *path, unzFile zip, VALUE *root) return ret_ok; } +static int process_lens_mist_from_list(const char *path, unzFile zip, int idx, VALUE *root) +{ + VALUE *val; + + val = value_dict_get(root, "name"); + if (val == NULL) + { + WARNLOG("Invalid lens mist %s/mists.json[%d]: no \"name\" key", path, idx); + return 0; + } + const char *name = value_string(val); + SYNCDBG(2, "found lens mist: '%s/%s'", path, name); + + VALUE *file_value = value_dict_get(root, "file"); + if (file_value == NULL) + { + WARNLOG("Invalid lens mist %s/mists.json[%d]: no \"file\" key", path, idx); + return 0; + } + + const char *file = NULL; + if (value_type(file_value) == VALUE_STRING) + { + file = value_string(file_value); + } + else if (value_type(file_value) == VALUE_ARRAY && value_array_size(file_value) > 0) + { + file = value_string(value_array_get(file_value, 0)); + } + else + { + WARNLOG("Invalid lens mist %s/mists.json[%d]: invalid \"file\" value", path, idx); + return 0; + } + + if (fastUnzLocateFile(zip, file, 0)) + { + WARNLOG("File '%s' not found in '%s'", file, path); + return 0; + } + + unz_file_info64 zip_info = {0}; + if (UNZ_OK != unzGetCurrentFileInfo64(zip, &zip_info, NULL, 0, NULL, 0, NULL, 0)) + { + WARNLOG("Failed to get file info for '%s' in '%s'", file, path); + return 0; + } + + if (UNZ_OK != unzOpenCurrentFile(zip)) + { + return 0; + } + + unsigned char *mist_data = NULL; + const size_t mist_size = 256 * 256; + + // Try PNG format first + if (zip_info.uncompressed_size != mist_size) + { + // Not RAW format, try PNG + int width, height; + mist_data = decode_png_to_indexed(zip, file, path, &width, &height, &zip_info); + if (mist_data == NULL) + { + // Already closed by decode function on failure + return 0; + } + + // Validate mist dimensions (must be 256x256) + if (width != 256 || height != 256) + { + WARNLOG("Invalid mist dimensions for '%s' in '%s': expected 256x256, got %dx%d", + file, path, width, height); + free(mist_data); + return 0; + } + + SYNCDBG(7, "Loaded PNG mist '%s' from '%s'", file, path); + } + else + { + // RAW format (256x256 = 65536 bytes) + mist_data = malloc(mist_size); + if (mist_data == NULL) + { + ERRORLOG("Failed to allocate memory for mist data"); + unzCloseCurrentFile(zip); + return 0; + } + + if (unzReadCurrentFile(zip, mist_data, mist_size) != mist_size) + { + WARNLOG("Failed to read mist file '%s' from '%s'", file, path); + free(mist_data); + unzCloseCurrentFile(zip); + return 0; + } + + unzCloseCurrentFile(zip); + SYNCDBG(7, "Loaded RAW mist '%s' from '%s'", file, path); + } + + // Check if mist with this name already exists + struct LensMistData *existing = NULL; + for (int i = 0; i < num_added_lens_mists; i++) + { + if (strcasecmp(added_lens_mists[i].name, name) == 0) + { + existing = &added_lens_mists[i]; + break; + } + } + + if (existing) + { + // Override existing mist + free(existing->data); + existing->data = mist_data; + JUSTLOG("Overriding lens mist '%s/%s'", path, name); + } + else + { + // Add new mist + if (num_added_lens_mists >= MAX_LENS_MISTS) + { + ERRORLOG("Too many lens mists (max %d)", MAX_LENS_MISTS); + free(mist_data); + return 0; + } + + added_lens_mists[num_added_lens_mists].name = strdup(name); + added_lens_mists[num_added_lens_mists].data = mist_data; + num_added_lens_mists++; + SYNCDBG(8, "Added lens mist '%s' (256x256)", name); + } + + return 1; +} + +static TbBool process_lens_mist(const char *path, unzFile zip, VALUE *root) +{ + TbBool ret_ok = true; + for (int i = 0; i < value_array_size(root); i++) + { + VALUE *val = value_array_get(root, i); + if (!process_lens_mist_from_list(path, zip, i, val)) + { + ret_ok = false; + continue; + } + } + return ret_ok; +} + static TbBool process_icon(const char *path, unzFile zip, VALUE *root) { TbBool ret_ok = true; @@ -1972,3 +2167,18 @@ const struct LensOverlayData* get_lens_overlay_data(const char *name) } return NULL; } + +const struct LensMistData* get_lens_mist_data(const char *name) +{ + if (name == NULL || name[0] == '\0') + return NULL; + + for (int i = 0; i < num_added_lens_mists; i++) + { + if (strcasecmp(added_lens_mists[i].name, name) == 0) + { + return &added_lens_mists[i]; + } + } + return NULL; +} diff --git a/src/custom_sprites.h b/src/custom_sprites.h index 1986015368..a89b0d85d6 100644 --- a/src/custom_sprites.h +++ b/src/custom_sprites.h @@ -50,9 +50,18 @@ struct LensOverlayData { int height; }; +// Lens mist data structure +struct LensMistData { + char *name; + unsigned char *data; // 256x256 mist texture +}; + // Get lens overlay data by name (returns NULL if not found) const struct LensOverlayData* get_lens_overlay_data(const char *name); +// Get lens mist data by name (returns NULL if not found) +const struct LensMistData* get_lens_mist_data(const char *name); + extern short bad_icon_id; #ifdef __cplusplus } diff --git a/src/lens_api.c b/src/lens_api.c index 07f8162ca2..399d643767 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -36,6 +36,7 @@ #include "game_legacy.h" #include "config_keeperfx.h" #include "custom_sprites.h" +#include "config_mods.h" #include "keeperfx.hpp" #include "post_inc.h" @@ -164,14 +165,95 @@ static TbBool load_overlay_image(struct LensConfig* lenscfg) return false; } + // Check if this is a direct file path (contains .raw or other file extension) + const char* ext = strrchr(lenscfg->overlay_file, '.'); + if (ext != NULL && strcasecmp(ext, ".raw") == 0) { + // Load directly from file in /data directory + char* fname = prepare_file_path(FGrp_StdData, lenscfg->overlay_file); + + // RAW overlay files are 256x256 8-bit indexed (same as mist files) + lenscfg->overlay_width = 256; + lenscfg->overlay_height = 256; + size_t size = lenscfg->overlay_width * lenscfg->overlay_height; // 1 byte per pixel + + lenscfg->overlay_data = (unsigned char*)malloc(size); + if (lenscfg->overlay_data == NULL) { + ERRORLOG("Failed to allocate memory for overlay image"); + return false; + } + + if (LbFileLoadAt(fname, lenscfg->overlay_data) != size) { + WARNLOG("Failed to load overlay file '%s' from /data directory", lenscfg->overlay_file); + free(lenscfg->overlay_data); + lenscfg->overlay_data = NULL; + lenscfg->overlay_width = 0; + lenscfg->overlay_height = 0; + return false; + } + + SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from file", lenscfg->overlay_file, lenscfg->overlay_width, lenscfg->overlay_height); + return true; + } + // Look up overlay by name in the custom sprites registry const struct LensOverlayData* overlay = get_lens_overlay_data(lenscfg->overlay_file); + // If not found in registry, try loading from /data directory as RAW file fallback if (overlay == NULL) { - WARNLOG("Lens overlay '%s' not found. Make sure it's defined in a lenses.json file in a .zip", lenscfg->overlay_file); - return false; + // Try loading RAW file from mod data directories, then base game /data + char fname_raw[256]; + snprintf(fname_raw, sizeof(fname_raw), "%s.raw", lenscfg->overlay_file); + + // RAW overlay files are 256x256 8-bit indexed (1 byte per pixel, same as mist files) + lenscfg->overlay_width = 256; + lenscfg->overlay_height = 256; + size_t size = lenscfg->overlay_width * lenscfg->overlay_height; // 65536 bytes + + lenscfg->overlay_data = (unsigned char*)malloc(size); + if (lenscfg->overlay_data == NULL) { + ERRORLOG("Failed to allocate memory for overlay image"); + return false; + } + + // Try loading from all loaded mods' data directories first (same order as mod loading) + TbBool loaded = false; + + // Check after_base mods + for (int i = 0; i < mods_conf.after_base_cnt && !loaded; i++) { + const struct ModConfigItem* mod_item = &mods_conf.after_base_item[i]; + if (mod_item->state.fx_data) { + char mod_dir[256]; + snprintf(mod_dir, sizeof(mod_dir), "%s/%s", MODS_DIR_NAME, mod_item->name); + char* fname_mod = prepare_file_path_mod(mod_dir, FGrp_StdData, fname_raw); + if (LbFileLoadAt(fname_mod, lenscfg->overlay_data) == size) { + SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from mod '%s' data directory", fname_raw, lenscfg->overlay_width, lenscfg->overlay_height, mod_item->name); + loaded = true; + } + } + } + + // If not found in mods, try base game /data directory + if (!loaded) { + char* fname_base = prepare_file_path(FGrp_StdData, fname_raw); + if (LbFileLoadAt(fname_base, lenscfg->overlay_data) == size) { + SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from base game data directory", fname_raw, lenscfg->overlay_width, lenscfg->overlay_height); + loaded = true; + } + } + + if (!loaded) { + WARNLOG("Lens overlay '%s' not found. Make sure it's defined in a lenses.json file in a .zip or provide a .raw file in /data", lenscfg->overlay_file); + free(lenscfg->overlay_data); + lenscfg->overlay_data = NULL; + lenscfg->overlay_width = 0; + lenscfg->overlay_height = 0; + return false; + } + + return true; } + // Found in registry - load it if (overlay->data == NULL || overlay->width <= 0 || overlay->height <= 0) { WARNLOG("Invalid lens overlay data for '%s'", lenscfg->overlay_file); return false; @@ -223,6 +305,8 @@ TbBool clear_lens_palette(void) static void set_lens_palette(unsigned char *palette) { struct PlayerInfo* player = get_my_player(); + WARNLOG("CONFIG_DEBUG: set_lens_palette called, palette[0-4]: %02x %02x %02x %02x %02x", + palette[0], palette[1], palette[2], palette[3], palette[4]); player->main_palette = palette; player->lens_palette = palette; } @@ -285,6 +369,7 @@ void initialise_eye_lenses(void) void setup_eye_lens(long nlens) { + WARNLOG("CONFIG_DEBUG: setup_eye_lens called with nlens=%ld", nlens); if ((game.mode_flags & MFlg_EyeLensReady) == 0) { WARNLOG("Can't setup lens - not initialized"); @@ -309,8 +394,30 @@ void setup_eye_lens(long nlens) if ((lenscfg->flags & LCF_HasMist) != 0) { SYNCDBG(9,"Mist config entered"); - char* fname = prepare_file_path(FGrp_StdData, lenscfg->mist_file); - LbFileLoadAt(fname, eye_lens_memory); + + // Try to load from registry first (ZIP files with mists.json) + const struct LensMistData* mist = get_lens_mist_data(lenscfg->mist_file); + + if (mist != NULL && mist->data != NULL) + { + // Load from registry + memcpy(eye_lens_memory, mist->data, 256 * 256); + SYNCDBG(7, "Loaded mist '%s' from registry", lenscfg->mist_file); + } + else + { + // Fall back to loading from /data directory + char* fname = prepare_file_path(FGrp_StdData, lenscfg->mist_file); + if (LbFileLoadAt(fname, eye_lens_memory) > 0) + { + SYNCDBG(7, "Loaded mist '%s' from file", lenscfg->mist_file); + } + else + { + WARNLOG("Failed to load mist file '%s'", lenscfg->mist_file); + } + } + setup_mist((unsigned char *)eye_lens_memory, &pixmap.fade_tables[(lenscfg->mist_lightness)*256], &pixmap.ghost[(lenscfg->mist_ghost)*256]); @@ -336,6 +443,7 @@ void setup_eye_lens(long nlens) if ((lenscfg->flags & LCF_HasPalette) != 0) { SYNCDBG(9,"Palette config entered"); + WARNLOG("CONFIG_DEBUG: setup_eye_lens %ld calling set_lens_palette, flags=0x%02x", nlens, lenscfg->flags); set_lens_palette(lenscfg->palette); } if ((lenscfg->flags & LCF_HasOverlay) != 0) diff --git a/src/thing_creature.c b/src/thing_creature.c index d3bae8c33f..1d82e3ca6b 100644 --- a/src/thing_creature.c +++ b/src/thing_creature.c @@ -286,6 +286,7 @@ TbBool control_creature_as_controller(struct PlayerInfo *player, struct Thing *t if (thing->class_id == TCls_Creature) { crconf = creature_stats_get_from_thing(thing); + SYNCDBG(7,"Controlling creature '%s', eye_effect=%d", crconf->name, crconf->eye_effect); setup_eye_lens(crconf->eye_effect); } return true; @@ -4253,6 +4254,8 @@ void draw_creature_view(struct Thing *thing) if (((game.mode_flags & MFlg_EyeLensReady) == 0) || (eye_lens_memory == NULL) || (game.applied_lens_type == 0)) { engine(player, render_cam); + // Still need to draw swipe even when no lens effect is active. + draw_swipe_graphic(); return; } // So there is an eye lens - we have to put a buffer in place of screen, From 8a6de5fdf2babcc9dcd92c9d8b3d4346702ea057 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:25:47 +0000 Subject: [PATCH 09/20] refactor: extract mod fallback file loading into reusable helper function - Created try_load_file_from_mods_with_fallback() helper function - Reduces code duplication for mod->base game file loading pattern - Applied to lens overlay loading as proof of concept - Helper function can be reused for other mod fallback scenarios --- src/lens_api.c | 90 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 32 deletions(-) diff --git a/src/lens_api.c b/src/lens_api.c index 399d643767..ba276d9992 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -51,6 +51,50 @@ extern "C" { uint32_t *eye_lens_memory; TbPixel *eye_lens_spare_screen_memory; /******************************************************************************/ +/** + * Try to load a file from mod data directories with fallback to base game. + * This is a helper function to reduce code duplication when loading files + * that support mod overrides. + * + * @param fname_base The base filename (without path) to load + * @param fgroup The file group (e.g. FGrp_StdData) + * @param buffer Pre-allocated buffer to load data into + * @param expected_size Expected file size in bytes + * @param loaded_from Optional output parameter to store which mod loaded the file (NULL if base game) + * @return true if file was loaded successfully, false otherwise + */ +static TbBool try_load_file_from_mods_with_fallback(const char* fname_base, short fgroup, + unsigned char* buffer, size_t expected_size, + const char** loaded_from) +{ + // Try loading from all loaded mods' data directories first (same order as mod loading) + for (int i = 0; i < mods_conf.after_base_cnt; i++) { + const struct ModConfigItem* mod_item = &mods_conf.after_base_item[i]; + if (mod_item->state.fx_data) { + char mod_dir[256]; + snprintf(mod_dir, sizeof(mod_dir), "%s/%s", MODS_DIR_NAME, mod_item->name); + char* fname_mod = prepare_file_path_mod(mod_dir, fgroup, fname_base); + if (LbFileLoadAt(fname_mod, buffer) == expected_size) { + if (loaded_from != NULL) { + *loaded_from = mod_item->name; + } + return true; + } + } + } + + // If not found in mods, try base game directory + char* fname_base_path = prepare_file_path(fgroup, fname_base); + if (LbFileLoadAt(fname_base_path, buffer) == expected_size) { + if (loaded_from != NULL) { + *loaded_from = NULL; // NULL indicates base game + } + return true; + } + + return false; +} +/******************************************************************************/ void init_lens(uint32_t *lens_mem, int width, int height, int pitch, int nlens, int mag, int period); /******************************************************************************/ @@ -215,42 +259,24 @@ static TbBool load_overlay_image(struct LensConfig* lenscfg) return false; } - // Try loading from all loaded mods' data directories first (same order as mod loading) - TbBool loaded = false; - - // Check after_base mods - for (int i = 0; i < mods_conf.after_base_cnt && !loaded; i++) { - const struct ModConfigItem* mod_item = &mods_conf.after_base_item[i]; - if (mod_item->state.fx_data) { - char mod_dir[256]; - snprintf(mod_dir, sizeof(mod_dir), "%s/%s", MODS_DIR_NAME, mod_item->name); - char* fname_mod = prepare_file_path_mod(mod_dir, FGrp_StdData, fname_raw); - if (LbFileLoadAt(fname_mod, lenscfg->overlay_data) == size) { - SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from mod '%s' data directory", fname_raw, lenscfg->overlay_width, lenscfg->overlay_height, mod_item->name); - loaded = true; - } - } - } - - // If not found in mods, try base game /data directory - if (!loaded) { - char* fname_base = prepare_file_path(FGrp_StdData, fname_raw); - if (LbFileLoadAt(fname_base, lenscfg->overlay_data) == size) { + // Try loading from mods with fallback to base game + const char* loaded_from = NULL; + if (try_load_file_from_mods_with_fallback(fname_raw, FGrp_StdData, lenscfg->overlay_data, size, &loaded_from)) { + if (loaded_from != NULL) { + SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from mod '%s' data directory", fname_raw, lenscfg->overlay_width, lenscfg->overlay_height, loaded_from); + } else { SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from base game data directory", fname_raw, lenscfg->overlay_width, lenscfg->overlay_height); - loaded = true; } + return true; } - if (!loaded) { - WARNLOG("Lens overlay '%s' not found. Make sure it's defined in a lenses.json file in a .zip or provide a .raw file in /data", lenscfg->overlay_file); - free(lenscfg->overlay_data); - lenscfg->overlay_data = NULL; - lenscfg->overlay_width = 0; - lenscfg->overlay_height = 0; - return false; - } - - return true; + // Not found anywhere + WARNLOG("Lens overlay '%s' not found. Make sure it's defined in a lenses.json file in a .zip or provide a .raw file in /data", lenscfg->overlay_file); + free(lenscfg->overlay_data); + lenscfg->overlay_data = NULL; + lenscfg->overlay_width = 0; + lenscfg->overlay_height = 0; + return false; } // Found in registry - load it From 819f549337a3e8afe0afbc36894b46f9b19fc9bf Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:28:13 +0000 Subject: [PATCH 10/20] refactor: apply mod fallback helper to mist file loading - Mist files now check mods' data directories before base game - Consistent mod fallback behavior between overlays and mists - Better logging to show which mod provided the mist file --- src/lens_api.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lens_api.c b/src/lens_api.c index ba276d9992..76cc2279c5 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -432,11 +432,16 @@ void setup_eye_lens(long nlens) } else { - // Fall back to loading from /data directory - char* fname = prepare_file_path(FGrp_StdData, lenscfg->mist_file); - if (LbFileLoadAt(fname, eye_lens_memory) > 0) + // Fall back to loading from mods with fallback to base game /data directory + const char* loaded_from = NULL; + if (try_load_file_from_mods_with_fallback(lenscfg->mist_file, FGrp_StdData, + (unsigned char*)eye_lens_memory, 256 * 256, &loaded_from)) { - SYNCDBG(7, "Loaded mist '%s' from file", lenscfg->mist_file); + if (loaded_from != NULL) { + SYNCDBG(7, "Loaded mist '%s' from mod '%s' data directory", lenscfg->mist_file, loaded_from); + } else { + SYNCDBG(7, "Loaded mist '%s' from base game data directory", lenscfg->mist_file); + } } else { From f249a5f29276d5aad9751817785cd8f7b9a71979 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:36:51 +0000 Subject: [PATCH 11/20] fix: correct mod fallback helper to check mod_dir flag and file existence - Changed from checking fx_data flag to mod_dir flag (allows any mod directory) - Added LbFileExists() check before LbFileLoadAt() to avoid error spam - Fixes crash when loading files that don't exist in all mods - Prevents access violations from failed file loads --- src/lens_api.c | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/lens_api.c b/src/lens_api.c index 76cc2279c5..beb4399103 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -70,26 +70,35 @@ static TbBool try_load_file_from_mods_with_fallback(const char* fname_base, shor // Try loading from all loaded mods' data directories first (same order as mod loading) for (int i = 0; i < mods_conf.after_base_cnt; i++) { const struct ModConfigItem* mod_item = &mods_conf.after_base_item[i]; - if (mod_item->state.fx_data) { + // Only check mods that have a directory (mod_dir flag) + if (mod_item->state.mod_dir) { char mod_dir[256]; snprintf(mod_dir, sizeof(mod_dir), "%s/%s", MODS_DIR_NAME, mod_item->name); char* fname_mod = prepare_file_path_mod(mod_dir, fgroup, fname_base); - if (LbFileLoadAt(fname_mod, buffer) == expected_size) { - if (loaded_from != NULL) { - *loaded_from = mod_item->name; + + // Check if file exists first to avoid error messages + if (LbFileExists(fname_mod)) { + long loaded = LbFileLoadAt(fname_mod, buffer); + if (loaded == expected_size) { + if (loaded_from != NULL) { + *loaded_from = mod_item->name; + } + return true; } - return true; } } } // If not found in mods, try base game directory char* fname_base_path = prepare_file_path(fgroup, fname_base); - if (LbFileLoadAt(fname_base_path, buffer) == expected_size) { - if (loaded_from != NULL) { - *loaded_from = NULL; // NULL indicates base game + if (LbFileExists(fname_base_path)) { + long loaded = LbFileLoadAt(fname_base_path, buffer); + if (loaded == expected_size) { + if (loaded_from != NULL) { + *loaded_from = NULL; // NULL indicates base game + } + return true; } - return true; } return false; From edb422185e79e0b68ef34765451ed2882b90ddee Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:54:28 +0000 Subject: [PATCH 12/20] feat: added try_load_file_from_mods_with_fallback to consolidate load code --- config/mods/lens_overlay_test/creatrs/imp.cfg | 4 -- .../fxdata/lens_overlays.zip | Bin 589 -> 0 bytes .../mods/lens_overlay_test/fxdata/lenses.cfg | 15 ------- src/lens_api.c | 38 +++++++++++++----- 4 files changed, 28 insertions(+), 29 deletions(-) delete mode 100644 config/mods/lens_overlay_test/creatrs/imp.cfg delete mode 100644 config/mods/lens_overlay_test/fxdata/lens_overlays.zip delete mode 100644 config/mods/lens_overlay_test/fxdata/lenses.cfg diff --git a/config/mods/lens_overlay_test/creatrs/imp.cfg b/config/mods/lens_overlay_test/creatrs/imp.cfg deleted file mode 100644 index 02793ff701..0000000000 --- a/config/mods/lens_overlay_test/creatrs/imp.cfg +++ /dev/null @@ -1,4 +0,0 @@ -; IMP with helmet visor overlay lens + mist effect - -[senses] -EyeEffect = TEST_WITH_MIST diff --git a/config/mods/lens_overlay_test/fxdata/lens_overlays.zip b/config/mods/lens_overlay_test/fxdata/lens_overlays.zip deleted file mode 100644 index a2d92f1b83c028c71b2bedcb6d525c64d32ffe9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 589 zcmWIWW@Zs#U|`^2aGPuufI43o)IJHiW)F089tXSdGrW8%Dn{~W!n0$y%8BeS8zGcztO%P%XiCNizCZemO?#A1G)M04X++ eHDZeigrname); char* fname_mod = prepare_file_path_mod(mod_dir, fgroup, fname_base); + SYNCDBG(7, "CONFIG_DEBUG: Checking mod file: '%s'", fname_mod); + // Check if file exists first to avoid error messages if (LbFileExists(fname_mod)) { - long loaded = LbFileLoadAt(fname_mod, buffer); - if (loaded == expected_size) { - if (loaded_from != NULL) { - *loaded_from = mod_item->name; + // Check file size before loading to prevent buffer overflows + long file_size = LbFileLengthRnc(fname_mod); + SYNCDBG(7, "CONFIG_DEBUG: File exists (size: %ld bytes, expected: %lu bytes)", file_size, (unsigned long)expected_size); + + // Only load if file size matches expected size (exact match required for safety) + if (file_size == expected_size) { + long loaded = LbFileLoadAt(fname_mod, buffer); + SYNCDBG(7, "CONFIG_DEBUG: Loaded %ld bytes", loaded); + if (loaded == expected_size) { + if (loaded_from != NULL) { + *loaded_from = mod_item->name; + } + return true; } - return true; + } else { + WARNLOG("File '%s' has wrong size: %ld bytes (expected %lu bytes)", fname_mod, file_size, (unsigned long)expected_size); } } } @@ -92,12 +104,18 @@ static TbBool try_load_file_from_mods_with_fallback(const char* fname_base, shor // If not found in mods, try base game directory char* fname_base_path = prepare_file_path(fgroup, fname_base); if (LbFileExists(fname_base_path)) { - long loaded = LbFileLoadAt(fname_base_path, buffer); - if (loaded == expected_size) { - if (loaded_from != NULL) { - *loaded_from = NULL; // NULL indicates base game + // Check file size before loading to prevent buffer overflows + long file_size = LbFileLengthRnc(fname_base_path); + + // Only load if file size matches expected size (exact match required for safety) + if (file_size == expected_size) { + long loaded = LbFileLoadAt(fname_base_path, buffer); + if (loaded == expected_size) { + if (loaded_from != NULL) { + *loaded_from = NULL; // NULL indicates base game + } + return true; } - return true; } } From 008bda5c830f9088823700a55ecc0207e2cc4aec Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Fri, 30 Jan 2026 17:59:36 +0000 Subject: [PATCH 13/20] chore: log cleanup --- src/config_creature.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config_creature.c b/src/config_creature.c index a138980445..a18f7e2eeb 100644 --- a/src/config_creature.c +++ b/src/config_creature.c @@ -396,7 +396,7 @@ void check_and_auto_fix_stats(void) /* Initialize all creature model stats, called only once when first loading a map. */ void init_creature_model_stats(void) { - WARNLOG("CONFIG_DEBUG: init_creature_model_stats() called"); + struct CreatureModelConfig *crconf; int n; for (int i = 0; i < CREATURE_TYPES_MAX; i++) From 2e64ad5c2183299aba1ccd5207870436373843e7 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Fri, 30 Jan 2026 18:59:09 +0000 Subject: [PATCH 14/20] fix: decoupled lens overlay data from save code --- src/config_lenses.c | 5 -- src/config_lenses.h | 4 -- src/lens_api.c | 143 +++++++++++++++++++++++++++----------------- 3 files changed, 88 insertions(+), 64 deletions(-) diff --git a/src/config_lenses.c b/src/config_lenses.c index ce1b18ffe7..04abc9efe5 100644 --- a/src/config_lenses.c +++ b/src/config_lenses.c @@ -132,11 +132,6 @@ static int64_t value_overlay(const struct NamedField* named_field, const char* v strncpy(lenscfg->overlay_file, value_text, DISKPATH_SIZE - 1); lenscfg->overlay_file[DISKPATH_SIZE - 1] = '\0'; - // Initialize overlay data pointer to NULL - will be looked up during setup - lenscfg->overlay_data = NULL; - lenscfg->overlay_width = 0; - lenscfg->overlay_height = 0; - SYNCDBG(9, "Registered overlay name '%s' for lens %d", value_text, idx); } else diff --git a/src/config_lenses.h b/src/config_lenses.h index c5f2605539..22bb464cae 100644 --- a/src/config_lenses.h +++ b/src/config_lenses.h @@ -49,10 +49,6 @@ struct LensConfig { short displace_magnitude; short displace_period; char overlay_file[DISKPATH_SIZE]; - char overlay_mod_dir[DISKPATH_SIZE]; // Mod directory for overlay, empty for base game - unsigned char *overlay_data; - int overlay_width; - int overlay_height; short overlay_alpha; }; diff --git a/src/lens_api.c b/src/lens_api.c index 905f35c0d1..476d116ae5 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -48,6 +48,16 @@ extern "C" { /******************************************************************************/ #define RAW_OVERLAY_SIZE 256 // RAW overlay files are 256x256 pixels (matching mist texture format) +// Cache structure for lens overlay data (separate from save files) +struct LensOverlayCache { + unsigned char *data; + int width; + int height; +}; + +// Overlay cache for each lens (indexed by lens number) +static struct LensOverlayCache overlay_cache[LENS_ITEMS_MAX]; + uint32_t *eye_lens_memory; TbPixel *eye_lens_spare_screen_memory; /******************************************************************************/ @@ -224,13 +234,20 @@ void init_lens(uint32_t *lens_mem, int width, int height, int pitch, int nlens, } } -static TbBool load_overlay_image(struct LensConfig* lenscfg) +static TbBool load_overlay_image(long lens_idx) { - if (lenscfg->overlay_data != NULL) { + if (lens_idx < 0 || lens_idx >= LENS_ITEMS_MAX) { + ERRORLOG("Invalid lens index %ld", lens_idx); + return false; + } + + struct LensOverlayCache* cache = &overlay_cache[lens_idx]; + if (cache->data != NULL) { // Already loaded return true; } + struct LensConfig* lenscfg = &lenses_conf.lenses[lens_idx]; if (lenscfg->overlay_file[0] == '\0') { WARNLOG("Empty overlay name"); return false; @@ -243,26 +260,26 @@ static TbBool load_overlay_image(struct LensConfig* lenscfg) char* fname = prepare_file_path(FGrp_StdData, lenscfg->overlay_file); // RAW overlay files are 256x256 8-bit indexed (same as mist files) - lenscfg->overlay_width = 256; - lenscfg->overlay_height = 256; - size_t size = lenscfg->overlay_width * lenscfg->overlay_height; // 1 byte per pixel + cache->width = 256; + cache->height = 256; + size_t size = cache->width * cache->height; // 1 byte per pixel - lenscfg->overlay_data = (unsigned char*)malloc(size); - if (lenscfg->overlay_data == NULL) { + cache->data = (unsigned char*)malloc(size); + if (cache->data == NULL) { ERRORLOG("Failed to allocate memory for overlay image"); return false; } - if (LbFileLoadAt(fname, lenscfg->overlay_data) != size) { + if (LbFileLoadAt(fname, cache->data) != size) { WARNLOG("Failed to load overlay file '%s' from /data directory", lenscfg->overlay_file); - free(lenscfg->overlay_data); - lenscfg->overlay_data = NULL; - lenscfg->overlay_width = 0; - lenscfg->overlay_height = 0; + free(cache->data); + cache->data = NULL; + cache->width = 0; + cache->height = 0; return false; } - SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from file", lenscfg->overlay_file, lenscfg->overlay_width, lenscfg->overlay_height); + SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from file", lenscfg->overlay_file, cache->width, cache->height); return true; } @@ -276,33 +293,33 @@ static TbBool load_overlay_image(struct LensConfig* lenscfg) snprintf(fname_raw, sizeof(fname_raw), "%s.raw", lenscfg->overlay_file); // RAW overlay files are 256x256 8-bit indexed (1 byte per pixel, same as mist files) - lenscfg->overlay_width = 256; - lenscfg->overlay_height = 256; - size_t size = lenscfg->overlay_width * lenscfg->overlay_height; // 65536 bytes + cache->width = 256; + cache->height = 256; + size_t size = cache->width * cache->height; // 65536 bytes - lenscfg->overlay_data = (unsigned char*)malloc(size); - if (lenscfg->overlay_data == NULL) { + cache->data = (unsigned char*)malloc(size); + if (cache->data == NULL) { ERRORLOG("Failed to allocate memory for overlay image"); return false; } // Try loading from mods with fallback to base game const char* loaded_from = NULL; - if (try_load_file_from_mods_with_fallback(fname_raw, FGrp_StdData, lenscfg->overlay_data, size, &loaded_from)) { + if (try_load_file_from_mods_with_fallback(fname_raw, FGrp_StdData, cache->data, size, &loaded_from)) { if (loaded_from != NULL) { - SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from mod '%s' data directory", fname_raw, lenscfg->overlay_width, lenscfg->overlay_height, loaded_from); + SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from mod '%s' data directory", fname_raw, cache->width, cache->height, loaded_from); } else { - SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from base game data directory", fname_raw, lenscfg->overlay_width, lenscfg->overlay_height); + SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from base game data directory", fname_raw, cache->width, cache->height); } return true; } // Not found anywhere WARNLOG("Lens overlay '%s' not found. Make sure it's defined in a lenses.json file in a .zip or provide a .raw file in /data", lenscfg->overlay_file); - free(lenscfg->overlay_data); - lenscfg->overlay_data = NULL; - lenscfg->overlay_width = 0; - lenscfg->overlay_height = 0; + free(cache->data); + cache->data = NULL; + cache->width = 0; + cache->height = 0; return false; } @@ -313,27 +330,32 @@ static TbBool load_overlay_image(struct LensConfig* lenscfg) } size_t size = overlay->width * overlay->height; - lenscfg->overlay_data = (unsigned char*)malloc(size); - if (lenscfg->overlay_data == NULL) { + cache->data = (unsigned char*)malloc(size); + if (cache->data == NULL) { ERRORLOG("Failed to allocate memory for overlay image"); return false; } - memcpy(lenscfg->overlay_data, overlay->data, size); - lenscfg->overlay_width = overlay->width; - lenscfg->overlay_height = overlay->height; + memcpy(cache->data, overlay->data, size); + cache->width = overlay->width; + cache->height = overlay->height; - SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from registry", lenscfg->overlay_file, lenscfg->overlay_width, lenscfg->overlay_height); + SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from registry", lenscfg->overlay_file, cache->width, cache->height); return true; } -static void free_overlay_image(struct LensConfig* lenscfg) +static void free_overlay_image(long lens_idx) { - if (lenscfg->overlay_data != NULL) { - free(lenscfg->overlay_data); - lenscfg->overlay_data = NULL; - lenscfg->overlay_width = 0; - lenscfg->overlay_height = 0; + if (lens_idx < 0 || lens_idx >= LENS_ITEMS_MAX) { + return; + } + + struct LensOverlayCache* cache = &overlay_cache[lens_idx]; + if (cache->data != NULL) { + free(cache->data); + cache->data = NULL; + cache->width = 0; + cache->height = 0; } } @@ -369,10 +391,10 @@ void reset_eye_lenses(void) SYNCDBG(7,"Starting"); free_mist(); clear_lens_palette(); - // Free any loaded overlay images - for (size_t i = 0; i < (size_t)lenses_conf.lenses_count; i++) + // Free any loaded overlay images from cache + for (int i = 0; i < LENS_ITEMS_MAX; i++) { - free_overlay_image(&lenses_conf.lenses[i]); + free_overlay_image(i); } if (eye_lens_memory != NULL) { @@ -507,10 +529,10 @@ void setup_eye_lens(long nlens) if ((lenscfg->flags & LCF_HasOverlay) != 0) { SYNCDBG(7, "Overlay config entered for lens %ld, name='%s'", nlens, lenscfg->overlay_file); - if (!load_overlay_image(lenscfg)) { + if (!load_overlay_image(nlens)) { WARNLOG("Failed to load overlay for lens %ld", nlens); } else { - SYNCDBG(7, "Successfully loaded overlay %dx%d", lenscfg->overlay_width, lenscfg->overlay_height); + SYNCDBG(7, "Successfully loaded overlay %dx%d", overlay_cache[nlens].width, overlay_cache[nlens].height); } } game.applied_lens_type = nlens; @@ -557,24 +579,32 @@ void draw_copy(unsigned char *dstbuf, long dstpitch, unsigned char *srcbuf, long } } -static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long height, struct LensConfig* lenscfg) +static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long height, long lens_idx) { - if (lenscfg->overlay_data == NULL) { + if (lens_idx < 0 || lens_idx >= LENS_ITEMS_MAX) { + ERRORLOG("Invalid lens index %ld", lens_idx); + return; + } + + struct LensOverlayCache* cache = &overlay_cache[lens_idx]; + struct LensConfig* lenscfg = &lenses_conf.lenses[lens_idx]; + + if (cache->data == NULL) { WARNLOG("Overlay data is NULL, cannot draw"); return; } // Validate dimensions - if (width <= 0 || height <= 0 || lenscfg->overlay_width <= 0 || lenscfg->overlay_height <= 0) { - WARNLOG("Invalid dimensions for overlay rendering: screen=%ldx%ld, overlay=%dx%d", width, height, lenscfg->overlay_width, lenscfg->overlay_height); + if (width <= 0 || height <= 0 || cache->width <= 0 || cache->height <= 0) { + WARNLOG("Invalid dimensions for overlay rendering: screen=%ldx%ld, overlay=%dx%d", width, height, cache->width, cache->height); return; } - SYNCDBG(8, "Drawing overlay: screen=%ldx%ld, overlay=%dx%d, alpha=%d", width, height, lenscfg->overlay_width, lenscfg->overlay_height, lenscfg->overlay_alpha); + SYNCDBG(8, "Drawing overlay: screen=%ldx%ld, overlay=%dx%d, alpha=%d", width, height, cache->width, cache->height, lenscfg->overlay_alpha); // Calculate scale factors to stretch/fit overlay to fill entire viewport - float scale_x = (float)lenscfg->overlay_width / width; - float scale_y = (float)lenscfg->overlay_height / height; + float scale_x = (float)cache->width / width; + float scale_y = (float)cache->height / height; // Determine dithering threshold based on alpha (0-255) // alpha=255 (opaque): draw all pixels @@ -589,14 +619,14 @@ static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long for (long y = 0; y < height; y++) { int src_y = (int)(y * scale_y); - if (src_y >= lenscfg->overlay_height) src_y = lenscfg->overlay_height - 1; + if (src_y >= cache->height) src_y = cache->height - 1; - unsigned char* src_row = lenscfg->overlay_data + (src_y * lenscfg->overlay_width); + unsigned char* src_row = cache->data + (src_y * cache->width); for (long x = 0; x < width; x++) { int src_x = (int)(x * scale_x); - if (src_x >= lenscfg->overlay_width) src_x = lenscfg->overlay_width - 1; + if (src_x >= cache->width) src_x = cache->width - 1; unsigned char overlay_pixel = src_row[src_x]; @@ -669,9 +699,12 @@ void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbu { draw_copy(dstbuf, dstpitch, srcbuf, srcpitch, width, height); } - // Now draw the overlay on top of the game scene - SYNCDBG(8, "Calling draw_overlay (flags=%d, data=%p)", lenscfg->flags, lenscfg->overlay_data); - draw_overlay(dstbuf, dstpitch, width, height, lenscfg); + // Load overlay if not already loaded + if (load_overlay_image(effect)) { + // Now draw the overlay on top of the game scene + SYNCDBG(8, "Calling draw_overlay (flags=%d, lens_idx=%ld)", lenscfg->flags, effect); + draw_overlay(dstbuf, dstpitch, width, height, effect); + } copied = true; } // If we haven't copied the buffer to screen yet, do so now From bf1af73eed407be6875803bb2987a80abcb848b3 Mon Sep 17 00:00:00 2001 From: Loobinex Date: Fri, 30 Jan 2026 21:05:00 +0100 Subject: [PATCH 15/20] Some documentation. --- config/fxdata/lenses.cfg | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/config/fxdata/lenses.cfg b/config/fxdata/lenses.cfg index 429e9628f6..78e9ae1586 100644 --- a/config/fxdata/lenses.cfg +++ b/config/fxdata/lenses.cfg @@ -2,7 +2,7 @@ ; Lens definitions are now starting. ; Note that lens can't use "Mist" and "Displacement" transformation -; at the same time - don't use both of them in one lens +; at the same time - don't use both of them in one lens. Overlay and Mist may be used together. [lens0] Name = NULL @@ -13,6 +13,9 @@ Name = LENS_WIBBLE ; For displacement kind=1, following parameters are: ; magnitude and period Displacement = 1 9 10 +; Overlay has two parameters: file name of a RAW texture (or name of a custom lens), +; Alpha transparency scaled 0..255, where 255 is fully opaque +; Overlay = frac00.raw 0 [lens2] Name = FISH_EYE @@ -26,7 +29,7 @@ Displacement = 3 1 1 [lens4] Name = MIST_WATER -; Mist effect has three parameters: file name of a RAW texture, +; Mist effect has three parameters: file name of a RAW texture (or name of a custom lens), ; lightness scaled 0..63, and ghost position (irrelevant) Mist = frac00.raw 0 0 From f1dc88b59790af3a7308b1ce67aa61b12e08efb4 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Fri, 30 Jan 2026 23:06:57 +0000 Subject: [PATCH 16/20] fix magenta 255 is now the transparent colour --- src/lens_api.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lens_api.c b/src/lens_api.c index 476d116ae5..f4dfe8a6f8 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -444,7 +444,6 @@ void initialise_eye_lenses(void) void setup_eye_lens(long nlens) { - WARNLOG("CONFIG_DEBUG: setup_eye_lens called with nlens=%ld", nlens); if ((game.mode_flags & MFlg_EyeLensReady) == 0) { WARNLOG("Can't setup lens - not initialized"); @@ -630,8 +629,8 @@ static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long unsigned char overlay_pixel = src_row[src_x]; - // Color 0 is treated as transparent - skip it completely - if (overlay_pixel != 0) + // Color 255 (magenta) is treated as transparent - skip it completely + if (overlay_pixel != 255) { // Apply alpha dithering using checkerboard pattern if (alpha >= 255) From 7ad9f6c8cd540e637e3f3f1b3a48e6cd70df1cf1 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Sat, 31 Jan 2026 01:13:02 +0000 Subject: [PATCH 17/20] fix added pallete conversion when loading the pngs preserve greyscale --- src/config.c | 9 ++- src/config_lenses.c | 2 +- src/config_lenses.h | 2 +- src/custom_sprites.c | 129 ++++++++++++++++++++++++++++++++++++++----- src/lens_api.c | 29 ++++++++-- 5 files changed, 149 insertions(+), 22 deletions(-) diff --git a/src/config.c b/src/config.c index 2208cf017c..9c174aa912 100644 --- a/src/config.c +++ b/src/config.c @@ -1047,10 +1047,15 @@ TbBool parse_named_field_blocks(char *buf, long len, const char *config_textname parse_named_field_block(buf, len, config_textname, flags, blockname_null, named_fields_set->named_fields, named_fields_set, i); } - // Set the terminator at the actual count position (not at max_count-1) + // Set the terminator safely within the allocated range if (named_fields_set->names != NULL && named_fields_set->count_field != NULL) { - named_fields_set->names[*named_fields_set->count_field].name = NULL; + int terminator_index = *named_fields_set->count_field; + if (terminator_index >= named_fields_set->max_count) + { + terminator_index = named_fields_set->max_count - 1; + } + named_fields_set->names[terminator_index].name = NULL; } return true; diff --git a/src/config_lenses.c b/src/config_lenses.c index 04abc9efe5..17357e3ec0 100644 --- a/src/config_lenses.c +++ b/src/config_lenses.c @@ -113,7 +113,7 @@ static int64_t value_pallete(const struct NamedField* named_field, const char* v static int64_t value_overlay(const struct NamedField* named_field, const char* value_text, const struct NamedFieldSet* named_fields_set, int idx, const char* src_str, unsigned char flags) { - SYNCLOG("value_overlay called: argnum=%d, value='%s', lens=%d", named_field->argnum, value_text, idx); + SYNCDBG (9, "value_overlay called: argnum=%d, value='%s', lens=%d", named_field->argnum, value_text, idx); if (value_text == NULL || value_text[0] == '\0') { CONFWRNLOG("Empty overlay name for \"%s\" parameter in [%s%d] block of lens.cfg file.", diff --git a/src/config_lenses.h b/src/config_lenses.h index 22bb464cae..299ae8fae5 100644 --- a/src/config_lenses.h +++ b/src/config_lenses.h @@ -29,7 +29,7 @@ extern "C" { #endif /******************************************************************************/ -#define LENS_ITEMS_MAX 255 +#define LENS_ITEMS_MAX 256 enum LensConfigFlags { LCF_HasMist = 0x01, diff --git a/src/custom_sprites.c b/src/custom_sprites.c index e67466eb63..bc89eded2a 100644 --- a/src/custom_sprites.c +++ b/src/custom_sprites.c @@ -1382,6 +1382,11 @@ add_custom_json(const char *path, const char *name, TbBool (*process)(const char return 0; } +// Forward declaration for internal PNG decoder +static unsigned char* decode_png_to_indexed_internal(unzFile zip, const char *file, const char *path, + int *out_width, int *out_height, + unz_file_info64 *zip_info, TbBool use_palette_conversion); + // Helper function to decode PNG from ZIP file to indexed palette format // Returns indexed data on success, NULL on failure // Caller must free the returned data @@ -1389,6 +1394,27 @@ static unsigned char* decode_png_to_indexed(unzFile zip, const char *file, const int *out_width, int *out_height, unz_file_info64 *zip_info) { + return decode_png_to_indexed_internal(zip, file, path, out_width, out_height, zip_info, true); +} + +static unsigned char* decode_png_to_indexed_no_palette(unzFile zip, const char *file, const char *path, + int *out_width, int *out_height, + unz_file_info64 *zip_info) +{ + return decode_png_to_indexed_internal(zip, file, path, out_width, out_height, zip_info, false); +} + +static unsigned char* decode_png_to_indexed_internal(unzFile zip, const char *file, const char *path, + int *out_width, int *out_height, + unz_file_info64 *zip_info, TbBool use_palette_conversion) +{ + JUSTLOG("decode_png_to_indexed_internal: file='%s', size=%d, use_palette=%d", file, (int)zip_info->uncompressed_size, use_palette_conversion); + // Only load RGB to palette conversion table if needed for color images + if (use_palette_conversion) { + load_rgb_to_pal_table(); + JUSTLOG("Loaded RGB to palette table"); + } + if (zip_info->uncompressed_size > 1024 * 1024 * 4) { WARNLOG("PNG file too large: '%s' in '%s'", file, path); @@ -1408,6 +1434,7 @@ static unsigned char* decode_png_to_indexed(unzFile zip, const char *file, const free(png_buffer); return NULL; } + JUSTLOG("Read PNG data from ZIP (%d bytes)", (int)zip_info->uncompressed_size); // Decode PNG using spng spng_ctx *ctx = spng_ctx_new(0); @@ -1439,6 +1466,7 @@ static unsigned char* decode_png_to_indexed(unzFile zip, const char *file, const free(png_buffer); return NULL; } + JUSTLOG("Got PNG IHDR: %dx%d, bit_depth=%d, color_type=%d", (int)ihdr.width, (int)ihdr.height, ihdr.bit_depth, ihdr.color_type); if (ihdr.width <= 0 || ihdr.height <= 0 || ihdr.width > 4096 || ihdr.height > 4096) { @@ -1476,6 +1504,7 @@ static unsigned char* decode_png_to_indexed(unzFile zip, const char *file, const free(png_buffer); return NULL; } + JUSTLOG("Decoded PNG to RGBA8 format (%d bytes)", (int)out_size); spng_ctx_free(ctx); free(png_buffer); @@ -1490,7 +1519,19 @@ static unsigned char* decode_png_to_indexed(unzFile zip, const char *file, const return NULL; } + // Complete RGBA data dump - filtered for Knight overlays only + TbBool is_knight = (strstr(file, "Knight") != NULL || strstr(file, "knight") != NULL); + if (use_palette_conversion && is_knight) { + JUSTLOG("=== COMPLETE RGBA DATA DUMP FOR '%s' (ALL %d PIXELS) ===", file, (int)indexed_size); + for (size_t i = 0; i < indexed_size; i++) { + JUSTLOG("Pixel[%d]: R=%3d G=%3d B=%3d A=%3d", (int)i, + rgba_buffer[i * 4 + 0], rgba_buffer[i * 4 + 1], + rgba_buffer[i * 4 + 2], rgba_buffer[i * 4 + 3]); + } + } + // Convert RGBA to palette indices using rgb_to_pal_table + int transparent_count = 0, opaque_count = 0; for (size_t i = 0; i < indexed_size; i++) { unsigned char red = rgba_buffer[i * 4 + 0]; @@ -1498,41 +1539,91 @@ static unsigned char* decode_png_to_indexed(unzFile zip, const char *file, const unsigned char blue = rgba_buffer[i * 4 + 2]; unsigned char alpha = rgba_buffer[i * 4 + 3]; - if (alpha < 128) { - // Transparent pixel - use color 0 (typically transparent/black) - indexed_data[i] = 0; - } else if (rgb_to_pal_table != NULL) { - // Use lookup table for color conversion - indexed_data[i] = rgb_to_pal_table[ - ((red >> 2) << 12) | ((green >> 2) << 6) | (blue >> 2) - ]; - } else { - // Fallback: simple grayscale conversion - indexed_data[i] = (red + green + blue) / 3; + if (use_palette_conversion) + { + // Color image - convert RGB to palette index + if (alpha < 128) { + // Transparent pixel - use palette index 255 as transparency marker + indexed_data[i] = 255; + transparent_count++; + } else if (rgb_to_pal_table != NULL) { + // Use lookup table for color conversion + indexed_data[i] = rgb_to_pal_table[ + ((red >> 2) << 12) | ((green >> 2) << 6) | (blue >> 2) + ]; + opaque_count++; + } else { + // Fallback: simple grayscale conversion + indexed_data[i] = (red + green + blue) / 3; + opaque_count++; + } + } + else + { + // Data image (mist/displacement) - preserve grayscale values as-is + // Use the red channel as the index value (assuming grayscale PNG) + indexed_data[i] = red; } } free(rgba_buffer); + if (use_palette_conversion) { + JUSTLOG("Conversion complete: %d transparent pixels, %d opaque pixels (total %d)", + transparent_count, opaque_count, (int)indexed_size); + + // Dump complete indexed buffer - filtered for Knight overlays only + if (is_knight) { + JUSTLOG("=== COMPLETE INDEXED BUFFER DUMP FOR '%s' (ALL %d PIXELS) ===", file, (int)indexed_size); + for (size_t i = 0; i < indexed_size; i++) { + JUSTLOG("IndexedPixel[%d]: %3d", (int)i, indexed_data[i]); + } + } + } + *out_width = ihdr.width; *out_height = ihdr.height; + JUSTLOG("Successfully converted PNG to indexed format: %dx%d", *out_width, *out_height); return indexed_data; } static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx, VALUE *root) { VALUE *val; + JUSTLOG("process_lens_overlay_from_list called for %s item %d, root type: %d", path, idx, value_type(root)); + + // If it's a dict, log its keys + if (value_type(root) == VALUE_DICT) + { + size_t dict_size = value_dict_size(root); + JUSTLOG("Dict has %d keys", (int)dict_size); + + // Get all keys + const VALUE *keys[20]; // Should be enough for lens overlay dict + size_t num_keys = value_dict_keys_ordered(root, keys, 20); + JUSTLOG("Retrieved %d keys:", (int)num_keys); + for (size_t i = 0; i < num_keys; i++) + { + if (value_type(keys[i]) == VALUE_STRING) + { + JUSTLOG(" Key %d: '%s'", (int)i, value_string(keys[i])); + } + } + } val = value_dict_get(root, "name"); + JUSTLOG("value_dict_get for 'name' returned: %p", val); if (val == NULL) { WARNLOG("Invalid lens overlay %s/lenses.json[%d]: no \"name\" key", path, idx); return 0; } const char *name = value_string(val); + JUSTLOG("found lens overlay: '%s/%s' (name=%s)", path, name, name); SYNCDBG(2, "found lens overlay: '%s/%s'", path, name); VALUE *file_value = value_dict_get(root, "file"); + JUSTLOG("value_dict_get for 'file' returned: %p", file_value); if (file_value == NULL) { WARNLOG("Invalid lens overlay %s/lenses.json[%d]: no \"file\" key", path, idx); @@ -1543,10 +1634,12 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx if (value_type(file_value) == VALUE_STRING) { file = value_string(file_value); + JUSTLOG("File is STRING type: '%s'", file); } else if (value_type(file_value) == VALUE_ARRAY && value_array_size(file_value) > 0) { file = value_string(value_array_get(file_value, 0)); + JUSTLOG("File is ARRAY type, first element: '%s'", file); } else { @@ -1554,11 +1647,13 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx return 0; } + JUSTLOG("Attempting to locate file '%s' in ZIP", file); if (fastUnzLocateFile(zip, file, 0)) { WARNLOG("File '%s' not found in '%s'", file, path); return 0; } + JUSTLOG("File located successfully, getting file info"); unz_file_info64 zip_info = {0}; if (UNZ_OK != unzGetCurrentFileInfo64(zip, &zip_info, NULL, 0, NULL, 0, NULL, 0)) @@ -1640,10 +1735,13 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx } // PNG format handling - use shared helper + JUSTLOG("Calling decode_png_to_indexed for '%s'", file); int width, height; unsigned char *indexed_data = decode_png_to_indexed(zip, file, path, &width, &height, &zip_info); + JUSTLOG("decode_png_to_indexed returned: %p (width=%d, height=%d)", indexed_data, width, height); if (indexed_data == NULL) { + WARNLOG("Failed to decode PNG '%s' from '%s'", file, path); return 0; } @@ -1689,6 +1787,7 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx added_lens_overlays[num_added_lens_overlays].width = width; added_lens_overlays[num_added_lens_overlays].height = height; num_added_lens_overlays++; + JUSTLOG("Added PNG lens overlay '%s' (%dx%d), total count now: %d", name, width, height, num_added_lens_overlays); SYNCDBG(8, "Added PNG lens overlay '%s' (%dx%d)", name, width, height); } @@ -1790,10 +1889,14 @@ static int process_icon_from_list(const char *path, unzFile zip, int idx, VALUE static TbBool process_lens_overlay(const char *path, unzFile zip, VALUE *root) { + int array_size = value_array_size(root); + JUSTLOG("Processing lens overlays from %s (array size: %d)", path, array_size); TbBool ret_ok = true; - for (int i = 0; i < value_array_size(root); i++) + for (int i = 0; i < array_size; i++) { + JUSTLOG("Processing lens overlay item %d from %s", i, path); VALUE *val = value_array_get(root, i); + JUSTLOG("Got value for item %d, calling process_lens_overlay_from_list", i); if (!process_lens_overlay_from_list(path, zip, i, val)) { ret_ok = false; @@ -1864,7 +1967,7 @@ static int process_lens_mist_from_list(const char *path, unzFile zip, int idx, V { // Not RAW format, try PNG int width, height; - mist_data = decode_png_to_indexed(zip, file, path, &width, &height, &zip_info); + mist_data = decode_png_to_indexed_no_palette(zip, file, path, &width, &height, &zip_info); if (mist_data == NULL) { // Already closed by decode function on failure diff --git a/src/lens_api.c b/src/lens_api.c index f4dfe8a6f8..64368aa30a 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -26,9 +26,6 @@ #include "bflib_fileio.h" #include "bflib_dernc.h" -#include -#include - #include "config_lenses.h" #include "lens_mist.h" #include "lens_flyeye.h" @@ -248,6 +245,7 @@ static TbBool load_overlay_image(long lens_idx) } struct LensConfig* lenscfg = &lenses_conf.lenses[lens_idx]; + JUSTLOG("load_overlay_image: lens_idx=%ld, overlay_file='%s'", lens_idx, lenscfg->overlay_file); if (lenscfg->overlay_file[0] == '\0') { WARNLOG("Empty overlay name"); return false; @@ -284,7 +282,9 @@ static TbBool load_overlay_image(long lens_idx) } // Look up overlay by name in the custom sprites registry + JUSTLOG("Looking up overlay '%s' in registry", lenscfg->overlay_file); const struct LensOverlayData* overlay = get_lens_overlay_data(lenscfg->overlay_file); + JUSTLOG("Registry lookup result: %p", overlay); // If not found in registry, try loading from /data directory as RAW file fallback if (overlay == NULL) { @@ -340,6 +340,7 @@ static TbBool load_overlay_image(long lens_idx) cache->width = overlay->width; cache->height = overlay->height; + JUSTLOG("Successfully loaded overlay '%s' (%dx%d) from registry", lenscfg->overlay_file, cache->width, cache->height); SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from registry", lenscfg->overlay_file, cache->width, cache->height); return true; } @@ -380,7 +381,7 @@ TbBool clear_lens_palette(void) static void set_lens_palette(unsigned char *palette) { struct PlayerInfo* player = get_my_player(); - WARNLOG("CONFIG_DEBUG: set_lens_palette called, palette[0-4]: %02x %02x %02x %02x %02x", + SYNCDBG(9,"CONFIG_DEBUG: set_lens_palette called, palette[0-4]: %02x %02x %02x %02x %02x", palette[0], palette[1], palette[2], palette[3], palette[4]); player->main_palette = palette; player->lens_palette = palette; @@ -588,6 +589,9 @@ static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long struct LensOverlayCache* cache = &overlay_cache[lens_idx]; struct LensConfig* lenscfg = &lenses_conf.lenses[lens_idx]; + JUSTLOG("draw_overlay called: lens_idx=%ld, screen=%ldx%ld, cache->data=%p, cache->width=%d, cache->height=%d, alpha=%d", + lens_idx, width, height, cache->data, cache->width, cache->height, lenscfg->overlay_alpha); + if (cache->data == NULL) { WARNLOG("Overlay data is NULL, cannot draw"); return; @@ -601,6 +605,21 @@ static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long SYNCDBG(8, "Drawing overlay: screen=%ldx%ld, overlay=%dx%d, alpha=%d", width, height, cache->width, cache->height, lenscfg->overlay_alpha); + // Log sample of overlay data to verify conversion + JUSTLOG("Overlay buffer sample (first 20 pixels): [%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d]", + cache->data[0], cache->data[1], cache->data[2], cache->data[3], cache->data[4], + cache->data[5], cache->data[6], cache->data[7], cache->data[8], cache->data[9], + cache->data[10], cache->data[11], cache->data[12], cache->data[13], cache->data[14], + cache->data[15], cache->data[16], cache->data[17], cache->data[18], cache->data[19]); + + // Count non-zero pixels in first row to verify we have actual data + int non_zero_count = 0; + int sample_size = (cache->width < 100) ? cache->width : 100; + for (int i = 0; i < sample_size; i++) { + if (cache->data[i] != 0) non_zero_count++; + } + JUSTLOG("First %d pixels: %d are non-zero (%d%% opaque)", sample_size, non_zero_count, (non_zero_count * 100) / sample_size); + // Calculate scale factors to stretch/fit overlay to fill entire viewport float scale_x = (float)cache->width / width; float scale_y = (float)cache->height / height; @@ -629,7 +648,7 @@ static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long unsigned char overlay_pixel = src_row[src_x]; - // Color 255 (magenta) is treated as transparent - skip it completely + // Palette index 255 is used as transparency marker - skip it completely if (overlay_pixel != 255) { // Apply alpha dithering using checkerboard pattern From b4966e34bbec6504b03def3e7f8360ca6732b82a Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Sat, 31 Jan 2026 09:41:59 +0000 Subject: [PATCH 18/20] fix clear mist flag and biffer if lens has no overlay --- src/config_lenses.c | 5 +++++ src/custom_sprites.c | 24 +----------------------- src/lens_api.c | 26 +------------------------- 3 files changed, 7 insertions(+), 48 deletions(-) diff --git a/src/config_lenses.c b/src/config_lenses.c index 17357e3ec0..771b90391d 100644 --- a/src/config_lenses.c +++ b/src/config_lenses.c @@ -124,6 +124,11 @@ static int64_t value_overlay(const struct NamedField* named_field, const char* v lenses_conf.lenses[idx].flags |= LCF_HasOverlay; struct LensConfig* lenscfg = &lenses_conf.lenses[idx]; + // Clear mist flag and mist_file if this lens only has an overlay + // This prevents garbage mist data from previous lens configurations + lenscfg->flags &= ~LCF_HasMist; + lenscfg->mist_file[0] = '\0'; + // Only store the overlay name when processing position 0 (the name field) // Position 1 is the alpha value, handled by value_default if (named_field->argnum == 0) diff --git a/src/custom_sprites.c b/src/custom_sprites.c index bc89eded2a..9cda70d903 100644 --- a/src/custom_sprites.c +++ b/src/custom_sprites.c @@ -1519,16 +1519,7 @@ static unsigned char* decode_png_to_indexed_internal(unzFile zip, const char *fi return NULL; } - // Complete RGBA data dump - filtered for Knight overlays only - TbBool is_knight = (strstr(file, "Knight") != NULL || strstr(file, "knight") != NULL); - if (use_palette_conversion && is_knight) { - JUSTLOG("=== COMPLETE RGBA DATA DUMP FOR '%s' (ALL %d PIXELS) ===", file, (int)indexed_size); - for (size_t i = 0; i < indexed_size; i++) { - JUSTLOG("Pixel[%d]: R=%3d G=%3d B=%3d A=%3d", (int)i, - rgba_buffer[i * 4 + 0], rgba_buffer[i * 4 + 1], - rgba_buffer[i * 4 + 2], rgba_buffer[i * 4 + 3]); - } - } + // Convert RGBA to palette indices using rgb_to_pal_table int transparent_count = 0, opaque_count = 0; @@ -1568,19 +1559,6 @@ static unsigned char* decode_png_to_indexed_internal(unzFile zip, const char *fi free(rgba_buffer); - if (use_palette_conversion) { - JUSTLOG("Conversion complete: %d transparent pixels, %d opaque pixels (total %d)", - transparent_count, opaque_count, (int)indexed_size); - - // Dump complete indexed buffer - filtered for Knight overlays only - if (is_knight) { - JUSTLOG("=== COMPLETE INDEXED BUFFER DUMP FOR '%s' (ALL %d PIXELS) ===", file, (int)indexed_size); - for (size_t i = 0; i < indexed_size; i++) { - JUSTLOG("IndexedPixel[%d]: %3d", (int)i, indexed_data[i]); - } - } - } - *out_width = ihdr.width; *out_height = ihdr.height; JUSTLOG("Successfully converted PNG to indexed format: %dx%d", *out_width, *out_height); diff --git a/src/lens_api.c b/src/lens_api.c index 64368aa30a..3348508531 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -245,7 +245,6 @@ static TbBool load_overlay_image(long lens_idx) } struct LensConfig* lenscfg = &lenses_conf.lenses[lens_idx]; - JUSTLOG("load_overlay_image: lens_idx=%ld, overlay_file='%s'", lens_idx, lenscfg->overlay_file); if (lenscfg->overlay_file[0] == '\0') { WARNLOG("Empty overlay name"); return false; @@ -282,9 +281,7 @@ static TbBool load_overlay_image(long lens_idx) } // Look up overlay by name in the custom sprites registry - JUSTLOG("Looking up overlay '%s' in registry", lenscfg->overlay_file); const struct LensOverlayData* overlay = get_lens_overlay_data(lenscfg->overlay_file); - JUSTLOG("Registry lookup result: %p", overlay); // If not found in registry, try loading from /data directory as RAW file fallback if (overlay == NULL) { @@ -340,7 +337,6 @@ static TbBool load_overlay_image(long lens_idx) cache->width = overlay->width; cache->height = overlay->height; - JUSTLOG("Successfully loaded overlay '%s' (%dx%d) from registry", lenscfg->overlay_file, cache->width, cache->height); SYNCDBG(7, "Loaded overlay '%s' (%dx%d) from registry", lenscfg->overlay_file, cache->width, cache->height); return true; } @@ -467,9 +463,7 @@ void setup_eye_lens(long nlens) } struct LensConfig* lenscfg = get_lens_config(nlens); if ((lenscfg->flags & LCF_HasMist) != 0) - { - SYNCDBG(9,"Mist config entered"); - + { // Try to load from registry first (ZIP files with mists.json) const struct LensMistData* mist = get_lens_mist_data(lenscfg->mist_file); @@ -589,9 +583,6 @@ static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long struct LensOverlayCache* cache = &overlay_cache[lens_idx]; struct LensConfig* lenscfg = &lenses_conf.lenses[lens_idx]; - JUSTLOG("draw_overlay called: lens_idx=%ld, screen=%ldx%ld, cache->data=%p, cache->width=%d, cache->height=%d, alpha=%d", - lens_idx, width, height, cache->data, cache->width, cache->height, lenscfg->overlay_alpha); - if (cache->data == NULL) { WARNLOG("Overlay data is NULL, cannot draw"); return; @@ -605,21 +596,6 @@ static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long SYNCDBG(8, "Drawing overlay: screen=%ldx%ld, overlay=%dx%d, alpha=%d", width, height, cache->width, cache->height, lenscfg->overlay_alpha); - // Log sample of overlay data to verify conversion - JUSTLOG("Overlay buffer sample (first 20 pixels): [%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d]", - cache->data[0], cache->data[1], cache->data[2], cache->data[3], cache->data[4], - cache->data[5], cache->data[6], cache->data[7], cache->data[8], cache->data[9], - cache->data[10], cache->data[11], cache->data[12], cache->data[13], cache->data[14], - cache->data[15], cache->data[16], cache->data[17], cache->data[18], cache->data[19]); - - // Count non-zero pixels in first row to verify we have actual data - int non_zero_count = 0; - int sample_size = (cache->width < 100) ? cache->width : 100; - for (int i = 0; i < sample_size; i++) { - if (cache->data[i] != 0) non_zero_count++; - } - JUSTLOG("First %d pixels: %d are non-zero (%d%% opaque)", sample_size, non_zero_count, (non_zero_count * 100) / sample_size); - // Calculate scale factors to stretch/fit overlay to fill entire viewport float scale_x = (float)cache->width / width; float scale_y = (float)cache->height / height; From 755f43f75378bd3752c8379e7e268ab4710daa63 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Sat, 31 Jan 2026 13:01:31 +0000 Subject: [PATCH 19/20] remove justlog --- src/custom_sprites.c | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/custom_sprites.c b/src/custom_sprites.c index 9cda70d903..32e4fcca8b 100644 --- a/src/custom_sprites.c +++ b/src/custom_sprites.c @@ -1408,11 +1408,9 @@ static unsigned char* decode_png_to_indexed_internal(unzFile zip, const char *fi int *out_width, int *out_height, unz_file_info64 *zip_info, TbBool use_palette_conversion) { - JUSTLOG("decode_png_to_indexed_internal: file='%s', size=%d, use_palette=%d", file, (int)zip_info->uncompressed_size, use_palette_conversion); // Only load RGB to palette conversion table if needed for color images if (use_palette_conversion) { load_rgb_to_pal_table(); - JUSTLOG("Loaded RGB to palette table"); } if (zip_info->uncompressed_size > 1024 * 1024 * 4) @@ -1434,7 +1432,6 @@ static unsigned char* decode_png_to_indexed_internal(unzFile zip, const char *fi free(png_buffer); return NULL; } - JUSTLOG("Read PNG data from ZIP (%d bytes)", (int)zip_info->uncompressed_size); // Decode PNG using spng spng_ctx *ctx = spng_ctx_new(0); @@ -1466,7 +1463,6 @@ static unsigned char* decode_png_to_indexed_internal(unzFile zip, const char *fi free(png_buffer); return NULL; } - JUSTLOG("Got PNG IHDR: %dx%d, bit_depth=%d, color_type=%d", (int)ihdr.width, (int)ihdr.height, ihdr.bit_depth, ihdr.color_type); if (ihdr.width <= 0 || ihdr.height <= 0 || ihdr.width > 4096 || ihdr.height > 4096) { @@ -1504,7 +1500,6 @@ static unsigned char* decode_png_to_indexed_internal(unzFile zip, const char *fi free(png_buffer); return NULL; } - JUSTLOG("Decoded PNG to RGBA8 format (%d bytes)", (int)out_size); spng_ctx_free(ctx); free(png_buffer); @@ -1561,47 +1556,23 @@ static unsigned char* decode_png_to_indexed_internal(unzFile zip, const char *fi *out_width = ihdr.width; *out_height = ihdr.height; - JUSTLOG("Successfully converted PNG to indexed format: %dx%d", *out_width, *out_height); return indexed_data; } static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx, VALUE *root) { VALUE *val; - JUSTLOG("process_lens_overlay_from_list called for %s item %d, root type: %d", path, idx, value_type(root)); - - // If it's a dict, log its keys - if (value_type(root) == VALUE_DICT) - { - size_t dict_size = value_dict_size(root); - JUSTLOG("Dict has %d keys", (int)dict_size); - - // Get all keys - const VALUE *keys[20]; // Should be enough for lens overlay dict - size_t num_keys = value_dict_keys_ordered(root, keys, 20); - JUSTLOG("Retrieved %d keys:", (int)num_keys); - for (size_t i = 0; i < num_keys; i++) - { - if (value_type(keys[i]) == VALUE_STRING) - { - JUSTLOG(" Key %d: '%s'", (int)i, value_string(keys[i])); - } - } - } val = value_dict_get(root, "name"); - JUSTLOG("value_dict_get for 'name' returned: %p", val); if (val == NULL) { WARNLOG("Invalid lens overlay %s/lenses.json[%d]: no \"name\" key", path, idx); return 0; } const char *name = value_string(val); - JUSTLOG("found lens overlay: '%s/%s' (name=%s)", path, name, name); SYNCDBG(2, "found lens overlay: '%s/%s'", path, name); VALUE *file_value = value_dict_get(root, "file"); - JUSTLOG("value_dict_get for 'file' returned: %p", file_value); if (file_value == NULL) { WARNLOG("Invalid lens overlay %s/lenses.json[%d]: no \"file\" key", path, idx); @@ -1612,12 +1583,10 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx if (value_type(file_value) == VALUE_STRING) { file = value_string(file_value); - JUSTLOG("File is STRING type: '%s'", file); } else if (value_type(file_value) == VALUE_ARRAY && value_array_size(file_value) > 0) { file = value_string(value_array_get(file_value, 0)); - JUSTLOG("File is ARRAY type, first element: '%s'", file); } else { @@ -1625,13 +1594,11 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx return 0; } - JUSTLOG("Attempting to locate file '%s' in ZIP", file); if (fastUnzLocateFile(zip, file, 0)) { WARNLOG("File '%s' not found in '%s'", file, path); return 0; } - JUSTLOG("File located successfully, getting file info"); unz_file_info64 zip_info = {0}; if (UNZ_OK != unzGetCurrentFileInfo64(zip, &zip_info, NULL, 0, NULL, 0, NULL, 0)) @@ -1689,7 +1656,6 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx existing->data = indexed_data; existing->width = 256; existing->height = 256; - JUSTLOG("Overriding lens overlay '%s/%s'", path, name); } else { @@ -1713,10 +1679,8 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx } // PNG format handling - use shared helper - JUSTLOG("Calling decode_png_to_indexed for '%s'", file); int width, height; unsigned char *indexed_data = decode_png_to_indexed(zip, file, path, &width, &height, &zip_info); - JUSTLOG("decode_png_to_indexed returned: %p (width=%d, height=%d)", indexed_data, width, height); if (indexed_data == NULL) { WARNLOG("Failed to decode PNG '%s' from '%s'", file, path); @@ -1748,7 +1712,6 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx existing->data = indexed_data; existing->width = width; existing->height = height; - JUSTLOG("Overriding lens overlay '%s/%s'", path, name); } else { @@ -1868,13 +1831,10 @@ static int process_icon_from_list(const char *path, unzFile zip, int idx, VALUE static TbBool process_lens_overlay(const char *path, unzFile zip, VALUE *root) { int array_size = value_array_size(root); - JUSTLOG("Processing lens overlays from %s (array size: %d)", path, array_size); TbBool ret_ok = true; for (int i = 0; i < array_size; i++) { - JUSTLOG("Processing lens overlay item %d from %s", i, path); VALUE *val = value_array_get(root, i); - JUSTLOG("Got value for item %d, calling process_lens_overlay_from_list", i); if (!process_lens_overlay_from_list(path, zip, i, val)) { ret_ok = false; From c8c9859ad54bee017b66864808cf9e0fe53675c6 Mon Sep 17 00:00:00 2001 From: Loobinex Date: Sat, 31 Jan 2026 14:31:54 +0100 Subject: [PATCH 20/20] Removed one more JUSTLOG --- src/config_creature.c | 1 - src/custom_sprites.c | 1 - 2 files changed, 2 deletions(-) diff --git a/src/config_creature.c b/src/config_creature.c index a18f7e2eeb..b0e41e3c6e 100644 --- a/src/config_creature.c +++ b/src/config_creature.c @@ -396,7 +396,6 @@ void check_and_auto_fix_stats(void) /* Initialize all creature model stats, called only once when first loading a map. */ void init_creature_model_stats(void) { - struct CreatureModelConfig *crconf; int n; for (int i = 0; i < CREATURE_TYPES_MAX; i++) diff --git a/src/custom_sprites.c b/src/custom_sprites.c index 32e4fcca8b..613df6268a 100644 --- a/src/custom_sprites.c +++ b/src/custom_sprites.c @@ -1728,7 +1728,6 @@ static int process_lens_overlay_from_list(const char *path, unzFile zip, int idx added_lens_overlays[num_added_lens_overlays].width = width; added_lens_overlays[num_added_lens_overlays].height = height; num_added_lens_overlays++; - JUSTLOG("Added PNG lens overlay '%s' (%dx%d), total count now: %d", name, width, height, num_added_lens_overlays); SYNCDBG(8, "Added PNG lens overlay '%s' (%dx%d)", name, width, height); }