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
7#include <engine/gfx/image_loader.h>
8#include <engine/shared/datafile.h>
9#include <engine/storage.h>
10
11#include <game/gamecore.h>
12#include <game/mapitems.h>
13
14/*
15 Usage: map_convert_07 <source map filepath> <dest map filepath>
16*/
17
18static CDataFileReader g_DataReader;
19static CDataFileWriter g_DataWriter;
20
21// global new image data (set by ReplaceImageItem)
22static int g_aNewDataSize[MAX_MAPIMAGES];
23static void *g_apNewData[MAX_MAPIMAGES];
24
25static int g_Index = 0;
26static int g_NextDataItemId = -1;
27
28static int g_aImageIds[MAX_MAPIMAGES];
29
30static bool CheckImageDimensions(void *pLayerItem, int LayerType, const char *pFilename)
31{
32 if(LayerType != MAPITEMTYPE_LAYER)
33 return true;
34
35 CMapItemLayer *pImgLayer = (CMapItemLayer *)pLayerItem;
36 if(pImgLayer->m_Type != LAYERTYPE_TILES)
37 return true;
38
39 CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pImgLayer;
40 if(pTMap->m_Image == -1)
41 return true;
42
43 int Type;
44 void *pItem = g_DataReader.GetItem(Index: g_aImageIds[pTMap->m_Image], pType: &Type);
45 if(Type != MAPITEMTYPE_IMAGE)
46 return true;
47
48 CMapItemImage *pImgItem = (CMapItemImage *)pItem;
49
50 if(pImgItem->m_Width % 16 == 0 && pImgItem->m_Height % 16 == 0 && pImgItem->m_Width > 0 && pImgItem->m_Height > 0)
51 return true;
52
53 char aTileLayerName[12];
54 IntsToStr(pInts: pTMap->m_aName, NumInts: std::size(pTMap->m_aName), pStr: aTileLayerName, StrSize: std::size(aTileLayerName));
55
56 const char *pName = g_DataReader.GetDataString(Index: pImgItem->m_ImageName);
57 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);
58 return false;
59}
60
61static void *ReplaceImageItem(int Index, CMapItemImage *pImgItem, CMapItemImage *pNewImgItem)
62{
63 if(!pImgItem->m_External)
64 return pImgItem;
65
66 const char *pName = g_DataReader.GetDataString(Index: pImgItem->m_ImageName);
67 if(pName == nullptr || pName[0] == '\0')
68 {
69 dbg_msg(sys: "map_convert_07", fmt: "failed to load name of image %d", Index);
70 return pImgItem;
71 }
72
73 dbg_msg(sys: "map_convert_07", fmt: "embedding image '%s'", pName);
74
75 char aStr[IO_MAX_PATH_LENGTH];
76 str_format(buffer: aStr, buffer_size: sizeof(aStr), format: "data/mapres/%s.png", pName);
77
78 CImageInfo ImgInfo;
79 int PngliteIncompatible;
80 if(!CImageLoader::LoadPng(File: io_open(filename: aStr, flags: IOFLAG_READ), pFilename: aStr, Image&: ImgInfo, PngliteIncompatible))
81 return pImgItem; // keep as external if we don't have a mapres to replace
82
83 const size_t MaxImageDimension = 1 << 13;
84 if(ImgInfo.m_Format != CImageInfo::FORMAT_RGBA || ImgInfo.m_Width > MaxImageDimension || ImgInfo.m_Height > MaxImageDimension)
85 {
86 dbg_msg(sys: "map_convert_07", fmt: "ERROR: only RGBA PNG images with maximum width/height %" PRIzu " are supported", MaxImageDimension);
87 ImgInfo.Free();
88 return pImgItem;
89 }
90
91 *pNewImgItem = *pImgItem;
92
93 pNewImgItem->m_Width = ImgInfo.m_Width;
94 pNewImgItem->m_Height = ImgInfo.m_Height;
95 pNewImgItem->m_External = false;
96 pNewImgItem->m_ImageData = g_NextDataItemId++;
97
98 g_apNewData[g_Index] = ImgInfo.m_pData;
99 g_aNewDataSize[g_Index] = ImgInfo.DataSize();
100 g_Index++;
101
102 return (void *)pNewImgItem;
103}
104
105int main(int argc, const char **argv)
106{
107 CCmdlineFix CmdlineFix(&argc, &argv);
108 log_set_global_logger_default();
109
110 if(argc < 2 || argc > 3)
111 {
112 dbg_msg(sys: "map_convert_07", fmt: "Invalid arguments");
113 dbg_msg(sys: "map_convert_07", fmt: "Usage: map_convert_07 <source map filepath> [<dest map filepath>]");
114 return -1;
115 }
116
117 std::unique_ptr<IStorage> pStorage = std::unique_ptr<IStorage>(CreateStorage(InitializationType: IStorage::EInitializationType::BASIC, NumArgs: argc, ppArguments: argv));
118 if(!pStorage)
119 {
120 log_error("map_convert_07", "Error creating basic storage");
121 return -1;
122 }
123
124 const char *pSourceFilename = argv[1];
125 char aDestFilename[IO_MAX_PATH_LENGTH];
126
127 if(argc == 3)
128 {
129 str_copy(dst: aDestFilename, src: argv[2], dst_size: sizeof(aDestFilename));
130 }
131 else
132 {
133 char aBuf[IO_MAX_PATH_LENGTH];
134 IStorage::StripPathAndExtension(pFilename: pSourceFilename, pBuffer: aBuf, BufferSize: sizeof(aBuf));
135 str_format(buffer: aDestFilename, buffer_size: sizeof(aDestFilename), format: "data/maps7/%s.map", aBuf);
136 if(fs_makedir(path: "data") != 0)
137 {
138 dbg_msg(sys: "map_convert_07", fmt: "failed to create data directory");
139 return -1;
140 }
141
142 if(fs_makedir(path: "data/maps7") != 0)
143 {
144 dbg_msg(sys: "map_convert_07", fmt: "failed to create data/maps7 directory");
145 return -1;
146 }
147 }
148
149 if(!g_DataReader.Open(pStorage: pStorage.get(), pFilename: pSourceFilename, StorageType: IStorage::TYPE_ABSOLUTE))
150 {
151 dbg_msg(sys: "map_convert_07", fmt: "failed to open source map. filename='%s'", pSourceFilename);
152 return -1;
153 }
154
155 if(!g_DataWriter.Open(pStorage: pStorage.get(), pFilename: aDestFilename, StorageType: IStorage::TYPE_ABSOLUTE))
156 {
157 dbg_msg(sys: "map_convert_07", fmt: "failed to open destination map. filename='%s'", aDestFilename);
158 return -1;
159 }
160
161 g_NextDataItemId = g_DataReader.NumData();
162
163 size_t i = 0;
164 for(int Index = 0; Index < g_DataReader.NumItems(); Index++)
165 {
166 int Type;
167 g_DataReader.GetItem(Index, pType: &Type);
168 if(Type == MAPITEMTYPE_IMAGE)
169 {
170 if(i >= MAX_MAPIMAGES)
171 {
172 dbg_msg(sys: "map_convert_07", fmt: "map uses more images than the client maximum of %" PRIzu ". filename='%s'", MAX_MAPIMAGES, pSourceFilename);
173 break;
174 }
175 g_aImageIds[i] = Index;
176 i++;
177 }
178 }
179
180 bool Success = true;
181
182 // add all items
183 for(int Index = 0; Index < g_DataReader.NumItems(); Index++)
184 {
185 int Type, Id;
186 CUuid Uuid;
187 void *pItem = g_DataReader.GetItem(Index, pType: &Type, pId: &Id, pUuid: &Uuid);
188
189 // Filter ITEMTYPE_EX items, they will be automatically added again.
190 if(Type == ITEMTYPE_EX)
191 {
192 continue;
193 }
194
195 int Size = g_DataReader.GetItemSize(Index);
196 Success &= CheckImageDimensions(pLayerItem: pItem, LayerType: Type, pFilename: pSourceFilename);
197
198 CMapItemImage NewImageItem;
199 if(Type == MAPITEMTYPE_IMAGE)
200 {
201 pItem = ReplaceImageItem(Index, pImgItem: (CMapItemImage *)pItem, pNewImgItem: &NewImageItem);
202 if(!pItem)
203 return -1;
204 Size = sizeof(CMapItemImage);
205 NewImageItem.m_Version = 1;
206 }
207 g_DataWriter.AddItem(Type, Id, Size, pData: pItem, pUuid: &Uuid);
208 }
209
210 // add all data
211 for(int Index = 0; Index < g_DataReader.NumData(); Index++)
212 {
213 void *pData = g_DataReader.GetData(Index);
214 int Size = g_DataReader.GetDataSize(Index);
215 g_DataWriter.AddData(Size, pData);
216 }
217
218 for(int Index = 0; Index < g_Index; Index++)
219 {
220 g_DataWriter.AddData(Size: g_aNewDataSize[Index], pData: g_apNewData[Index]);
221 }
222
223 g_DataReader.Close();
224 g_DataWriter.Finish();
225 return Success ? 0 : -1;
226}
227