1/* (c) DDNet developers. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include <base/logger.h>
5#include <base/system.h>
6#include <engine/gfx/image_loader.h>
7#include <engine/graphics.h>
8#include <engine/shared/datafile.h>
9#include <engine/storage.h>
10#include <game/gamecore.h>
11#include <game/mapitems.h>
12/*
13 Usage: map_convert_07 <source map filepath> <dest map filepath>
14*/
15
16CDataFileReader g_DataReader;
17CDataFileWriter g_DataWriter;
18
19// global new image data (set by ReplaceImageItem)
20int g_aNewDataSize[MAX_MAPIMAGES];
21void *g_apNewData[MAX_MAPIMAGES];
22
23int g_Index = 0;
24int g_NextDataItemId = -1;
25
26int g_aImageIds[MAX_MAPIMAGES];
27
28int LoadPNG(CImageInfo *pImg, const char *pFilename)
29{
30 IOHANDLE File = io_open(filename: pFilename, flags: IOFLAG_READ);
31 if(File)
32 {
33 io_seek(io: File, offset: 0, origin: IOSEEK_END);
34 long int FileSize = io_tell(io: File);
35 if(FileSize <= 0)
36 {
37 io_close(io: File);
38 dbg_msg(sys: "map_convert_07", fmt: "failed to get file size (%ld). filename='%s'", FileSize, pFilename);
39 return false;
40 }
41 io_seek(io: File, offset: 0, origin: IOSEEK_START);
42 TImageByteBuffer ByteBuffer;
43 SImageByteBuffer ImageByteBuffer(&ByteBuffer);
44
45 ByteBuffer.resize(new_size: FileSize);
46 io_read(io: File, buffer: &ByteBuffer.front(), size: FileSize);
47
48 io_close(io: File);
49
50 uint8_t *pImgBuffer = NULL;
51 EImageFormat ImageFormat;
52 int PngliteIncompatible;
53 if(LoadPNG(ByteLoader&: ImageByteBuffer, pFileName: pFilename, PngliteIncompatible, Width&: pImg->m_Width, Height&: pImg->m_Height, pImageBuff&: pImgBuffer, ImageFormat))
54 {
55 pImg->m_pData = pImgBuffer;
56
57 if(ImageFormat == IMAGE_FORMAT_RGBA && pImg->m_Width <= (2 << 13) && pImg->m_Height <= (2 << 13))
58 {
59 pImg->m_Format = CImageInfo::FORMAT_RGBA;
60 }
61 else
62 {
63 dbg_msg(sys: "map_convert_07", fmt: "invalid image format. filename='%s'", pFilename);
64 return 0;
65 }
66 }
67 else
68 return 0;
69 }
70 else
71 return 0;
72 return 1;
73}
74
75bool CheckImageDimensions(void *pLayerItem, int LayerType, const char *pFilename)
76{
77 if(LayerType != MAPITEMTYPE_LAYER)
78 return true;
79
80 CMapItemLayer *pImgLayer = (CMapItemLayer *)pLayerItem;
81 if(pImgLayer->m_Type != LAYERTYPE_TILES)
82 return true;
83
84 CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pImgLayer;
85 if(pTMap->m_Image == -1)
86 return true;
87
88 int Type;
89 void *pItem = g_DataReader.GetItem(Index: g_aImageIds[pTMap->m_Image], pType: &Type);
90 if(Type != MAPITEMTYPE_IMAGE)
91 return true;
92
93 CMapItemImage *pImgItem = (CMapItemImage *)pItem;
94
95 if(pImgItem->m_Width % 16 == 0 && pImgItem->m_Height % 16 == 0 && pImgItem->m_Width > 0 && pImgItem->m_Height > 0)
96 return true;
97
98 char aTileLayerName[12];
99 IntsToStr(pInts: pTMap->m_aName, NumInts: std::size(pTMap->m_aName), pStr: aTileLayerName, StrSize: std::size(aTileLayerName));
100
101 const char *pName = g_DataReader.GetDataString(Index: pImgItem->m_ImageName);
102 dbg_msg(sys: "map_convert_07", fmt: "%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);
103 return false;
104}
105
106void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, CMapItemImage *pNewImgItem)
107{
108 if(!pImgItem->m_External)
109 return pImgItem;
110
111 const char *pName = g_DataReader.GetDataString(Index: pImgItem->m_ImageName);
112 if(pName == nullptr || pName[0] == '\0')
113 {
114 dbg_msg(sys: "map_convert_07", fmt: "failed to load name of image %d", Index);
115 return pImgItem;
116 }
117
118 dbg_msg(sys: "map_convert_07", fmt: "embedding image '%s'", pName);
119
120 CImageInfo ImgInfo;
121 char aStr[IO_MAX_PATH_LENGTH];
122 str_format(buffer: aStr, buffer_size: sizeof(aStr), format: "data/mapres/%s.png", pName);
123 if(!LoadPNG(pImg: &ImgInfo, pFilename: aStr))
124 return pImgItem; // keep as external if we don't have a mapres to replace
125
126 if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA)
127 {
128 dbg_msg(sys: "map_convert_07", fmt: "image '%s' is not in RGBA format", aStr);
129 return pImgItem;
130 }
131
132 *pNewImgItem = *pImgItem;
133
134 pNewImgItem->m_Width = ImgInfo.m_Width;
135 pNewImgItem->m_Height = ImgInfo.m_Height;
136 pNewImgItem->m_External = false;
137 pNewImgItem->m_ImageData = g_NextDataItemId++;
138
139 g_apNewData[g_Index] = ImgInfo.m_pData;
140 g_aNewDataSize[g_Index] = ImgInfo.DataSize();
141 g_Index++;
142
143 return (void *)pNewImgItem;
144}
145
146int main(int argc, const char **argv)
147{
148 CCmdlineFix CmdlineFix(&argc, &argv);
149 log_set_global_logger_default();
150
151 if(argc < 2 || argc > 3)
152 {
153 dbg_msg(sys: "map_convert_07", fmt: "Invalid arguments");
154 dbg_msg(sys: "map_convert_07", fmt: "Usage: map_convert_07 <source map filepath> [<dest map filepath>]");
155 return -1;
156 }
157
158 IStorage *pStorage = CreateStorage(StorageType: IStorage::STORAGETYPE_BASIC, NumArgs: argc, ppArguments: argv);
159 if(!pStorage)
160 {
161 dbg_msg(sys: "map_convert_07", fmt: "error loading storage");
162 return -1;
163 }
164
165 const char *pSourceFileName = argv[1];
166 char aDestFileName[IO_MAX_PATH_LENGTH];
167
168 if(argc == 3)
169 {
170 str_copy(dst: aDestFileName, src: argv[2], dst_size: sizeof(aDestFileName));
171 }
172 else
173 {
174 char aBuf[IO_MAX_PATH_LENGTH];
175 IStorage::StripPathAndExtension(pFilename: pSourceFileName, pBuffer: aBuf, BufferSize: sizeof(aBuf));
176 str_format(buffer: aDestFileName, buffer_size: sizeof(aDestFileName), format: "data/maps7/%s.map", aBuf);
177 if(fs_makedir(path: "data") != 0)
178 {
179 dbg_msg(sys: "map_convert_07", fmt: "failed to create data directory");
180 return -1;
181 }
182
183 if(fs_makedir(path: "data/maps7") != 0)
184 {
185 dbg_msg(sys: "map_convert_07", fmt: "failed to create data/maps7 directory");
186 return -1;
187 }
188 }
189
190 if(!g_DataReader.Open(pStorage, pFilename: pSourceFileName, StorageType: IStorage::TYPE_ABSOLUTE))
191 {
192 dbg_msg(sys: "map_convert_07", fmt: "failed to open source map. filename='%s'", pSourceFileName);
193 return -1;
194 }
195
196 if(!g_DataWriter.Open(pStorage, pFilename: aDestFileName, StorageType: IStorage::TYPE_ABSOLUTE))
197 {
198 dbg_msg(sys: "map_convert_07", fmt: "failed to open destination map. filename='%s'", aDestFileName);
199 return -1;
200 }
201
202 g_NextDataItemId = g_DataReader.NumData();
203
204 size_t i = 0;
205 for(int Index = 0; Index < g_DataReader.NumItems(); Index++)
206 {
207 int Type;
208 g_DataReader.GetItem(Index, pType: &Type);
209 if(Type == MAPITEMTYPE_IMAGE)
210 {
211 if(i >= MAX_MAPIMAGES)
212 {
213 dbg_msg(sys: "map_convert_07", fmt: "map uses more images than the client maximum of %" PRIzu ". filename='%s'", MAX_MAPIMAGES, pSourceFileName);
214 break;
215 }
216 g_aImageIds[i] = Index;
217 i++;
218 }
219 }
220
221 bool Success = true;
222
223 // add all items
224 for(int Index = 0; Index < g_DataReader.NumItems(); Index++)
225 {
226 int Type, Id;
227 CUuid Uuid;
228 void *pItem = g_DataReader.GetItem(Index, pType: &Type, pId: &Id, pUuid: &Uuid);
229
230 // Filter ITEMTYPE_EX items, they will be automatically added again.
231 if(Type == ITEMTYPE_EX)
232 {
233 continue;
234 }
235
236 int Size = g_DataReader.GetItemSize(Index);
237 Success &= CheckImageDimensions(pLayerItem: pItem, LayerType: Type, pFilename: pSourceFileName);
238
239 CMapItemImage NewImageItem;
240 if(Type == MAPITEMTYPE_IMAGE)
241 {
242 pItem = ReplaceImageItem(Index, pImgItem: (CMapItemImage *)pItem, pNewImgItem: &NewImageItem);
243 if(!pItem)
244 return -1;
245 Size = sizeof(CMapItemImage);
246 NewImageItem.m_Version = CMapItemImage::CURRENT_VERSION;
247 }
248 g_DataWriter.AddItem(Type, Id, Size, pData: pItem, pUuid: &Uuid);
249 }
250
251 // add all data
252 for(int Index = 0; Index < g_DataReader.NumData(); Index++)
253 {
254 void *pData = g_DataReader.GetData(Index);
255 int Size = g_DataReader.GetDataSize(Index);
256 g_DataWriter.AddData(Size, pData);
257 }
258
259 for(int Index = 0; Index < g_Index; Index++)
260 {
261 g_DataWriter.AddData(Size: g_aNewDataSize[Index], pData: g_apNewData[Index]);
262 }
263
264 g_DataReader.Close();
265 g_DataWriter.Finish();
266 return Success ? 0 : -1;
267}
268