2019-12-08 19:27:17 +00:00
/* (c) DDNet developers. See licence.txt in the root of the distribution for more information. */
/* If you are missing that file, acquire a complete release at teeworlds.com. */
2022-04-22 23:04:48 +00:00
# include <base/logger.h>
2020-09-26 19:41:58 +00:00
# include <base/system.h>
2022-06-21 13:26:23 +00:00
# include <engine/gfx/image_loader.h>
2020-09-26 19:41:58 +00:00
# include <engine/graphics.h>
2019-12-08 19:27:17 +00:00
# include <engine/shared/datafile.h>
# include <engine/storage.h>
2020-10-27 11:15:11 +00:00
# include <game/gamecore.h>
2019-12-08 19:27:17 +00:00
# include <game/mapitems.h>
/*
Usage : map_convert_07 < source map filepath > < dest map filepath >
*/
CDataFileReader g_DataReader ;
CDataFileWriter g_DataWriter ;
// global new image data (set by ReplaceImageItem)
2023-10-28 15:36:28 +00:00
int g_aNewDataSize [ MAX_MAPIMAGES ] ;
void * g_apNewData [ MAX_MAPIMAGES ] ;
2019-12-08 19:27:17 +00:00
int g_Index = 0 ;
int g_NextDataItemID = - 1 ;
2023-10-28 15:36:28 +00:00
int g_aImageIDs [ MAX_MAPIMAGES ] ;
2020-06-20 19:53:48 +00:00
2019-12-08 19:27:17 +00:00
int LoadPNG ( CImageInfo * pImg , const char * pFilename )
{
2021-11-05 16:21:41 +00:00
IOHANDLE File = io_open ( pFilename , IOFLAG_READ ) ;
2022-06-11 07:09:42 +00:00
if ( File )
2021-11-05 16:21:41 +00:00
{
2022-06-11 07:09:42 +00:00
io_seek ( File , 0 , IOSEEK_END ) ;
unsigned int FileSize = io_tell ( File ) ;
io_seek ( File , 0 , IOSEEK_START ) ;
TImageByteBuffer ByteBuffer ;
SImageByteBuffer ImageByteBuffer ( & ByteBuffer ) ;
2019-12-08 19:27:17 +00:00
2022-06-11 07:09:42 +00:00
ByteBuffer . resize ( FileSize ) ;
io_read ( File , & ByteBuffer . front ( ) , FileSize ) ;
2019-12-08 19:27:17 +00:00
2021-11-05 16:21:41 +00:00
io_close ( File ) ;
2019-12-08 19:27:17 +00:00
2022-06-11 07:09:42 +00:00
uint8_t * pImgBuffer = NULL ;
EImageFormat ImageFormat ;
2022-06-27 14:57:23 +00:00
int PngliteIncompatible ;
if ( LoadPNG ( ImageByteBuffer , pFilename , PngliteIncompatible , pImg - > m_Width , pImg - > m_Height , pImgBuffer , ImageFormat ) )
2022-06-11 07:09:42 +00:00
{
pImg - > m_pData = pImgBuffer ;
if ( ImageFormat = = IMAGE_FORMAT_RGBA & & pImg - > m_Width < = ( 2 < < 13 ) & & pImg - > m_Height < = ( 2 < < 13 ) )
{
pImg - > m_Format = CImageInfo : : FORMAT_RGBA ;
}
else
{
dbg_msg ( " map_convert_07 " , " invalid image format. filename='%s' " , pFilename ) ;
return 0 ;
}
}
else
return 0 ;
2020-09-30 16:03:06 +00:00
}
2022-06-11 07:09:42 +00:00
else
return 0 ;
2019-12-08 19:27:17 +00:00
return 1 ;
}
2022-06-04 08:13:11 +00:00
bool CheckImageDimensions ( void * pLayerItem , int LayerType , const char * pFilename )
2020-06-20 19:53:48 +00:00
{
2022-06-04 08:13:11 +00:00
if ( LayerType ! = MAPITEMTYPE_LAYER )
2020-06-20 19:53:48 +00:00
return true ;
2022-06-04 08:13:11 +00:00
CMapItemLayer * pImgLayer = ( CMapItemLayer * ) pLayerItem ;
2020-06-20 19:53:48 +00:00
if ( pImgLayer - > m_Type ! = LAYERTYPE_TILES )
return true ;
CMapItemLayerTilemap * pTMap = ( CMapItemLayerTilemap * ) pImgLayer ;
if ( pTMap - > m_Image = = - 1 )
return true ;
2022-06-04 08:13:11 +00:00
int Type ;
2023-02-20 22:38:54 +00:00
void * pItem = g_DataReader . GetItem ( g_aImageIDs [ pTMap - > m_Image ] , & Type ) ;
2022-06-04 08:13:11 +00:00
if ( Type ! = MAPITEMTYPE_IMAGE )
2020-06-20 19:53:48 +00:00
return true ;
2022-06-04 08:13:11 +00:00
CMapItemImage * pImgItem = ( CMapItemImage * ) pItem ;
2020-08-31 12:01:30 +00:00
2020-09-14 16:03:20 +00:00
if ( pImgItem - > m_Width % 16 = = 0 & & pImgItem - > m_Height % 16 = = 0 & & pImgItem - > m_Width > 0 & & pImgItem - > m_Height > 0 )
2020-06-20 19:53:48 +00:00
return true ;
char aTileLayerName [ 12 ] ;
2020-09-26 19:41:58 +00:00
IntsToStr ( pTMap - > m_aName , sizeof ( pTMap - > m_aName ) / sizeof ( int ) , aTileLayerName ) ;
2023-08-13 09:50:35 +00:00
const char * pName = g_DataReader . GetDataString ( pImgItem - > m_ImageName ) ;
dbg_msg ( " map_convert_07 " , " %s: Tile layer \" %s \" uses image \" %s \" with width %d, height %d, which is not divisible by 16. This is not supported in Teeworlds 0.7. Please scale the image and replace it manually. " , pFilename , aTileLayerName , pName = = nullptr ? " (error) " : pName , pImgItem - > m_Width , pImgItem - > m_Height ) ;
2020-06-20 19:53:48 +00:00
return false ;
}
2023-08-13 09:50:35 +00:00
void * ReplaceImageItem ( int Index , CMapItemImage * pImgItem , CMapItemImage * pNewImgItem )
2019-12-08 19:27:17 +00:00
{
if ( ! pImgItem - > m_External )
2023-04-27 15:13:35 +00:00
return pImgItem ;
2019-12-08 19:27:17 +00:00
2023-08-13 09:50:35 +00:00
const char * pName = g_DataReader . GetDataString ( pImgItem - > m_ImageName ) ;
if ( pName = = nullptr | | pName [ 0 ] = = ' \0 ' )
{
dbg_msg ( " map_convert_07 " , " failed to load name of image %d " , Index ) ;
return pImgItem ;
}
2019-12-08 19:27:17 +00:00
dbg_msg ( " map_convert_07 " , " embedding image '%s' " , pName ) ;
CImageInfo ImgInfo ;
2023-04-27 15:13:35 +00:00
char aStr [ IO_MAX_PATH_LENGTH ] ;
2019-12-08 19:27:17 +00:00
str_format ( aStr , sizeof ( aStr ) , " data/mapres/%s.png " , pName ) ;
if ( ! LoadPNG ( & ImgInfo , aStr ) )
2023-04-27 15:13:35 +00:00
return pImgItem ; // keep as external if we don't have a mapres to replace
if ( ImgInfo . m_Format ! = CImageInfo : : FORMAT_RGBA )
{
dbg_msg ( " map_convert_07 " , " image '%s' is not in RGBA format " , aStr ) ;
return pImgItem ;
}
2019-12-08 19:27:17 +00:00
* pNewImgItem = * pImgItem ;
2020-06-20 19:53:48 +00:00
pNewImgItem - > m_Width = ImgInfo . m_Width ;
2019-12-08 19:27:17 +00:00
pNewImgItem - > m_Height = ImgInfo . m_Height ;
pNewImgItem - > m_External = false ;
pNewImgItem - > m_ImageData = g_NextDataItemID + + ;
2022-06-04 08:13:11 +00:00
g_apNewData [ g_Index ] = ImgInfo . m_pData ;
2023-04-27 15:15:48 +00:00
g_aNewDataSize [ g_Index ] = ( size_t ) ImgInfo . m_Width * ImgInfo . m_Height * ImgInfo . PixelSize ( ) ;
2019-12-08 19:27:17 +00:00
g_Index + + ;
return ( void * ) pNewImgItem ;
}
int main ( int argc , const char * * argv )
{
2022-06-13 16:07:29 +00:00
CCmdlineFix CmdlineFix ( & argc , & argv ) ;
2022-04-22 23:04:48 +00:00
log_set_global_logger_default ( ) ;
2019-12-08 19:27:17 +00:00
2020-08-16 21:21:00 +00:00
if ( argc < 2 | | argc > 3 )
2019-12-08 19:27:17 +00:00
{
dbg_msg ( " map_convert_07 " , " Invalid arguments " ) ;
2020-08-16 21:21:00 +00:00
dbg_msg ( " map_convert_07 " , " Usage: map_convert_07 <source map filepath> [<dest map filepath>] " ) ;
2019-12-08 19:27:17 +00:00
return - 1 ;
}
2022-06-04 08:13:11 +00:00
IStorage * pStorage = CreateStorage ( IStorage : : STORAGETYPE_BASIC , argc , argv ) ;
2020-08-16 21:21:00 +00:00
if ( ! pStorage )
2019-12-08 19:27:17 +00:00
{
dbg_msg ( " map_convert_07 " , " error loading storage " ) ;
return - 1 ;
}
const char * pSourceFileName = argv [ 1 ] ;
2021-09-13 08:06:34 +00:00
char aDestFileName [ IO_MAX_PATH_LENGTH ] ;
2020-08-16 21:21:00 +00:00
if ( argc = = 3 )
{
2022-06-04 08:13:11 +00:00
str_copy ( aDestFileName , argv [ 2 ] , sizeof ( aDestFileName ) ) ;
2020-08-16 21:21:00 +00:00
}
else
{
2021-09-13 08:06:34 +00:00
char aBuf [ IO_MAX_PATH_LENGTH ] ;
2020-08-16 21:21:00 +00:00
IStorage : : StripPathAndExtension ( pSourceFileName , aBuf , sizeof ( aBuf ) ) ;
str_format ( aDestFileName , sizeof ( aDestFileName ) , " data/maps7/%s.map " , aBuf ) ;
2020-08-31 12:01:30 +00:00
if ( fs_makedir ( " data " ) ! = 0 )
{
dbg_msg ( " map_convert_07 " , " failed to create data directory " ) ;
return - 1 ;
}
2020-08-16 21:21:00 +00:00
if ( fs_makedir ( " data/maps7 " ) ! = 0 )
{
2020-08-31 12:01:30 +00:00
dbg_msg ( " map_convert_07 " , " failed to create data/maps7 directory " ) ;
2020-08-16 21:21:00 +00:00
return - 1 ;
}
}
2019-12-08 19:27:17 +00:00
2019-12-08 22:14:56 +00:00
if ( ! g_DataReader . Open ( pStorage , pSourceFileName , IStorage : : TYPE_ABSOLUTE ) )
2019-12-08 19:27:17 +00:00
{
dbg_msg ( " map_convert_07 " , " failed to open source map. filename='%s' " , pSourceFileName ) ;
return - 1 ;
}
2022-06-04 08:13:11 +00:00
if ( ! g_DataWriter . Open ( pStorage , aDestFileName , IStorage : : TYPE_ABSOLUTE ) )
2019-12-08 19:27:17 +00:00
{
2022-06-04 08:13:11 +00:00
dbg_msg ( " map_convert_07 " , " failed to open destination map. filename='%s' " , aDestFileName ) ;
2019-12-08 19:27:17 +00:00
return - 1 ;
}
2019-12-29 11:43:41 +00:00
g_NextDataItemID = g_DataReader . NumData ( ) ;
2019-12-08 19:27:17 +00:00
2023-10-28 15:36:28 +00:00
size_t i = 0 ;
2020-06-20 19:53:48 +00:00
for ( int Index = 0 ; Index < g_DataReader . NumItems ( ) ; Index + + )
{
2022-06-04 08:13:11 +00:00
int Type ;
2023-02-20 22:38:54 +00:00
g_DataReader . GetItem ( Index , & Type ) ;
2020-06-20 19:53:48 +00:00
if ( Type = = MAPITEMTYPE_IMAGE )
2023-10-28 15:36:28 +00:00
{
if ( i > = MAX_MAPIMAGES )
{
dbg_msg ( " map_convert_07 " , " map uses more images than the client maximum of % " PRIzu " . filename='%s' " , MAX_MAPIMAGES , pSourceFileName ) ;
break ;
}
g_aImageIDs [ i ] = Index ;
i + + ;
}
2020-06-20 19:53:48 +00:00
}
bool Success = true ;
2019-12-08 19:27:17 +00:00
// add all items
for ( int Index = 0 ; Index < g_DataReader . NumItems ( ) ; Index + + )
{
2022-06-04 08:13:11 +00:00
int Type , ID ;
void * pItem = g_DataReader . GetItem ( Index , & Type , & ID ) ;
2020-06-20 19:53:48 +00:00
2023-12-21 23:10:34 +00:00
// Filter items with unknown type, as we can't write them back.
// Filter ITEMTYPE_EX items, they will be automatically added again.
if ( Type < 0 | | Type = = ITEMTYPE_EX )
2021-05-03 08:43:00 +00:00
{
continue ;
}
2023-12-21 23:10:34 +00:00
int Size = g_DataReader . GetItemSize ( Index ) ;
2020-06-20 19:53:48 +00:00
Success & = CheckImageDimensions ( pItem , Type , pSourceFileName ) ;
2022-06-04 08:13:11 +00:00
CMapItemImage NewImageItem ;
2023-04-27 15:13:35 +00:00
if ( Type = = MAPITEMTYPE_IMAGE )
{
2023-08-13 09:50:35 +00:00
pItem = ReplaceImageItem ( Index , ( CMapItemImage * ) pItem , & NewImageItem ) ;
2023-04-27 15:13:35 +00:00
if ( ! pItem )
return - 1 ;
Size = sizeof ( CMapItemImage ) ;
NewImageItem . m_Version = CMapItemImage : : CURRENT_VERSION ;
}
2019-12-08 19:27:17 +00:00
g_DataWriter . AddItem ( Type , ID , Size , pItem ) ;
}
// add all data
2019-12-29 11:43:41 +00:00
for ( int Index = 0 ; Index < g_DataReader . NumData ( ) ; Index + + )
2019-12-08 19:27:17 +00:00
{
2022-06-04 08:13:11 +00:00
void * pData = g_DataReader . GetData ( Index ) ;
int Size = g_DataReader . GetDataSize ( Index ) ;
2019-12-29 12:35:19 +00:00
g_DataWriter . AddData ( Size , pData ) ;
2019-12-08 19:27:17 +00:00
}
for ( int Index = 0 ; Index < g_Index ; Index + + )
{
2022-06-04 08:13:11 +00:00
g_DataWriter . AddData ( g_aNewDataSize [ Index ] , g_apNewData [ Index ] ) ;
2019-12-08 19:27:17 +00:00
}
g_DataReader . Close ( ) ;
g_DataWriter . Finish ( ) ;
2020-06-20 19:53:48 +00:00
return Success ? 0 : - 1 ;
2019-12-08 19:27:17 +00:00
}