2009-08-21 21:21:05 +01:00
/*
* This file is part of OpenTTD .
* OpenTTD is free software ; you can redistribute it and / or modify it under the terms of the GNU General Public License as published by the Free Software Foundation , version 2.
* OpenTTD is distributed in the hope that it will be useful , but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
* See the GNU General Public License for more details . You should have received a copy of the GNU General Public License along with OpenTTD . If not , see < http : //www.gnu.org/licenses/>.
*/
2008-05-06 16:11:33 +01:00
/** @file grf.cpp Reading graphics data from (New)GRF files. */
2007-06-12 13:27:40 +01:00
2007-06-11 12:50:49 +01:00
# include "../stdafx.h"
2007-12-23 10:56:02 +00:00
# include "../gfx_func.h"
2007-06-11 12:50:49 +01:00
# include "../debug.h"
2021-03-13 09:00:53 +00:00
# include "../settings_type.h"
2008-11-23 13:42:05 +00:00
# include "../strings_func.h"
# include "table/strings.h"
2011-12-10 13:54:10 +00:00
# include "../error.h"
2011-01-22 14:52:20 +00:00
# include "../core/math_func.hpp"
2012-02-04 13:28:40 +00:00
# include "../core/alloc_type.hpp"
2012-02-04 13:29:18 +00:00
# include "../core/bitmath_func.hpp"
2007-06-11 12:50:49 +01:00
# include "grf.hpp"
2014-04-23 21:13:33 +01:00
# include "../safeguards.h"
2011-05-04 18:12:37 +01:00
extern const byte _palmap_w2d [ ] ;
2008-11-23 13:42:05 +00:00
/**
* We found a corrupted sprite . This means that the sprite itself
* contains invalid data or is too small for the given dimensions .
* @ param file_slot the file the errored sprite is in
* @ param file_pos the location in the file of the errored sprite
* @ param line the line where the error occurs .
* @ return always false ( to tell loading the sprite failed )
*/
2021-04-14 16:20:39 +01:00
static bool WarnCorruptSprite ( const SpriteFile & file , size_t file_pos , int line )
2008-11-23 13:42:05 +00:00
{
static byte warning_level = 0 ;
if ( warning_level = = 0 ) {
2021-05-30 10:40:54 +01:00
SetDParamStr ( 0 , file . GetSimplifiedFilename ( ) ) ;
2010-02-24 14:46:15 +00:00
ShowErrorMessage ( STR_NEWGRF_ERROR_CORRUPT_SPRITE , INVALID_STRING_ID , WL_ERROR ) ;
2008-11-23 13:42:05 +00:00
}
2021-06-12 08:10:17 +01:00
Debug ( sprite , warning_level , " [{}] Loading corrupted sprite from {} at position {} " , line , file . GetSimplifiedFilename ( ) , file_pos ) ;
2008-11-23 13:42:05 +00:00
warning_level = 6 ;
return false ;
}
2012-02-04 13:28:40 +00:00
/**
* Decode the image data of a single sprite .
* @ param [ in , out ] sprite Filled with the sprite image data .
2021-04-14 16:20:39 +01:00
* @ param file The file with the sprite data .
2012-02-04 13:28:40 +00:00
* @ param file_pos File position .
* @ param sprite_type Type of the sprite we ' re decoding .
* @ param num Size of the decompressed sprite .
* @ param type Type of the encoded sprite .
2012-02-04 13:29:13 +00:00
* @ param zoom_lvl Requested zoom level .
2012-02-04 22:18:57 +00:00
* @ param colour_fmt Colour format of the sprite .
2012-02-04 19:21:32 +00:00
* @ param container_format Container format of the GRF this sprite is in .
2012-02-04 13:28:40 +00:00
* @ return True if the sprite was successfully loaded .
*/
2021-04-14 16:20:39 +01:00
bool DecodeSingleSprite ( SpriteLoader : : Sprite * sprite , SpriteFile & file , size_t file_pos , SpriteType sprite_type , int64 num , byte type , ZoomLevel zoom_lvl , byte colour_fmt , byte container_format )
2007-06-11 12:50:49 +01:00
{
2019-04-14 15:36:06 +01:00
std : : unique_ptr < byte [ ] > dest_orig ( new byte [ num ] ) ;
byte * dest = dest_orig . get ( ) ;
2012-02-04 13:28:40 +00:00
const int64 dest_size = num ;
2007-06-11 12:50:49 +01:00
/* Read the file, which has some kind of compression */
while ( num > 0 ) {
2021-04-14 16:20:39 +01:00
int8 code = file . ReadByte ( ) ;
2007-06-11 12:50:49 +01:00
if ( code > = 0 ) {
/* Plain bytes to read */
int size = ( code = = 0 ) ? 0x80 : code ;
num - = size ;
2021-04-14 16:20:39 +01:00
if ( num < 0 ) return WarnCorruptSprite ( file , file_pos , __LINE__ ) ;
2007-06-11 12:50:49 +01:00
for ( ; size > 0 ; size - - ) {
2021-04-14 16:20:39 +01:00
* dest = file . ReadByte ( ) ;
2007-06-11 12:50:49 +01:00
dest + + ;
}
} else {
/* Copy bytes from earlier in the sprite */
2021-04-14 16:20:39 +01:00
const uint data_offset = ( ( code & 7 ) < < 8 ) | file . ReadByte ( ) ;
if ( dest - data_offset < dest_orig . get ( ) ) return WarnCorruptSprite ( file , file_pos , __LINE__ ) ;
2007-06-11 12:50:49 +01:00
int size = - ( code > > 3 ) ;
num - = size ;
2021-04-14 16:20:39 +01:00
if ( num < 0 ) return WarnCorruptSprite ( file , file_pos , __LINE__ ) ;
2007-06-11 12:50:49 +01:00
for ( ; size > 0 ; size - - ) {
* dest = * ( dest - data_offset ) ;
dest + + ;
}
}
}
2021-04-14 16:20:39 +01:00
if ( num ! = 0 ) return WarnCorruptSprite ( file , file_pos , __LINE__ ) ;
2007-06-11 12:50:49 +01:00
2012-02-04 13:29:13 +00:00
sprite - > AllocateData ( zoom_lvl , sprite - > width * sprite - > height ) ;
2007-06-11 12:50:49 +01:00
2012-02-04 22:18:57 +00:00
/* Convert colour depth to pixel size. */
int bpp = 0 ;
if ( colour_fmt & SCC_RGB ) bpp + = 3 ; // Has RGB data.
if ( colour_fmt & SCC_ALPHA ) bpp + + ; // Has alpha data.
if ( colour_fmt & SCC_PAL ) bpp + + ; // Has palette data.
2010-01-01 18:45:40 +00:00
/* When there are transparency pixels, this format has another trick.. decode it */
2007-06-11 12:50:49 +01:00
if ( type & 0x08 ) {
for ( int y = 0 ; y < sprite - > height ; y + + ) {
bool last_item = false ;
/* Look up in the header-table where the real data is stored for this row */
2012-02-04 19:21:32 +00:00
int offset ;
2012-02-04 20:12:06 +00:00
if ( container_format > = 2 & & dest_size > UINT16_MAX ) {
2012-02-04 19:21:32 +00:00
offset = ( dest_orig [ y * 4 + 3 ] < < 24 ) | ( dest_orig [ y * 4 + 2 ] < < 16 ) | ( dest_orig [ y * 4 + 1 ] < < 8 ) | dest_orig [ y * 4 ] ;
} else {
offset = ( dest_orig [ y * 2 + 1 ] < < 8 ) | dest_orig [ y * 2 ] ;
}
2008-11-23 13:42:05 +00:00
2007-06-11 12:50:49 +01:00
/* Go to that row */
2019-04-14 15:36:06 +01:00
dest = dest_orig . get ( ) + offset ;
2007-06-11 12:50:49 +01:00
do {
2019-04-14 15:36:06 +01:00
if ( dest + ( container_format > = 2 & & sprite - > width > 256 ? 4 : 2 ) > dest_orig . get ( ) + dest_size ) {
2021-04-14 16:20:39 +01:00
return WarnCorruptSprite ( file , file_pos , __LINE__ ) ;
2008-11-23 13:42:05 +00:00
}
2007-06-11 12:50:49 +01:00
SpriteLoader : : CommonPixel * data ;
2012-02-04 19:21:32 +00:00
/* Read the header. */
int length , skip ;
if ( container_format > = 2 & & sprite - > width > 256 ) {
/* 0 .. 14 - length
* 15 - last_item
* 16 . . 31 - transparency bytes */
last_item = ( dest [ 1 ] & 0x80 ) ! = 0 ;
length = ( ( dest [ 1 ] & 0x7F ) < < 8 ) | dest [ 0 ] ;
skip = ( dest [ 3 ] < < 8 ) | dest [ 2 ] ;
dest + = 4 ;
} else {
/* 0 .. 6 - length
* 7 - last_item
* 8 . . 15 - transparency bytes */
last_item = ( ( * dest ) & 0x80 ) ! = 0 ;
length = ( * dest + + ) & 0x7F ;
skip = * dest + + ;
}
2007-06-11 12:50:49 +01:00
data = & sprite - > data [ y * sprite - > width + skip ] ;
2019-04-14 15:36:06 +01:00
if ( skip + length > sprite - > width | | dest + length * bpp > dest_orig . get ( ) + dest_size ) {
2021-04-14 16:20:39 +01:00
return WarnCorruptSprite ( file , file_pos , __LINE__ ) ;
2008-11-23 13:42:05 +00:00
}
2007-06-11 12:50:49 +01:00
for ( int x = 0 ; x < length ; x + + ) {
2012-02-04 22:18:57 +00:00
if ( colour_fmt & SCC_RGB ) {
data - > r = * dest + + ;
data - > g = * dest + + ;
data - > b = * dest + + ;
}
data - > a = ( colour_fmt & SCC_ALPHA ) ? * dest + + : 0xFF ;
if ( colour_fmt & SCC_PAL ) {
switch ( sprite_type ) {
2021-04-14 16:20:39 +01:00
case ST_NORMAL : data - > m = file . NeedsPaletteRemap ( ) ? _palmap_w2d [ * dest ] : * dest ; break ;
2021-01-08 10:16:18 +00:00
case ST_FONT : data - > m = std : : min < uint > ( * dest , 2u ) ; break ;
2012-02-04 22:18:57 +00:00
default : data - > m = * dest ; break ;
}
/* Magic blue. */
if ( colour_fmt = = SCC_PAL & & * dest = = 0 ) data - > a = 0x00 ;
dest + + ;
2009-12-03 15:27:33 +00:00
}
2007-06-11 12:50:49 +01:00
data + + ;
}
} while ( ! last_item ) ;
}
} else {
2012-02-04 22:18:57 +00:00
if ( dest_size < sprite - > width * sprite - > height * bpp ) {
2021-04-14 16:20:39 +01:00
return WarnCorruptSprite ( file , file_pos , __LINE__ ) ;
2008-11-23 13:42:05 +00:00
}
2012-02-04 22:18:57 +00:00
if ( dest_size > sprite - > width * sprite - > height * bpp ) {
2008-11-23 13:42:05 +00:00
static byte warning_level = 0 ;
2021-06-12 08:10:17 +01:00
Debug ( sprite , warning_level , " Ignoring {} unused extra bytes from the sprite from {} at position {} " , dest_size - sprite - > width * sprite - > height * bpp , file . GetSimplifiedFilename ( ) , file_pos ) ;
2008-11-23 13:42:05 +00:00
warning_level = 6 ;
}
2019-04-14 15:36:06 +01:00
dest = dest_orig . get ( ) ;
2008-09-02 19:45:15 +01:00
for ( int i = 0 ; i < sprite - > width * sprite - > height ; i + + ) {
2012-02-04 22:18:57 +00:00
byte * pixel = & dest [ i * bpp ] ;
if ( colour_fmt & SCC_RGB ) {
sprite - > data [ i ] . r = * pixel + + ;
sprite - > data [ i ] . g = * pixel + + ;
sprite - > data [ i ] . b = * pixel + + ;
}
sprite - > data [ i ] . a = ( colour_fmt & SCC_ALPHA ) ? * pixel + + : 0xFF ;
if ( colour_fmt & SCC_PAL ) {
switch ( sprite_type ) {
2021-04-14 16:20:39 +01:00
case ST_NORMAL : sprite - > data [ i ] . m = file . NeedsPaletteRemap ( ) ? _palmap_w2d [ * pixel ] : * pixel ; break ;
2021-01-08 10:16:18 +00:00
case ST_FONT : sprite - > data [ i ] . m = std : : min < uint > ( * pixel , 2u ) ; break ;
2012-02-04 22:18:57 +00:00
default : sprite - > data [ i ] . m = * pixel ; break ;
}
/* Magic blue. */
if ( colour_fmt = = SCC_PAL & & * pixel = = 0 ) sprite - > data [ i ] . a = 0x00 ;
pixel + + ;
2009-12-03 15:27:33 +00:00
}
2008-09-02 19:45:15 +01:00
}
2007-06-11 12:50:49 +01:00
}
return true ;
}
2012-02-04 13:28:40 +00:00
2021-04-14 16:20:39 +01:00
uint8 LoadSpriteV1 ( SpriteLoader : : Sprite * sprite , SpriteFile & file , size_t file_pos , SpriteType sprite_type , bool load_32bpp )
2012-02-04 13:28:40 +00:00
{
2012-02-04 22:18:57 +00:00
/* Check the requested colour depth. */
if ( load_32bpp ) return 0 ;
2012-02-04 13:28:40 +00:00
/* Open the right file and go to the correct position */
2021-04-14 16:20:39 +01:00
file . SeekTo ( file_pos , SEEK_SET ) ;
2012-02-04 13:28:40 +00:00
/* Read the size and type */
2021-04-14 16:20:39 +01:00
int num = file . ReadWord ( ) ;
byte type = file . ReadByte ( ) ;
2012-02-04 13:28:40 +00:00
/* Type 0xFF indicates either a colourmap or some other non-sprite info; we do not handle them here */
2012-02-04 13:29:13 +00:00
if ( type = = 0xFF ) return 0 ;
2014-10-12 21:43:25 +01:00
ZoomLevel zoom_lvl = ( sprite_type ! = ST_MAPGEN ) ? ZOOM_LVL_OUT_4X : ZOOM_LVL_NORMAL ;
2012-02-04 13:28:40 +00:00
2021-04-14 16:20:39 +01:00
sprite [ zoom_lvl ] . height = file . ReadByte ( ) ;
sprite [ zoom_lvl ] . width = file . ReadWord ( ) ;
sprite [ zoom_lvl ] . x_offs = file . ReadWord ( ) ;
sprite [ zoom_lvl ] . y_offs = file . ReadWord ( ) ;
2021-01-16 15:43:30 +00:00
sprite [ zoom_lvl ] . colours = SCC_PAL ;
2012-02-04 13:28:40 +00:00
2013-11-26 22:03:56 +00:00
if ( sprite [ zoom_lvl ] . width > INT16_MAX ) {
2021-04-14 16:20:39 +01:00
WarnCorruptSprite ( file , file_pos , __LINE__ ) ;
2013-11-26 22:03:56 +00:00
return 0 ;
}
2012-02-04 13:28:40 +00:00
/* 0x02 indicates it is a compressed sprite, so we can't rely on 'num' to be valid.
* In case it is uncompressed , the size is ' num ' - 8 ( header - size ) . */
2012-02-04 13:29:13 +00:00
num = ( type & 0x02 ) ? sprite [ zoom_lvl ] . width * sprite [ zoom_lvl ] . height : num - 8 ;
2012-02-04 13:28:40 +00:00
2021-04-14 16:20:39 +01:00
if ( DecodeSingleSprite ( & sprite [ zoom_lvl ] , file , file_pos , sprite_type , num , type , zoom_lvl , SCC_PAL , 1 ) ) return 1 < < zoom_lvl ;
2012-02-04 13:29:13 +00:00
return 0 ;
2012-02-04 13:28:40 +00:00
}
2012-02-04 13:29:04 +00:00
2021-04-14 16:20:39 +01:00
uint8 LoadSpriteV2 ( SpriteLoader : : Sprite * sprite , SpriteFile & file , size_t file_pos , SpriteType sprite_type , bool load_32bpp )
2012-02-04 13:29:04 +00:00
{
2012-02-04 13:29:18 +00:00
static const ZoomLevel zoom_lvl_map [ 6 ] = { ZOOM_LVL_OUT_4X , ZOOM_LVL_NORMAL , ZOOM_LVL_OUT_2X , ZOOM_LVL_OUT_8X , ZOOM_LVL_OUT_16X , ZOOM_LVL_OUT_32X } ;
2012-02-04 13:29:04 +00:00
/* Is the sprite not present/stripped in the GRF? */
2012-02-04 13:29:13 +00:00
if ( file_pos = = SIZE_MAX ) return 0 ;
2012-02-04 13:29:04 +00:00
/* Open the right file and go to the correct position */
2021-04-14 16:20:39 +01:00
file . SeekTo ( file_pos , SEEK_SET ) ;
2012-02-04 13:29:04 +00:00
2021-04-14 16:20:39 +01:00
uint32 id = file . ReadDword ( ) ;
2012-02-04 13:29:04 +00:00
2012-02-04 13:29:18 +00:00
uint8 loaded_sprites = 0 ;
2012-02-04 13:29:04 +00:00
do {
2021-04-14 16:20:39 +01:00
int64 num = file . ReadDword ( ) ;
size_t start_pos = file . GetPos ( ) ;
byte type = file . ReadByte ( ) ;
2012-02-04 13:29:04 +00:00
/* Type 0xFF indicates either a colourmap or some other non-sprite info; we do not handle them here. */
2012-02-04 13:29:13 +00:00
if ( type = = 0xFF ) return 0 ;
2012-02-04 13:29:04 +00:00
byte colour = type & SCC_MASK ;
2021-04-14 16:20:39 +01:00
byte zoom = file . ReadByte ( ) ;
2012-02-04 13:29:04 +00:00
2021-03-13 09:00:53 +00:00
bool is_wanted_colour_depth = ( colour ! = 0 & & ( load_32bpp ? colour ! = SCC_PAL : colour = = SCC_PAL ) ) ;
bool is_wanted_zoom_lvl ;
if ( sprite_type ! = ST_MAPGEN ) {
is_wanted_zoom_lvl = ( zoom < lengthof ( zoom_lvl_map ) & & zoom_lvl_map [ zoom ] > = _settings_client . gui . sprite_zoom_min ) ;
} else {
is_wanted_zoom_lvl = ( zoom = = 0 ) ;
}
if ( is_wanted_colour_depth & & is_wanted_zoom_lvl ) {
2014-10-12 21:43:25 +01:00
ZoomLevel zoom_lvl = ( sprite_type ! = ST_MAPGEN ) ? zoom_lvl_map [ zoom ] : ZOOM_LVL_NORMAL ;
2012-02-04 13:29:18 +00:00
if ( HasBit ( loaded_sprites , zoom_lvl ) ) {
/* We already have this zoom level, skip sprite. */
2021-06-12 08:10:17 +01:00
Debug ( sprite , 1 , " Ignoring duplicate zoom level sprite {} from {} " , id , file . GetSimplifiedFilename ( ) ) ;
2021-04-14 16:20:39 +01:00
file . SkipBytes ( num - 2 ) ;
2012-02-04 13:29:18 +00:00
continue ;
}
2012-02-04 13:29:13 +00:00
2021-04-14 16:20:39 +01:00
sprite [ zoom_lvl ] . height = file . ReadWord ( ) ;
sprite [ zoom_lvl ] . width = file . ReadWord ( ) ;
sprite [ zoom_lvl ] . x_offs = file . ReadWord ( ) ;
sprite [ zoom_lvl ] . y_offs = file . ReadWord ( ) ;
2012-02-04 13:29:04 +00:00
2013-11-26 22:03:56 +00:00
if ( sprite [ zoom_lvl ] . width > INT16_MAX | | sprite [ zoom_lvl ] . height > INT16_MAX ) {
2021-04-14 16:20:39 +01:00
WarnCorruptSprite ( file , file_pos , __LINE__ ) ;
2013-11-26 22:03:56 +00:00
return 0 ;
}
2012-02-04 13:29:04 +00:00
/* Mask out colour information. */
type = type & ~ SCC_MASK ;
2012-02-04 22:18:57 +00:00
/* Convert colour depth to pixel size. */
int bpp = 0 ;
if ( colour & SCC_RGB ) bpp + = 3 ; // Has RGB data.
if ( colour & SCC_ALPHA ) bpp + + ; // Has alpha data.
if ( colour & SCC_PAL ) bpp + + ; // Has palette data.
2021-01-16 15:43:30 +00:00
sprite [ zoom_lvl ] . colours = ( SpriteColourComponent ) colour ;
2012-02-04 13:29:04 +00:00
/* For chunked encoding we store the decompressed size in the file,
* otherwise we can calculate it from the image dimensions . */
2021-04-14 16:20:39 +01:00
uint decomp_size = ( type & 0x08 ) ? file . ReadDword ( ) : sprite [ zoom_lvl ] . width * sprite [ zoom_lvl ] . height * bpp ;
2012-02-04 13:29:04 +00:00
2021-04-14 16:20:39 +01:00
bool valid = DecodeSingleSprite ( & sprite [ zoom_lvl ] , file , file_pos , sprite_type , decomp_size , type , zoom_lvl , colour , 2 ) ;
if ( file . GetPos ( ) ! = start_pos + num ) {
WarnCorruptSprite ( file , file_pos , __LINE__ ) ;
2012-02-04 13:29:13 +00:00
return 0 ;
}
2012-02-04 13:29:18 +00:00
if ( valid ) SetBit ( loaded_sprites , zoom_lvl ) ;
2012-02-04 13:29:04 +00:00
} else {
/* Not the wanted zoom level or colour depth, continue searching. */
2021-04-14 16:20:39 +01:00
file . SkipBytes ( num - 2 ) ;
2012-02-04 13:29:04 +00:00
}
2021-04-14 16:20:39 +01:00
} while ( file . ReadDword ( ) = = id ) ;
2012-02-04 13:29:04 +00:00
2012-02-04 13:29:18 +00:00
return loaded_sprites ;
2012-02-04 13:29:04 +00:00
}
2021-04-14 16:20:39 +01:00
uint8 SpriteLoaderGrf : : LoadSprite ( SpriteLoader : : Sprite * sprite , SpriteFile & file , size_t file_pos , SpriteType sprite_type , bool load_32bpp )
2012-02-04 13:29:04 +00:00
{
if ( this - > container_ver > = 2 ) {
2021-04-14 16:20:39 +01:00
return LoadSpriteV2 ( sprite , file , file_pos , sprite_type , load_32bpp ) ;
2012-02-04 13:29:04 +00:00
} else {
2021-04-14 16:20:39 +01:00
return LoadSpriteV1 ( sprite , file , file_pos , sprite_type , load_32bpp ) ;
2012-02-04 13:29:04 +00:00
}
}