diff options
-rw-r--r-- | conf/battle/client.conf | 8 | ||||
-rw-r--r-- | src/map/battle.c | 1 | ||||
-rw-r--r-- | src/map/battle.h | 1 | ||||
-rw-r--r-- | src/map/clif.c | 122 |
4 files changed, 122 insertions, 10 deletions
diff --git a/conf/battle/client.conf b/conf/battle/client.conf index eae8aad3b..069dea1a5 100644 --- a/conf/battle/client.conf +++ b/conf/battle/client.conf @@ -118,3 +118,11 @@ client_sort_storage: no // because the client sees multiple commands in succession as spam. // Default: 0 (means disabled) client_accept_chatdori: 0 + +// Limits use of blank (transparent) pixels in guild emblems to a set +// percentage of the total. +// Official servers do not enforce this technically to date, but some disallow +// use of blank emblems in their rules. (Note 2) +// A value of 100 (allowing 100% blank pixels) disables this check. +// NOTE: Enabling this option slightly degrades performance. +client_emblem_max_blank_percent: 100 diff --git a/src/map/battle.c b/src/map/battle.c index e1dffb796..b44bc353e 100644 --- a/src/map/battle.c +++ b/src/map/battle.c @@ -6442,6 +6442,7 @@ static const struct _battle_data { { "cashshop_show_points", &battle_config.cashshop_show_points, 0, 0, 1, }, { "mail_show_status", &battle_config.mail_show_status, 0, 0, 2, }, { "client_limit_unit_lv", &battle_config.client_limit_unit_lv, 0, 0, BL_ALL, }, + { "client_emblem_max_blank_percent", &battle_config.client_emblem_max_blank_percent, 100, 0, 100, }, // BattleGround Settings { "bg_update_interval", &battle_config.bg_update_interval, 1000, 100, INT_MAX, }, { "bg_flee_penalty", &battle_config.bg_flee_penalty, 20, 0, INT_MAX, }, diff --git a/src/map/battle.h b/src/map/battle.h index 0f3a22c4b..fd6699f4d 100644 --- a/src/map/battle.h +++ b/src/map/battle.h @@ -426,6 +426,7 @@ struct Battle_Config { int cashshop_show_points; int mail_show_status; int client_limit_unit_lv; + int client_emblem_max_blank_percent; int hom_max_level; int hom_S_max_level; diff --git a/src/map/clif.c b/src/map/clif.c index 6740c7a74..c509da7dd 100644 --- a/src/map/clif.c +++ b/src/map/clif.c @@ -12967,20 +12967,122 @@ void clif_parse_GuildRequestEmblem(int fd,struct map_session_data *sd) clif->guild_emblem(sd,g); } - /// Validates data of a guild emblem (compressed bitmap) -bool clif_validate_emblem(const uint8* emblem, unsigned long emblem_len) { - bool success; - uint8 buf[1800]; // no well-formed emblem bitmap is larger than 1782 (24 bit) / 1654 (8 bit) bytes +bool clif_validate_emblem(const uint8 *emblem, unsigned long emblem_len) { + enum e_bitmapconst { + RGBTRIPLE_SIZE = 3, // sizeof(RGBTRIPLE) + RGBQUAD_SIZE = 4, // sizeof(RGBQUAD) + BITMAPFILEHEADER_SIZE = 14, // sizeof(BITMAPFILEHEADER) + BITMAPINFOHEADER_SIZE = 40, // sizeof(BITMAPINFOHEADER) + BITMAP_WIDTH = 24, + BITMAP_HEIGHT = 24, + }; +#pragma pack(push, 1) + struct s_bitmaptripple { + //uint8 b; + //uint8 g; + //uint8 r; + unsigned int rgb:24; + } __attribute__((packed)); +#pragma pack(pop) + uint8 buf[1800]; // no well-formed emblem bitmap is larger than 1782 (24 bit) / 1654 (8 bit) bytes unsigned long buf_len = sizeof(buf); + int header = 0, bitmap = 0, offbits = 0, palettesize = 0, i = 0; + + if( decode_zip(buf, &buf_len, emblem, emblem_len) != 0 || buf_len < BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE + || RBUFW(buf,0) != 0x4d42 // BITMAPFILEHEADER.bfType (signature) + || RBUFL(buf,2) != buf_len // BITMAPFILEHEADER.bfSize (file size) + || RBUFL(buf,14) != BITMAPINFOHEADER_SIZE // BITMAPINFOHEADER.biSize (other headers are not supported) + || RBUFL(buf,18) != BITMAP_WIDTH // BITMAPINFOHEADER.biWidth + || RBUFL(buf,22) != BITMAP_HEIGHT // BITMAPINFOHEADER.biHeight (top-down bitmaps (-24) are not supported) + || RBUFL(buf,30) != 0 // BITMAPINFOHEADER.biCompression == BI_RGB (compression not supported) + ) { + // Invalid data + return false; + } - success = ( decode_zip(buf, &buf_len, emblem, emblem_len) == 0 && buf_len >= 18 ) // sizeof(BITMAPFILEHEADER) + sizeof(biSize) of the following info header struct - && RBUFW(buf,0) == 0x4d42 // BITMAPFILEHEADER.bfType (signature) - && RBUFL(buf,2) == buf_len // BITMAPFILEHEADER.bfSize (file size) - && RBUFL(buf,10) < buf_len // BITMAPFILEHEADER.bfOffBits (offset to bitmap bits) - ; + offbits = RBUFL(buf,10); // BITMAPFILEHEADER.bfOffBits (offset to bitmap bits) + + switch( RBUFW(buf,28) ) { // BITMAPINFOHEADER.biBitCount + case 8: + palettesize = RBUFL(buf,46); // BITMAPINFOHEADER.biClrUsed (number of colors in the palette) + if( palettesize == 0 ) + palettesize = 256; // Defaults to 2^n if set to zero + else if( palettesize > 256 ) + return false; + header = BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE + RGBQUAD_SIZE * palettesize; // headers + palette + bitmap = BITMAP_WIDTH * BITMAP_HEIGHT; + break; + case 24: + header = BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE; + bitmap = BITMAP_WIDTH * BITMAP_HEIGHT * RGBTRIPLE_SIZE; + break; + default: + return false; + } + + // NOTE: This check gives a little freedom for bitmap-producing implementations, + // that align the start of bitmap data, which is harmless but unnecessary. + // If you want it paranoidly strict, change the first condition from < to !=. + // This also allows files with trailing garbage at the end of the file. + // If you want to avoid that, change the last condition to !=. + if( offbits < header || buf_len <= bitmap || offbits > buf_len - bitmap ) { + return false; + } - return success; + if( battle_config.client_emblem_max_blank_percent < 100 ) { + int required_pixels = BITMAP_WIDTH * BITMAP_HEIGHT * (100 - battle_config.client_emblem_max_blank_percent) / 100; + int found_pixels = 0; + /// Checks what percentage of a guild emblem is blank. A blank emblem + /// consists solely of magenta pixels. Since the client uses 16-bit + /// colors, any magenta shade that reduces to #ff00ff passes off as + /// transparent color as well (down to #f807f8). + /// + /// Unlike real magenta, reduced magenta causes the guild window to + /// become see-through in the transparent parts of the emblem + /// background (glitch). + switch( RBUFW(buf,28) ) { + case 8: // palette indexes + { + const uint8 *indexes = (const uint8 *)RBUFP(buf,offbits); + const uint32 *palette = (const uint32 *)RBUFP(buf,BITMAPFILEHEADER_SIZE + BITMAPINFOHEADER_SIZE); + + for( i = 0; i < BITMAP_WIDTH * BITMAP_HEIGHT; i++ ) { + if( indexes[i] >= palettesize ) // Invalid color + return false; + + // if( color->r < 0xF8 || color->g > 0x07 || color->b < 0xF8 ) + if( ( palette[indexes[i]]&0x00F8F8F8 ) != 0x00F800F8 ) { + if( ++found_pixels >= required_pixels ) { + // Enough valid pixels were found + return true; + } + } + } + break; + } + case 24: // full colors + { + const struct s_bitmaptripple *pixels = (const struct s_bitmaptripple*)RBUFP(buf,offbits); + + for( i = 0; i < BITMAP_WIDTH * BITMAP_HEIGHT; i++ ) { + // if( pixels[i].r < 0xF8 || pixels[i].g > 0x07 || pixels[i].b < 0xF8 ) + if( ( pixels[i].rgb&0xF8F8F8 ) != 0xF800F8 ) { + if( ++found_pixels >= required_pixels ) { + // Enough valid pixels were found + return true; + } + } + } + break; + } + } + + // Not enough non-blank pixels found + return false; + } + + return true; } |